216 lines
9.1 KiB
Python
216 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
|
|
|
###############################################################################/
|
|
# Copyright (c) 2021 Nerian Vision GmbH
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
###############################################################################/
|
|
|
|
#
|
|
# This helper script auto-generates pydoc comments (Google-style syntax)
|
|
# from the Doxygen comments in the specified Nerian headers.
|
|
#
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
|
|
def print_error(what):
|
|
sys.stderr.write(what + '\n')
|
|
|
|
class RegexMatcher(object):
|
|
def __init__(self):
|
|
self.result = None
|
|
def search(self, regex, where):
|
|
self.result = re.search(regex, where)
|
|
return self.result is not None
|
|
def group(self, i=0):
|
|
return self.result.group(i)
|
|
def groups(self):
|
|
return self.result.groups()
|
|
|
|
class DocstringExtractor(object):
|
|
def __init__(self):
|
|
self.docstrings = {}
|
|
pass
|
|
|
|
def snake_case(self, fnname):
|
|
'''Convert mixed case to Python methods' snake case'''
|
|
fnname_snake = ''
|
|
for c in fnname:
|
|
if c.isupper():
|
|
fnname_snake += '_' + c.lower()
|
|
else:
|
|
fnname_snake += c
|
|
# Some conventional exceptions :)
|
|
fnname_snake = fnname_snake.replace('r_o_i', 'roi')
|
|
return fnname_snake
|
|
|
|
def beautified_docstring(self, comment, indent=8):
|
|
ds = ''
|
|
cs = [l.strip() for l in comment.split('\n')] # if l.strip()!='']
|
|
# remove leading blank lines
|
|
reallines = list(filter(lambda x: x>0, [c!='' for c in cs]))
|
|
if len(reallines):
|
|
cs = cs[reallines[0]:]
|
|
#
|
|
printed_kwarg = False
|
|
extra_indent = 0
|
|
for i, c in enumerate(cs):
|
|
if c.strip() == '':
|
|
extra_indent = 0
|
|
next_is_param = False
|
|
cnew = ''
|
|
increase_extra_indent = 0
|
|
for j, w in enumerate(c.split()):
|
|
if w in ['\\brief', '\\c']:
|
|
pass
|
|
elif w in ['\\return']:
|
|
ds += '\n'
|
|
ds += ' '*indent + 'Returns:\n'
|
|
extra_indent = 4
|
|
increase_extra_indent = 4
|
|
elif w in ['\\param']:
|
|
if not printed_kwarg:
|
|
ds += ' '*indent + 'Args:\n'
|
|
extra_indent = 4
|
|
increase_extra_indent = 4
|
|
printed_kwarg = True
|
|
next_is_param = True
|
|
pass
|
|
elif w.startswith('\\'):
|
|
cnew += (' ' if len(cnew) else '') + w[1].upper()+w[2:]+': '
|
|
else:
|
|
cnew += (' ' if len(cnew) else '') + w
|
|
if next_is_param:
|
|
cnew += ':'
|
|
next_is_param = False
|
|
ds += ' '*indent + ' '*extra_indent + ("'''" if i==0 else "") + cnew + ("'''\n" if i==len(cs)-1 else "")
|
|
ds += '\n'
|
|
extra_indent += increase_extra_indent
|
|
return ds
|
|
|
|
def generate(self, basedir, filename):
|
|
with open(basedir + '/' + filename, 'r') as f:
|
|
in_comment = False
|
|
comment = ''
|
|
names = []
|
|
currentname = ''
|
|
currentargs = ''
|
|
level = 0
|
|
restl =''
|
|
for rawl in [ll.strip() for ll in f.readlines()]:
|
|
l = restl + rawl
|
|
had_restl = len(restl) > 0
|
|
restl = ''
|
|
apply_comment = False
|
|
if in_comment:
|
|
end = l.find('*/')
|
|
thisline = (l if end<0 else l[:end]).lstrip('*').strip()
|
|
#if thisline != '':
|
|
comment += '\n' + thisline
|
|
if end >= 0:
|
|
in_comment = False
|
|
else:
|
|
start = l.find('/**')
|
|
if start >= 0:
|
|
currentname = '' # force finding new name
|
|
currentargs = ''
|
|
in_comment = True
|
|
comment = l[start+3:]
|
|
else:
|
|
rem = RegexMatcher()
|
|
if rem.search(r'(namespace|class|enum)([^:]*).*[{;]', l):
|
|
if comment != '':
|
|
cls = rem.group(2).strip().split()[-1]
|
|
currentname = cls
|
|
currentargs = ''
|
|
apply_comment = True
|
|
elif rem.search(r'[ \t]*(.*)\(', l): # match word and opening paren
|
|
if currentname == '':
|
|
cls = rem.group(1).strip().split()[-1]
|
|
currentname = cls
|
|
if rem.search(r'[ \t]*([^(]*)\((.*)\).*[{;]', l) and l.count('(') == l.count(')'): #: # match function
|
|
if l.count('(') == l.count(')'):
|
|
# reduce argument list (just names, no types or defaults)
|
|
args_just_names = [(a.split('=')[0].strip().split()[-1] if a.strip()!='' else '') for a in rem.group(2).split(',')]
|
|
currentargs = '(' + (', '.join(args_just_names)) + ')'
|
|
if comment != '':
|
|
apply_comment = True
|
|
else: # match partial fn or something like it
|
|
restl = l # save line for next iteration
|
|
continue # and proceed to next line
|
|
else:
|
|
pass
|
|
if apply_comment:
|
|
ns = names + [currentname+currentargs]
|
|
ns = [n for n in ns if n!='']
|
|
name = '::'.join(ns)
|
|
if name in self.docstrings and len(ns)>1: # warn, but not for the namespace doc
|
|
print_error('Note: not overwriting previous docstring for '+name)
|
|
else:
|
|
self.docstrings[name] = self.beautified_docstring(comment, indent=8)
|
|
|
|
comment = ''
|
|
for j in range(l.count('{')):
|
|
level += 1
|
|
names.append(currentname+currentargs)
|
|
currentname = ''
|
|
currentargs = ''
|
|
for j in range(l.count('}')):
|
|
level -= 1
|
|
names = names[:-1]
|
|
currentname = ''
|
|
currentargs = ''
|
|
|
|
def store_docstrings_to_file(self, filename='', fobj=None):
|
|
f = open(filename, 'w') if fobj is None else fobj
|
|
f.write('''
|
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
# !! CAUTION !!
|
|
# !! !!
|
|
# !! This file is autogenerated from the libvisiontransfer headers !!
|
|
# !! using autogen_docstrings.py - manual changes are not permanent !!
|
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!''' + '\n\n')
|
|
f.write('_NERIAN_COMPILED_DOCSTRINGS = {\n')
|
|
for name, comment in self.docstrings.items():
|
|
f.write(" '"+ name + "': \\\n")
|
|
f.write(comment.rstrip('\n') + ',\n')
|
|
f.write('}\n')
|
|
f.write("\n# Also add parameter-less versions for convenience (duplicates overwritten)\n")
|
|
f.write("for __k in list(_NERIAN_COMPILED_DOCSTRINGS):\n")
|
|
f.write(" if __k.count('('):\n")
|
|
f.write(" _NERIAN_COMPILED_DOCSTRINGS[__k.split('(')[0]] = _NERIAN_COMPILED_DOCSTRINGS[__k]\n\n")
|
|
if fobj is not None:
|
|
f.close()
|
|
|
|
if __name__=='__main__':
|
|
basedir = os.getenv("LIBVISIONTRANSFER_SRCDIR", '../..')
|
|
if os.path.isdir(basedir):
|
|
d = DocstringExtractor()
|
|
for filename in [
|
|
'visiontransfer/deviceparameters.h',
|
|
'visiontransfer/imageset.h',
|
|
'visiontransfer/imageprotocol.h',
|
|
'visiontransfer/imagetransfer.h',
|
|
'visiontransfer/asynctransfer.h',
|
|
'visiontransfer/deviceenumeration.h',
|
|
'visiontransfer/deviceinfo.h',
|
|
'visiontransfer/sensordata.h',
|
|
'visiontransfer/datachannelservice.h',
|
|
'visiontransfer/reconstruct3d.h',
|
|
]:
|
|
d.generate(basedir, filename)
|
|
|
|
d.store_docstrings_to_file('visiontransfer_src/visiontransfer_docstrings_autogen.py')
|
|
else:
|
|
print("Could not open library base dir, please set a correct LIBVISIONTRANSFER_SRCDIR")
|
|
|