diff options
Diffstat (limited to 'gr-utils/modtool/templates/gr-newmod/docs/doxygen')
-rw-r--r-- | gr-utils/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in | 2 | ||||
-rw-r--r-- | gr-utils/modtool/templates/gr-newmod/docs/doxygen/pydoc_macros.h | 19 | ||||
-rw-r--r-- | gr-utils/modtool/templates/gr-newmod/docs/doxygen/update_pydoc.py (renamed from gr-utils/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py) | 209 |
3 files changed, 137 insertions, 93 deletions
diff --git a/gr-utils/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in b/gr-utils/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in index 55816e8252..307fed08c5 100644 --- a/gr-utils/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in +++ b/gr-utils/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in @@ -723,8 +723,6 @@ EXCLUDE_PATTERNS = */.deps/* \ EXCLUDE_SYMBOLS = ad9862 \ numpy \ - *swig* \ - *Swig* \ *my_top_block* \ *my_graph* \ *app_top_block* \ diff --git a/gr-utils/modtool/templates/gr-newmod/docs/doxygen/pydoc_macros.h b/gr-utils/modtool/templates/gr-newmod/docs/doxygen/pydoc_macros.h new file mode 100644 index 0000000000..98bf7cd639 --- /dev/null +++ b/gr-utils/modtool/templates/gr-newmod/docs/doxygen/pydoc_macros.h @@ -0,0 +1,19 @@ +#ifndef PYDOC_MACROS_H +#define PYDOC_MACROS_H + +#define __EXPAND(x) x +#define __COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT +#define __VA_SIZE(...) __EXPAND(__COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1)) +#define __CAT1(a, b) a##b +#define __CAT2(a, b) __CAT1(a, b) +#define __DOC1(n1) __doc_##n1 +#define __DOC2(n1, n2) __doc_##n1##_##n2 +#define __DOC3(n1, n2, n3) __doc_##n1##_##n2##_##n3 +#define __DOC4(n1, n2, n3, n4) __doc_##n1##_##n2##_##n3##_##n4 +#define __DOC5(n1, n2, n3, n4, n5) __doc_##n1##_##n2##_##n3##_##n4##_##n5 +#define __DOC6(n1, n2, n3, n4, n5, n6) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 +#define __DOC7(n1, n2, n3, n4, n5, n6, n7) \ + __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 +#define DOC(...) __EXPAND(__EXPAND(__CAT2(__DOC, __VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) + +#endif // PYDOC_MACROS_H
\ No newline at end of file diff --git a/gr-utils/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py b/gr-utils/modtool/templates/gr-newmod/docs/doxygen/update_pydoc.py index 288fed2aa7..44290e2fc6 100644 --- a/gr-utils/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py +++ b/gr-utils/modtool/templates/gr-newmod/docs/doxygen/update_pydoc.py @@ -2,22 +2,23 @@ # Copyright 2010-2012 Free Software Foundation, Inc. # # This file was generated by gr_modtool, a tool from the GNU Radio framework -# This file is a part of gr-howto +# This file is a part of gnuradio # # SPDX-License-Identifier: GPL-3.0-or-later # # """ -Creates the swig_doc.i SWIG interface file. -Execute using: python swig_doc.py xml_path outputfilename +Updates the *pydoc_h files for a module +Execute using: python update_pydoc.py xml_path outputfilename -The file instructs SWIG to transfer the doxygen comments into the +The file instructs Pybind11 to transfer the doxygen comments into the python docstrings. """ from __future__ import unicode_literals -import sys, time +import os, sys, time, glob, re, json +from argparse import ArgumentParser from doxyxml import DoxyIndex, DoxyClass, DoxyFriend, DoxyFunction, DoxyFile from doxyxml import DoxyOther, base @@ -75,6 +76,7 @@ def utoascii(text): return '' out = text.encode('ascii', 'replace') # swig will require us to replace blackslash with 4 backslashes + # TODO: evaluate what this should be for pybind11 out = out.replace(b'\\', b'\\\\\\\\') out = out.replace(b'"', b'\\"').decode('ascii') return str(out) @@ -103,7 +105,7 @@ def format_params(parameteritems): entry_templ = '%feature("docstring") {name} "{docstring}"' def make_entry(obj, name=None, templ="{description}", description=None, params=[]): """ - Create a docstring entry for a swig interface file. + Create a docstring key/value pair, where the key is the object name. obj - a doxyxml object from which documentation will be extracted. name - the name of the C object (defaults to obj.name()) @@ -114,6 +116,8 @@ def make_entry(obj, name=None, templ="{description}", description=None, params=[ """ if name is None: name=obj.name() + if hasattr(obj,'_parse_data') and hasattr(obj._parse_data,'definition'): + name=obj._parse_data.definition.split(' ')[-1] if "operator " in name: return '' if description is None: @@ -122,56 +126,28 @@ def make_entry(obj, name=None, templ="{description}", description=None, params=[ description += '\n\n' description += utoascii(format_params(params)) docstring = templ.format(description=description) - if not docstring: - return '' - return entry_templ.format( - name=name, - docstring=docstring, - ) - -def make_func_entry(func, name=None, description=None, params=None): - """ - Create a function docstring entry for a swig interface file. - - func - a doxyxml object from which documentation will be extracted. - name - the name of the C object (defaults to func.name()) - description - if this optional variable is set then it's value is - used as the description instead of extracting it from func. - params - a parameter list that overrides using func.params. - """ - #if params is None: - # params = func.params - #params = [prm.declname for prm in params] - #if params: - # sig = "Params: (%s)" % ", ".join(params) - #else: - # sig = "Params: (NONE)" - #templ = "{description}\n\n" + sig - #return make_entry(func, name=name, templ=utoascii(templ), - # description=description) - return make_entry(func, name=name, description=description, params=params) + return {name: docstring} def make_class_entry(klass, description=None, ignored_methods=[], params=None): """ - Create a class docstring for a swig interface file. + Create a class docstring key/value pair. """ if params is None: params = klass.params - output = [] - output.append(make_entry(klass, description=description, params=params)) + output = {} + output.update(make_entry(klass, description=description, params=params)) for func in klass.in_category(DoxyFunction): if func.name() not in ignored_methods: name = klass.name() + '::' + func.name() - output.append(make_func_entry(func, name=name)) - return "\n\n".join(output) + output.update(make_entry(func, name=name)) + return output def make_block_entry(di, block): """ - Create class and function docstrings of a gnuradio block for a - swig interface file. + Create class and function docstrings of a gnuradio block """ descriptions = [] # Get the documentation associated with the class. @@ -196,18 +172,16 @@ def make_block_entry(di, block): super_description = "\n\n".join(descriptions) # Associate the combined description with the class and # the make function. - output = [] - output.append(make_class_entry(block, description=super_description)) - output.append(make_func_entry(make_func, description=super_description, + output = {} + output.update(make_class_entry(block, description=super_description)) + output.update(make_entry(make_func, description=super_description, params=block.params)) - return "\n\n".join(output) + return output def make_block2_entry(di, block): """ - Create class and function docstrings of a new style gnuradio block for a - swig interface file. + Create class and function docstrings of a new style gnuradio block """ - descriptions = [] # For new style blocks all the relevant documentation should be # associated with the 'make' method. class_description = combine_descriptions(block) @@ -216,28 +190,21 @@ def make_block2_entry(di, block): description = class_description + "\n\nConstructor Specific Documentation:\n\n" + make_description # Associate the combined description with the class and # the make function. - output = [] - output.append(make_class_entry( + output = {} + output.update(make_class_entry( block, description=description, ignored_methods=['make'], params=make_func.params)) makename = block.name() + '::make' - output.append(make_func_entry( + output.update(make_entry( make_func, name=makename, description=description, params=make_func.params)) - return "\n\n".join(output) + return output -def make_swig_interface_file(di, swigdocfilename, custom_output=None): +def get_docstrings_dict(di, custom_output=None): - output = [""" -/* - * This file was automatically generated using swig_doc.py. - * - * Any changes to it will be lost next time it is regenerated. - */ -"""] - - if custom_output is not None: - output.append(custom_output) + output = {} + if custom_output: + output.update(custom_output) # Create docstrings for the blocks. blocks = di.in_category(Block) @@ -250,7 +217,7 @@ def make_swig_interface_file(di, swigdocfilename, custom_output=None): # Don't want to risk writing to output twice. if make_func.name() not in make_funcs: make_funcs.add(make_func.name()) - output.append(make_block_entry(di, block)) + output.update(make_block_entry(di, block)) except block.ParsingError: sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) raise @@ -262,7 +229,7 @@ def make_swig_interface_file(di, swigdocfilename, custom_output=None): # Don't want to risk writing to output twice. if make_func_name not in make_funcs: make_funcs.add(make_func_name) - output.append(make_block2_entry(di, block)) + output.update(make_block2_entry(di, block)) except block.ParsingError: sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) raise @@ -273,7 +240,7 @@ def make_swig_interface_file(di, swigdocfilename, custom_output=None): if f.name() not in make_funcs and not f.name().startswith('std::')] for f in funcs: try: - output.append(make_func_entry(f)) + output.update(make_entry(f)) except f.ParsingError: sys.stderr.write('Parsing error for function {0}\n'.format(f.name())) @@ -284,37 +251,97 @@ def make_swig_interface_file(di, swigdocfilename, custom_output=None): if k.name() not in block_names and not k.name().startswith('std::')] for k in klasses: try: - output.append(make_class_entry(k)) + output.update(make_class_entry(k)) except k.ParsingError: sys.stderr.write('Parsing error for class {0}\n'.format(k.name())) # Docstrings are not created for anything that is not a function or a class. # If this excludes anything important please add it here. - output = "\n\n".join(output) - - swig_doc = open(swigdocfilename, 'w') - swig_doc.write(output) - swig_doc.close() + return output + +def sub_docstring_in_pydoc_h(pydoc_files, docstrings_dict, output_dir, filter_str=None): + if filter_str: + docstrings_dict = {k: v for k, v in docstrings_dict.items() if k.startswith(filter_str)} + + with open(os.path.join(output_dir,'docstring_status'),'w') as status_file: + + for pydoc_file in pydoc_files: + if filter_str: + filter_str2 = "::".join((filter_str,os.path.split(pydoc_file)[-1].split('_pydoc_template.h')[0])) + docstrings_dict2 = {k: v for k, v in docstrings_dict.items() if k.startswith(filter_str2)} + else: + docstrings_dict2 = docstrings_dict + + + + file_in = open(pydoc_file,'r').read() + for key, value in docstrings_dict2.items(): + file_in_tmp = file_in + try: + doc_key = key.split("::") + # if 'gr' in doc_key: + # doc_key.remove('gr') + doc_key = '_'.join(doc_key) + regexp = r'(__doc_{} =\sR\"doc\()[^)]*(\)doc\")'.format(doc_key) + regexp = re.compile(regexp, re.MULTILINE) + + (file_in, nsubs) = regexp.subn(r'\1'+value+r'\2', file_in, count=1) + if nsubs == 1: + status_file.write("PASS: " + pydoc_file + "\n") + except KeyboardInterrupt: + raise KeyboardInterrupt + except: # be permissive, TODO log, but just leave the docstring blank + status_file.write("FAIL: " + pydoc_file + "\n") + file_in = file_in_tmp + + output_pathname = os.path.join(output_dir, os.path.basename(pydoc_file).replace('_template.h','.h')) + # FIXME: Remove this debug print + print('output docstrings to {}'.format(output_pathname)) + with open(output_pathname,'w') as file_out: + file_out.write(file_in) + +def copy_docstring_templates(pydoc_files, output_dir): + with open(os.path.join(output_dir,'docstring_status'),'w') as status_file: + for pydoc_file in pydoc_files: + file_in = open(pydoc_file,'r').read() + output_pathname = os.path.join(output_dir, os.path.basename(pydoc_file).replace('_template.h','.h')) + # FIXME: Remove this debug print + print('copy docstrings to {}'.format(output_pathname)) + with open(output_pathname,'w') as file_out: + file_out.write(file_in) + status_file.write("DONE") + +def argParse(): + """Parses commandline args.""" + desc='Scrape the doxygen generated xml for docstrings to insert into python bindings' + parser = ArgumentParser(description=desc) + + parser.add_argument("function", help="Operation to perform on docstrings", choices=["scrape","sub","copy"]) + + parser.add_argument("--xml_path") + parser.add_argument("--bindings_dir") + parser.add_argument("--output_dir") + parser.add_argument("--json_path") + parser.add_argument("--filter", default=None) + + return parser.parse_args() if __name__ == "__main__": # Parse command line options and set up doxyxml. - err_msg = "Execute using: python swig_doc.py xml_path outputfilename" - if len(sys.argv) != 3: - raise Exception(err_msg) - xml_path = sys.argv[1] - swigdocfilename = sys.argv[2] - di = DoxyIndex(xml_path) - - # gnuradio.gr.msq_queue.insert_tail and delete_head create errors unless docstrings are defined! - # This is presumably a bug in SWIG. - #msg_q = di.get_member(u'gr_msg_queue', DoxyClass) - #insert_tail = msg_q.get_member(u'insert_tail', DoxyFunction) - #delete_head = msg_q.get_member(u'delete_head', DoxyFunction) - output = [] - #output.append(make_func_entry(insert_tail, name='gr_py_msg_queue__insert_tail')) - #output.append(make_func_entry(delete_head, name='gr_py_msg_queue__delete_head')) - custom_output = "\n\n".join(output) - - # Generate the docstrings interface file. - make_swig_interface_file(di, swigdocfilename, custom_output=custom_output) + args = argParse() + if args.function.lower() == 'scrape': + di = DoxyIndex(args.xml_path) + docstrings_dict = get_docstrings_dict(di) + with open(args.json_path, 'w') as fp: + json.dump(docstrings_dict, fp) + elif args.function.lower() == 'sub': + with open(args.json_path, 'r') as fp: + docstrings_dict = json.load(fp) + pydoc_files = glob.glob(os.path.join(args.bindings_dir,'*_pydoc_template.h')) + sub_docstring_in_pydoc_h(pydoc_files, docstrings_dict, args.output_dir, args.filter) + elif args.function.lower() == 'copy': + pydoc_files = glob.glob(os.path.join(args.bindings_dir,'*_pydoc_template.h')) + copy_docstring_templates(pydoc_files, args.output_dir) + + |