From 2a3a235634ce9fa7015a7ec6807679a361873ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Tue, 25 Apr 2017 10:16:26 +0200 Subject: [PATCH] Add code in wrappers generator for reference handling in docstrings --- tools/abstractapi.py | 38 ++++++++++- tools/genapixml.py | 15 ++--- tools/metadoc.py | 146 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 171 insertions(+), 28 deletions(-) diff --git a/tools/abstractapi.py b/tools/abstractapi.py index eabf276ec..27ff941f3 100644 --- a/tools/abstractapi.py +++ b/tools/abstractapi.py @@ -480,6 +480,18 @@ class CParser(object): else: self.classesIndex[_class.name] = None + self.methodsIndex = {} + for _class in self.cProject.classes: + for funcname in _class.classMethods: + self.methodsIndex[funcname] = None + for funcname in _class.instanceMethods: + self.methodsIndex[funcname] = None + for _property in _class.properties.values(): + if _property.setter is not None: + self.methodsIndex[_property.setter.name] = None + if _property.getter is not None: + self.methodsIndex[_property.getter.name] = None + name = NamespaceName() name.from_snake_case('linphone') @@ -507,9 +519,24 @@ class CParser(object): pass except Error as e: print('Could not parse \'{0}\' class: {1}'.format(_class.name, e.args[0])) - + + + self._clean_all_indexes() self._fix_all_types() + self._fix_all_docs() + def _clean_all_indexes(self): + for index in [self.classesIndex, self.interfacesIndex, self.methodsIndex]: + self._clean_index(index) + + def _clean_index(self, index): + keysToRemove = [] + for key in index.keys(): + if index[key] is None: + keysToRemove.append(key) + + for key in keysToRemove: + del index[key] def _class_is_refcountable(self, _class): if _class.name in self.forcedRefcountableClasses: @@ -576,6 +603,14 @@ class CParser(object): else: raise Error('bctbx_list_t type without specified contained type') + def _fix_all_docs(self): + for _class in self.classesIndex.values(): + if _class.briefDescription is not None: + _class.briefDescription.resolve_all_references(self) + for method in self.methodsIndex.values(): + if method.briefDescription is not None: + method.briefDescription.resolve_all_references(self) + def parse_enum(self, cenum): if 'associatedTypedef' in dir(cenum): nameStr = cenum.associatedTypedef.name @@ -750,6 +785,7 @@ class CParser(object): absArg = Argument(argName, aType) method.add_arguments(absArg) + self.methodsIndex[cfunction.name] = method return method def parse_type(self, cType): diff --git a/tools/genapixml.py b/tools/genapixml.py index d52d92f09..8666c0574 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -246,6 +246,7 @@ class Project: self.__events = [] self.__functions = [] self.classes = [] + self.docparser = metadoc.Parser() def add(self, elem): if isinstance(elem, CClass): @@ -387,7 +388,7 @@ class Project: if deprecatedNode is not None: ev.deprecated = True ev.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - ev.briefDoc = metadoc.Description(node.find('./briefdescription')) + ev.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) ev.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) return ev @@ -399,7 +400,7 @@ class Project: if deprecatedNode is not None: e.deprecated = True e.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - e.briefDoc = metadoc.Description(node.find('./briefdescription')) + e.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) e.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) enumvalues = node.findall("enumvalue[@prot='public']") for enumvalue in enumvalues: @@ -422,7 +423,7 @@ class Project: if deprecatedNode is not None: sm.deprecated = True sm.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - sm.briefDoc = metadoc.Description(node.find('./briefdescription')) + sm.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) sm.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) return sm @@ -432,7 +433,7 @@ class Project: if deprecatedNode is not None: s.deprecated = True s.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - s.briefDoc = metadoc.Description(node.find('./briefdescription')) + s.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) s.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) structmembers = node.findall("sectiondef/memberdef[@kind='variable'][@prot='public']") for structmember in structmembers: @@ -501,7 +502,7 @@ class Project: if deprecatedNode is not None: f.deprecated = True f.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - f.briefDoc = metadoc.Description(node.find('./briefdescription')) + f.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) f.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) return f else: @@ -513,7 +514,7 @@ class Project: if deprecatedNode is not None: td.deprecated = True td.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - td.briefDoc = metadoc.Description(node.find('./briefdescription')) + td.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) td.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) return td return None @@ -573,7 +574,7 @@ class Project: if deprecatedNode is not None: f.deprecated = True f.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() - f.briefDoc = metadoc.Description(node.find('./briefdescription')) + f.briefDoc = self.docparser.parse_description(node.find('./briefdescription')) f.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) if f.briefDescription == '' and ''.join(f.detailedDescription.itertext()).strip() == '': return None diff --git a/tools/metadoc.py b/tools/metadoc.py index eb9786db8..1cf932daf 100644 --- a/tools/metadoc.py +++ b/tools/metadoc.py @@ -16,45 +16,95 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import abstractapi + class Nil: pass class Reference: - def __init__(self, name): - self.cObjectName = name + def __init__(self, cname): + self.cname = cname + self.relatedObject = None + + +class ClassReference(Reference): + def resolve(self, api): + try: + self.relatedObject = api.classesIndex[self.cname] + except KeyError: + print('doc reference pointing on an unknown object ({0})'.format(self.cname)) + + +class FunctionReference(Reference): + def resolve(self, api): + try: + self.relatedObject = api.methodsIndex[self.cname] + except KeyError: + print('doc reference pointing on an unknown object ({0})'.format(self.cname)) class Paragraph: - def __init__(self, node=None): + def __init__(self): self.parts = [] - if node is not None: - self.parse_doxygen_node(node) - def parse_doxygen_node(self, node): - for partNode in node.iter(): - text = partNode.text - if text is not None: - self.parts.append(text) - if partNode is not node: - tail = partNode.tail - if tail is not None: - self.parts.append(tail) + def resolve_all_references(self, api): + for part in self.parts: + if isinstance(part, Reference): + part.resolve(api) class Description: - def __init__(self, node=None): + def __init__(self): self.paragraphs = [] - if node is not None: - self.parse_doxygen_node(node) - def parse_doxygen_node(self, node): + def resolve_all_references(self, api): + for paragraph in self.paragraphs: + paragraph.resolve_all_references(api) + + +class Parser: + def parse_description(self, node): + desc = Description() for paraNode in node.findall('./para'): - self.paragraphs.append(Paragraph(paraNode)) + desc.paragraphs.append(self._parse_paragraph(paraNode)) + return desc + + def _parse_paragraph(self, node): + paragraph = Paragraph() + for partNode in node.iter(): + if partNode is node: + text = partNode.text + if text is not None: + paragraph.parts.append(text) + else: + if partNode.tag == 'ref': + ref = self._parse_reference(partNode) + if ref is not None: + paragraph.parts.append(ref) + else: + text = partNode.text + if text is not None: + paragraph.parts.append(text) + + tail = partNode.tail + if tail is not None: + paragraph.parts.append(tail) + + return paragraph + + def _parse_reference(self, node): + if node.text.endswith('()'): + return FunctionReference(node.text[0:-2]) + else: + return ClassReference(node.text) class Translator: + def __init__(self): + self.textWidth = 80 + def translate(self, description): if description is None: return None @@ -63,21 +113,77 @@ class Translator: for para in description.paragraphs: if para is not description.paragraphs[0]: lines.append('') - lines.append(''.join(para.parts)) + lines.append(self._translate_paragraph(para)) self._tag_as_brief(lines) + lines = self._crop_text(lines, self.textWidth) translatedDoc = {'lines': []} for line in lines: translatedDoc['lines'].append({'line': line}) return translatedDoc + + def _translate_paragraph(self, para): + strPara = '' + for part in para.parts: + if isinstance(part, str): + strPara += part + elif isinstance(part, Reference): + try: + strPara += self._translate_reference(part) + except ReferenceTranslationError as e: + print('could not translate one reference in docstrings ({0})'.format(e.args[0])) + strPara += Translator._translate_reference(self, part) + else: + raise TypeError('untranslatable paragraph element ({0})'.format(part)) + + return strPara + + def _translate_reference(self, ref): + if isinstance(ref, FunctionReference): + return ref.cname + '()' + else: + return ref.cname + + def _crop_text(self, inputLines, width): + outputLines = [] + for line in inputLines: + outputLines += self._split_line(line, width) + return outputLines + + def _split_line(self, line, width): + lines = [] + while len(line) > width: + cutIndex = line.rfind(' ', 0, width) + if cutIndex != -1: + lines.append(line[0:cutIndex]) + line = line[cutIndex+1:] + else: + cutIndex = width + lines.append(line[0:cutIndex]) + line = line[cutIndex:] + + lines.append(line) + return lines + + +class ReferenceTranslationError(RuntimeError): + pass class DoxygenCppTranslator(Translator): def _tag_as_brief(self, lines): if len(lines) > 0: lines[0] = '@brief ' + lines[0] + + def _translate_reference(self, ref): + if isinstance(ref.relatedObject, (abstractapi.Class, abstractapi.Enum)): + return '#' + ref.relatedObject.name.to_c() + elif isinstance(ref.relatedObject, abstractapi.Method): + return ref.relatedObject.name.to_c() + '()' + else: + raise ReferenceTranslationError(ref.cname) class SandcastleCSharpTranslator(Translator):