Commit b15bc194 authored by Alexander Alekhin's avatar Alexander Alekhin

doc: finalize Python signatures injection

parent 164a77e7
...@@ -218,7 +218,7 @@ if(BUILD_DOCS AND DOXYGEN_FOUND) ...@@ -218,7 +218,7 @@ if(BUILD_DOCS AND DOXYGEN_FOUND)
COMPONENT "docs" OPTIONAL COMPONENT "docs" OPTIONAL
) )
if(BUILD_opencv_python2) if(PYTHON2_EXECUTABLE)
add_custom_target(doxygen_python add_custom_target(doxygen_python
COMMAND ${PYTHON2_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/tools/add_signatures.py" "${CMAKE_CURRENT_BINARY_DIR}/doxygen/html/" "${OPENCV_PYTHON2_SIGNATURES_FILE}" "python" COMMAND ${PYTHON2_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/tools/add_signatures.py" "${CMAKE_CURRENT_BINARY_DIR}/doxygen/html/" "${OPENCV_PYTHON2_SIGNATURES_FILE}" "python"
DEPENDS "${doxygen_result}" gen_opencv_python2 DEPENDS "${doxygen_result}" gen_opencv_python2
...@@ -226,7 +226,7 @@ if(BUILD_DOCS AND DOXYGEN_FOUND) ...@@ -226,7 +226,7 @@ if(BUILD_DOCS AND DOXYGEN_FOUND)
add_custom_target(doxygen add_custom_target(doxygen
DEPENDS doxygen_cpp doxygen_python DEPENDS doxygen_cpp doxygen_python
) )
elseif(BUILD_opencv_python3) elseif(PYTHON3_EXECUTABLE)
add_custom_target(doxygen_python add_custom_target(doxygen_python
COMMAND ${PYTHON3_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/tools/add_signatures.py" "${CMAKE_CURRENT_BINARY_DIR}/doxygen/html/" "${OPENCV_PYTHON3_SIGNATURES_FILE}" "python" COMMAND ${PYTHON3_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/tools/add_signatures.py" "${CMAKE_CURRENT_BINARY_DIR}/doxygen/html/" "${OPENCV_PYTHON3_SIGNATURES_FILE}" "python"
DEPENDS "${doxygen_result}" gen_opencv_python3 DEPENDS "${doxygen_result}" gen_opencv_python3
......
...@@ -12,10 +12,15 @@ TODO: ...@@ -12,10 +12,15 @@ TODO:
http://docs.opencv.org/3.2.0/db/de0/group__core__utils.html#ga4910d7f86336cd4eff9dd05575667e41 http://docs.opencv.org/3.2.0/db/de0/group__core__utils.html#ga4910d7f86336cd4eff9dd05575667e41
""" """
from __future__ import print_function from __future__ import print_function
import sys
sys.dont_write_bytecode = True # Don't generate .pyc files / __pycache__ directories
import os import os
from pprint import pprint
import re import re
import sys
import logging import logging
import json
import html_functions import html_functions
import doxygen_scan import doxygen_scan
...@@ -23,37 +28,23 @@ loglevel=os.environ.get("LOGLEVEL", None) ...@@ -23,37 +28,23 @@ loglevel=os.environ.get("LOGLEVEL", None)
if loglevel: if loglevel:
logging.basicConfig(level=loglevel) logging.basicConfig(level=loglevel)
ROOT_DIR = sys.argv[1] ROOT_DIR = sys.argv[1]
PYTHON_SIGNATURES_FILE = sys.argv[2] PYTHON_SIGNATURES_FILE = sys.argv[2]
JAVA_PYTHON = sys.argv[3] JAVA_OR_PYTHON = sys.argv[3]
ADD_JAVA = False ADD_JAVA = False
ADD_PYTHON = False ADD_PYTHON = False
if JAVA_PYTHON == "python": if JAVA_OR_PYTHON == "python":
ADD_PYTHON = True ADD_PYTHON = True
import json
python_signatures = dict() python_signatures = dict()
with open(PYTHON_SIGNATURES_FILE, "rt") as f: with open(PYTHON_SIGNATURES_FILE, "rt") as f:
python_signatures = json.load(f) python_signatures = json.load(f)
print("Loaded Python signatures: %d" % len(python_signatures)) print("Loaded Python signatures: %d" % len(python_signatures))
# only name -> class
# name and ret -> constant
# name, ret, arg-> function / class method
class Configuration():
def __init__(self):
self.ADD_PYTHON = ADD_PYTHON
self.python_signatures = python_signatures
self.ADD_JAVA = ADD_JAVA
config = Configuration()
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
root = ET.parse(ROOT_DIR + 'opencv.tag') root = ET.parse(ROOT_DIR + 'opencv.tag')
files_dict = dict() files_dict = {}
# constants and function from opencv.tag # constants and function from opencv.tag
namespaces = root.findall("./compound[@kind='namespace']") namespaces = root.findall("./compound[@kind='namespace']")
...@@ -61,41 +52,48 @@ namespaces = root.findall("./compound[@kind='namespace']") ...@@ -61,41 +52,48 @@ namespaces = root.findall("./compound[@kind='namespace']")
for ns in namespaces: for ns in namespaces:
ns_name = ns.find("./name").text ns_name = ns.find("./name").text
#print('NS: {}'.format(ns_name)) #print('NS: {}'.format(ns_name))
doxygen_scan.scan_namespace_constants(ns, ns_name, files_dict)
files_dict = doxygen_scan.scan_namespace_constants(ns, ns_name, files_dict) doxygen_scan.scan_namespace_functions(ns, ns_name, files_dict)
files_dict = doxygen_scan.scan_namespace_functions(ns, ns_name, files_dict)
# class methods from opencv.tag # class methods from opencv.tag
classes = root.findall("./compound[@kind='class']") classes = root.findall("./compound[@kind='class']")
#print("Found {} classes".format(len(classes))) #print("Found {} classes".format(len(classes)))
for c in classes: for c in classes:
c_name = c.find("./name").text c_name = c.find("./name").text
name = ns_name + '::' + c_name
file = c.find("./filename").text file = c.find("./filename").text
#print('Class: {} => {}'.format(name, file)) #print('Class: {} => {}'.format(c_name, file))
files_dict = doxygen_scan.scan_class_methods(c, c_name, files_dict) doxygen_scan.scan_class_methods(c, c_name, files_dict)
print('Doxygen files to scan: %s' % len(files_dict))
files_processed = 0
files_skipped = 0
symbols_processed = 0
# test
for file in files_dict: for file in files_dict:
soup = html_functions.load_html_file(ROOT_DIR + file) #if file != "dd/d9e/classcv_1_1VideoWriter.html":
if file == "dd/d9e/classcv_1_1VideoWriter.html":#"d4/d86/group__imgproc__filter.html":#"d4/d86/group__imgproc__filter.html": #if file != "d4/d86/group__imgproc__filter.html":
#if file != "df/dfb/group__imgproc__object.html":
# continue
#print('File: ' + file)
anchor_list = files_dict[file] anchor_list = files_dict[file]
counter = 0 active_anchors = [a for a in anchor_list if a.cppname in python_signatures]
anchor_tmp_list = [] if len(active_anchors) == 0: # no linked Python symbols
for anchor in anchor_list: #print('Skip: ' + file)
counter += 1 files_skipped = files_skipped + 1
# if the next anchor shares the same C++ name (= same method/function), join them together
if counter < len(anchor_list) and anchor_list[counter].cppname == anchor.cppname:
anchor_tmp_list.append(anchor)
continue continue
else:
anchor_tmp_list.append(anchor) active_anchors_dict = {a.anchor: a for a in active_anchors}
# check if extists a python equivalent signature if len(active_anchors_dict) != len(active_anchors):
for signature in python_signatures: # signature is a key with the C++ name logging.info('Duplicate entries detected: %s -> %s (%s)' % (len(active_anchors), len(active_anchors_dict), file))
if signature == anchor.cppname: # if available name in python
# they should also have the same type files_processed = files_processed + 1
soup = html_functions.append_python_signature(python_signatures[signature], anchor_tmp_list, soup)
#print(signature) #pprint(active_anchors)
# reset anchor temporary list symbols_processed = symbols_processed + len(active_anchors_dict)
anchor_tmp_list[:] = []
html_functions.update_html(ROOT_DIR + file, soup) logging.info('File: %r' % file)
html_functions.insert_python_signatures(python_signatures, active_anchors_dict, ROOT_DIR + file)
print('Done (processed files %d, symbols %d, skipped %d files)' % (files_processed, symbols_processed, files_skipped))
class Anchor(object): import traceback
anchor = ""
type = ""
cppname = ""
class Symbol(object):
def __init__(self, anchor, type, cppname): def __init__(self, anchor, type, cppname):
self.anchor = anchor self.anchor = anchor
self.type = type self.type = type
self.cppname = cppname self.cppname = cppname
#if anchor == 'ga586ebfb0a7fb604b35a23d85391329be':
# print(repr(self))
# traceback.print_stack()
def __repr__(self):
return '%s:%s@%s' % (self.type, self.cppname, self.anchor)
def add_to_file(files_dict, file, anchor): def add_to_file(files_dict, file, anchor):
if file in files_dict: anchors = files_dict.setdefault(file, [])
# if that file already exists as a key in the dictionary anchors.append(anchor)
files_dict[file].append(anchor)
else:
files_dict[file] = [anchor]
return files_dict
def scan_namespace_constants(ns, ns_name, files_dict): def scan_namespace_constants(ns, ns_name, files_dict):
...@@ -25,8 +25,7 @@ def scan_namespace_constants(ns, ns_name, files_dict): ...@@ -25,8 +25,7 @@ def scan_namespace_constants(ns, ns_name, files_dict):
file = c.find("./anchorfile").text file = c.find("./anchorfile").text
anchor = c.find("./anchor").text anchor = c.find("./anchor").text
#print(' CONST: {} => {}#{}'.format(name, file, anchor)) #print(' CONST: {} => {}#{}'.format(name, file, anchor))
files_dict = add_to_file(files_dict, file, Anchor(anchor, "const", name)) add_to_file(files_dict, file, Symbol(anchor, "const", name))
return files_dict
def scan_namespace_functions(ns, ns_name, files_dict): def scan_namespace_functions(ns, ns_name, files_dict):
functions = ns.findall("./member[@kind='function']") functions = ns.findall("./member[@kind='function']")
...@@ -36,8 +35,7 @@ def scan_namespace_functions(ns, ns_name, files_dict): ...@@ -36,8 +35,7 @@ def scan_namespace_functions(ns, ns_name, files_dict):
file = f.find("./anchorfile").text file = f.find("./anchorfile").text
anchor = f.find("./anchor").text anchor = f.find("./anchor").text
#print(' FN: {} => {}#{}'.format(name, file, anchor)) #print(' FN: {} => {}#{}'.format(name, file, anchor))
files_dict = add_to_file(files_dict, file, Anchor(anchor, "fn", name)) add_to_file(files_dict, file, Symbol(anchor, "fn", name))
return files_dict
def scan_class_methods(c, c_name, files_dict): def scan_class_methods(c, c_name, files_dict):
methods = c.findall("./member[@kind='function']") methods = c.findall("./member[@kind='function']")
...@@ -47,5 +45,4 @@ def scan_class_methods(c, c_name, files_dict): ...@@ -47,5 +45,4 @@ def scan_class_methods(c, c_name, files_dict):
file = m.find("./anchorfile").text file = m.find("./anchorfile").text
anchor = m.find("./anchor").text anchor = m.find("./anchor").text
#print(' Method: {} => {}#{}'.format(name, file, anchor)) #print(' Method: {} => {}#{}'.format(name, file, anchor))
files_dict = add_to_file(files_dict, file, Anchor(anchor, "method", name)) add_to_file(files_dict, file, Symbol(anchor, "method", name))
return files_dict
from __future__ import print_function from __future__ import print_function
import sys
import logging import logging
import os import os
from pprint import pprint from pprint import pprint
import traceback
try: try:
import bs4 import bs4
...@@ -13,195 +16,110 @@ except ImportError: ...@@ -13,195 +16,110 @@ except ImportError:
def load_html_file(file_dir): def load_html_file(file_dir):
""" Uses BeautifulSoup to load an html """ """ Uses BeautifulSoup to load an html """
with open(file_dir) as fp: with open(file_dir, 'rb') as fp:
soup = BeautifulSoup(fp, 'html.parser') soup = BeautifulSoup(fp, 'html.parser')
return soup return soup
def add_item(soup, new_row, is_parameter, text): def update_html(file, soup):
""" Adds a new html tag for the table with the signature """ s = str(soup)
new_item = soup.new_tag('td') if os.name == 'nt' or sys.version_info[0] == 3: # if Windows
if is_parameter: s = s.encode('utf-8', 'ignore')
new_item = soup.new_tag('td', **{'class': 'paramname'}) with open(file, 'wb') as f:
new_item.append(text) f.write(s)
new_row.append(new_item)
return new_row, soup
def insert_python_signatures(python_signatures, symbols_dict, filepath):
soup = load_html_file(filepath)
entries = soup.find_all(lambda tag: tag.name == "a" and tag.has_attr('id'))
for e in entries:
anchor = e['id']
if anchor in symbols_dict:
s = symbols_dict[anchor]
logging.info('Process: %r' % s)
if s.type == 'fn' or s.type == 'method':
process_fn(soup, e, python_signatures[s.cppname], s)
elif s.type == 'const':
process_const(soup, e, python_signatures[s.cppname], s)
else:
logging.error('unsupported type: %s' % s);
def add_signature_to_table(soup, tmp_row, signature, language, type): update_html(filepath, soup)
""" Add a signature to an html table"""
new_item = soup.new_tag('td', style="padding-left: 0.5cm;")
if str(signature.get('ret', None)) != "None":
new_item.append(signature.get('ret') + ' =')
tmp_row.append(new_item)
tmp_name = signature.get('name', None) def process_fn(soup, anchor, python_signature, symbol):
if type is not "method": try:
tmp_name = "cv2." + tmp_name r = anchor.find_next_sibling(class_='memitem').find(class_='memproto').find('table')
else: insert_python_fn_signature(soup, r, python_signature, symbol)
tmp_name = "obj." + tmp_name except:
tmp_row, soup = add_item(soup, tmp_row, False, tmp_name + '(') logging.error("Can't process: %s" % symbol)
tmp_row, soup = add_item(soup, tmp_row, True, signature['arg']) traceback.print_exc()
tmp_row, soup = add_item(soup, tmp_row, False, ')') pprint(anchor)
return tmp_row, soup
def new_line(soup, tmp_table, new_row):
""" Adds a new line to the html table """
tmp_table.append(new_row)
new_row = soup.new_tag('tr')
return new_row, soup
def add_bolded(soup, new_row, text):
""" Adds bolded text to the table """
new_item = soup.new_tag('th', style="text-align:left")
new_item.append(text)
new_row.append(new_item)
return new_row, soup
def create_description(soup, language, signatures, type):
""" Insert the new Python / Java table after the current html c++ table """
assert signatures
tmp_table = soup.new_tag('table')
new_row = soup.new_tag('tr')
new_row, soup = add_bolded(soup, new_row, language)
new_row, soup = new_line(soup, tmp_table, new_row)
for s in signatures:
new_row, soup = new_line(soup, tmp_table, new_row)
new_row, soup = add_signature_to_table(soup, new_row, s, language, type)
new_row, soup = new_line(soup, tmp_table, new_row)
return tmp_table, soup
def get_anchor_list(anchor, soup):
a_list = []
# go through all the links
for a in soup.find_all('a', href=True):
# find links with the same anchor
last_part_of_link = a['href'].rsplit('#', 1)[-1]
if last_part_of_link == anchor:
a_list.append(a)
return a_list
def is_static_method(element):
if element.name == "table":
tmp_element = element.find('td', {'class': 'memname'})
if tmp_element is not None:
if 'static' in tmp_element.text:
return True
else:
if element['class'][0] == 'memItemRight':
if "static" in element.previousSibling.text:
return True
return False
def append_python_signatures_to_table(soup, signatures, table, type):
if type == "method":
if is_static_method(table):
type = "static" + type
description, soup = create_description(soup, "Python:", signatures, type)
description['class'] = 'python_language'
soup = insert_or_replace(soup, table, description, "table", "python_language")
return soup
def get_heading_text(a): def process_const(soup, anchor, python_signature, symbol):
str = "" try:
element = a.parent #pprint(anchor.parent)
if element is not None: description = append(soup.new_tag('div', **{'class' : ['python_language']}),
childs = element.find_all('a') 'Python: ' + python_signature[0]['name'])
# the anchor should not be an argument of a function / method old = anchor.find_next_sibling('div', class_='python_language')
if childs.index(a) is not 0:
return str
element = element.parent
if element is not None:
if element.has_attr('class'):
tmp_class = element["class"][0]
if "memitem:" in tmp_class and "python" not in tmp_class:
str = element.parent.find("tr").text
return str
def insert_or_replace(soup, element, description, tag_name, tag_class):
old = element.next_sibling
if old is not None:
if old.name != tag_name:
old = None
elif not tag_class in old.get('class', []):
old = None
# if already existed replace with the new
if old is None: if old is None:
element.insert_after(description) anchor.parent.append(description)
else: else:
old.replace_with(description) old.replace_with(description)
return soup #pprint(anchor.parent)
except:
logging.error("Can't process: %s" % symbol)
traceback.print_exc()
pprint(anchor)
def new_heading_td(soup, s, href, type):
if href is None:
attrs = {'class': 'memItemLeft', 'valign': 'top', 'align': 'right'}
new_td = soup.new_tag('td', **attrs)
new_td.append(str(s.get('ret', None)))
else:
attrs = {'class': 'memItemRight', 'valign': 'bottom'}
new_td = soup.new_tag('td', **attrs)
# make the function name linkable
attrs_a = {'class': 'el', 'href': href}
new_a = soup.new_tag('a', **attrs_a)
tmp_name = str(s.get('name', None))
if type is not "method":
tmp_name = "cv2." + tmp_name
else:
tmp_name = "obj." + tmp_name
new_a.append(tmp_name)
new_td.append(new_a)
new_td.append("(" + s['arg'] +")") def insert_python_fn_signature(soup, table, variants, symbol):
return soup, new_td description = create_python_fn_description(soup, variants)
description['class'] = 'python_language'
soup = insert_or_replace(table, description, 'table', 'python_language')
return soup
def append_python_signatures_to_heading(soup, signatures, element, href, type):
if type == "method":
if is_static_method(element):
type = "static" + type
for s in signatures:
attrs = {'class': 'memitem:python'}
new_tr = soup.new_tag('tr', **attrs)
soup, new_td_left = new_heading_td(soup, s, None, type) def create_python_fn_description(soup, variants):
new_tr.append(new_td_left) language = 'Python:'
table = soup.new_tag('table')
heading_row = soup.new_tag('th')
table.append(
append(soup.new_tag('tr'),
append(soup.new_tag('th', colspan=999, style="text-align:left"), language)))
for v in variants:
#logging.debug(v)
add_signature_to_table(soup, table, v, language, type)
#print(table)
return table
soup, new_td_right = new_heading_td(soup, s, href, type)
new_tr.append(new_td_right)
soup = insert_or_replace(soup, element, new_tr, "tr", "memitem:python") def add_signature_to_table(soup, table, signature, language, type):
return soup """ Add a signature to an html table"""
row = soup.new_tag('tr')
row.append(soup.new_tag('td', style='width: 20px;'))
def append_python_signature(function_variants, anchor_list, soup): if 'ret' in signature:
type = anchor_list[0].type row.append(append(soup.new_tag('td'), signature['ret']))
if type == "method" or type == "fn": row.append(append(soup.new_tag('td'), '='))
if len(anchor_list) == 1:
tmp_anchor = anchor_list[0].anchor
a_list = get_anchor_list(tmp_anchor, soup)
for a in a_list:
if a['href'] == "#" + tmp_anchor:
tmp_element = a.parent
# ignore the More... link <td class = mdescRight>
if tmp_element is None or tmp_element['class'][0] == 'mdescRight':
continue
# Function Documentation (tables)
table = a.findNext('table')
if table is not None:
soup = append_python_signatures_to_table(soup, function_variants, table, type)
else: else:
str = get_heading_text(a) row.append(soup.new_tag('td')) # return values
if "Functions" in str: row.append(soup.new_tag('td')) # '='
soup = append_python_signatures_to_heading(soup, function_variants, a.parent, a['href'], type)
return soup
def update_html(file, soup): row.append(append(soup.new_tag('td'), signature['name'] + '('))
tmp_str = str(soup) row.append(append(soup.new_tag('td', **{'class': 'paramname'}), signature['arg']))
if os.name == 'nt': # if Windows row.append(append(soup.new_tag('td'), ')'))
with open(file, "wb") as tmp_file: table.append(row)
tmp_file.write(tmp_str.encode("ascii","ignore"))
def append(target, obj):
target.append(obj)
return target
def insert_or_replace(element_before, new_element, tag, tag_class):
old = element_before.find_next_sibling(tag, class_=tag_class)
if old is None:
element_before.insert_after(new_element)
else: else:
with open(file, "w") as tmp_file: old.replace_with(new_element)
tmp_file.write(tmp_str)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment