#!/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")