def find_doxygen_link(name, rawtext, text, lineno, inliner, options={}, content=[]): text = utils.unescape(text) # from :name:`title <part>` has_explicit_title, title, part = split_explicit_title(text) warning_messages = [] if tag_file: # ?? url = find_url(tag_file, part) try: url = find_url2(app.env.doxylink_cache[cache_name]['mapping'], part) except LookupError as error: warning_messages.append('Error while parsing `%s`. Is not a well-formed C++ function call or symbol. If this is not the case, it is a doxylink bug so please report it. Error reported was: %s' % (part, error)) if url: #If it's an absolute path then the link will work regardless of the document directory #Also check if it is a URL (i.e. it has a 'scheme' like 'http' or 'file') if os.path.isabs(rootdir) or urlparse.urlparse(rootdir).scheme: full_url = join(rootdir, url['file']) #But otherwise we need to add the relative path of the current document to the root source directory to the link else: relative_path_to_docsrc = os.path.relpath(app.env.srcdir, os.path.dirname(inliner.document.current_source)) full_url = join(relative_path_to_docsrc, '/', rootdir, url['file']) #We always use the '/' here rather than os.sep since this is a web link avoids problems like documentation/.\../library/doc/ (mixed slashes) if url['kind'] == 'function' and app.config.add_function_parentheses and not normalise(title)[1]: title = join(title, '()') pnode = nodes.reference(title, title, internal=False, refuri=full_url) return [pnode], [] #By here, no match was found warning_messages.append('Could not find match for `%s` in `%s` tag file' % (part, tag_filename)) else: warning_messages.append('Could not find match for `%s` because tag file not found' % (part)) pnode = nodes.inline(rawsource=title, text=title) return [pnode], [inliner.reporter.warning(message, line=lineno) for message in warning_messages]
def find_doxygen_link(name, rawtext, text, lineno, inliner, options={}, content=[]): text = utils.unescape(text) # from :name:`title <part>` has_explicit_title, title, part = split_explicit_title(text) warning_messages = [] if tag_file: url = find_url(tag_file, part) try: url = find_url2(app.env.doxylink_cache[cache_name]['mapping'], part) except LookupError as error: warning_messages.append('Error while parsing `%s`. Is not a well-formed C++ function call or symbol. If this is not the case, it is a doxylink bug so please report it. Error reported was: %s' % (part, error)) if url: #If it's an absolute path then the link will work regardless of the document directory #Also check if it is a URL (i.e. it has a 'scheme' like 'http' or 'file') if os.path.isabs(rootdir) or urlparse.urlparse(rootdir).scheme: full_url = join(rootdir, url['file']) #But otherwise we need to add the relative path of the current document to the root source directory to the link else: relative_path_to_docsrc = os.path.relpath(app.env.srcdir, os.path.dirname(inliner.document.current_source)) full_url = join(relative_path_to_docsrc, '/', rootdir, url['file']) #We always use the '/' here rather than os.sep since this is a web link avoids problems like documentation/.\../library/doc/ (mixed slashes) if url['kind'] == 'function' and app.config.add_function_parentheses and not normalise(title)[1]: title = join(title, '()') pnode = nodes.reference(title, title, internal=False, refuri=full_url) return [pnode], [] #By here, no match was found warning_messages.append('Could not find match for `%s` in `%s` tag file' % (part, tag_filename)) else: warning_messages.append('Could not find match for `%s` because tag file not found' % (part)) pnode = nodes.inline(rawsource=title, text=title) return [pnode], [inliner.reporter.warning(message, line=lineno) for message in warning_messages]
def find_url2(mapping, symbol): """ Return the URL for a given symbol. This is where the magic happens. .. todo:: Maybe print a list of all possible matches as a warning (but still only return the first) :Parameters: mapping : dictionary A dictionary of the form returned by :py:func:`parse_tag_file` symbol : string The symbol to lookup in the file. E.g. something like 'PolyVox::Array' or 'tidyUpMemory' :return: String representing the filename part of the URL :raises: LookupError Raised if the symbol could not be matched in the file """ #print "\n\nSearching for", symbol try: symbol, normalised_arglist = normalise(symbol) except ParseException, error: raise LookupError(error)
def __getitem__(self, item: str) -> Entry: symbol, normalised_arglist = normalise(item) matched_symbol = self._get_symbol_match(symbol) entry = self._mapping[matched_symbol] if isinstance(entry, FunctionList): entry = entry[normalised_arglist] return entry
def find_url2(mapping, symbol): """ Return the URL for a given symbol. This is where the magic happens. .. todo:: Maybe print a list of all possible matches as a warning (but still only return the first) :Parameters: mapping : dictionary A dictionary of the form returned by :py:func:`parse_tag_file` symbol : string The symbol to lookup in the file. E.g. something like 'PolyVox::Array' or 'tidyUpMemory' :return: String representing the filename part of the URL :raises: LookupError Raised if the symbol could not be matched in the file """ #print "\n\nSearching for", symbol try: symbol, normalised_arglist = normalise(symbol) except ParseException as error: raise LookupError(error) #print symbol, normalised_arglist #If we have an exact match then return it. if mapping.get(symbol): #print ('Exact match') return return_from_mapping(mapping[symbol], normalised_arglist) #If the user didn't pass in any arguments, i.e. `arguments == ''` then they don't care which version of the overloaded funtion they get. #First we check for any mapping entries which even slightly match the requested symbol #endswith_list = {} #for item, data in mapping.items(): # if item.endswith(symbol): #print symbol + ' : ' + item # endswith_list[item] = data # mapping[item]['file'] #If we only find one then we return it. #if len(endswith_list) is 1: # return endswith_list.values()[0]['file'] #print("Still", len(endswith_list), 'possible matches') piecewise_list = find_url_piecewise(mapping, symbol) #If there is only one match, return it. if len(piecewise_list) is 1: return return_from_mapping(piecewise_list.values()[0], normalised_arglist) #print("Still", len(piecewise_list), 'possible matches') #If there is more than one item in piecewise_list then there is an ambiguity #Often this is due to the symbol matching the name of the constructor as well as the class name itself classes_list = find_url_classes(piecewise_list, symbol) #If there is only one by here we return it. if len(classes_list) is 1: return classes_list.values()[0] #print("Still", len(classes_list), 'possible matches') #If we exhaused the list by requiring classes, use the list from before the filter. if len(classes_list) == 0: classes_list = piecewise_list no_templates_list = find_url_remove_templates(classes_list, symbol) if len(no_templates_list) is 1: return return_from_mapping(no_templates_list.values()[0], normalised_arglist) #print("Still", len(no_templates_list), 'possible matches') #If not found by now, just return the first one in the list if len(no_templates_list) != 0: #TODO return a warning here? return return_from_mapping(no_templates_list.values()[0], normalised_arglist) #Else return None if the list is empty else: LookupError('Could not find a match')
def find_url2(mapping, symbol): """ Return the URL for a given symbol. This is where the magic happens. .. todo:: Maybe print a list of all possible matches as a warning (but still only return the first) :Parameters: mapping : dictionary A dictionary of the form returned by :py:func:`parse_tag_file` symbol : string The symbol to lookup in the file. E.g. something like 'PolyVox::Array' or 'tidyUpMemory' :return: String representing the filename part of the URL :raises: LookupError Raised if the symbol could not be matched in the file """ #print "\n\nSearching for", symbol try: symbol, normalised_arglist = normalise(symbol) except ParseException as error: raise LookupError(error) #print symbol, normalised_arglist #If we have an exact match then return it. if mapping.get(symbol): #print ('Exact match') return return_from_mapping(mapping[symbol], normalised_arglist) #If the user didn't pass in any arguments, i.e. `arguments == ''` then they don't care which version of the overloaded funtion they get. #First we check for any mapping entries which even slightly match the requested symbol #endswith_list = {} #for item, data in mapping.items(): # if item.endswith(symbol): #print symbol + ' : ' + item # endswith_list[item] = data # mapping[item]['file'] #If we only find one then we return it. #if len(endswith_list) is 1: # return endswith_list.values()[0]['file'] #print("Still", len(endswith_list), 'possible matches') piecewise_list = find_url_piecewise(mapping, symbol) #If there is only one match, return it. if len(piecewise_list) is 1: return return_from_mapping(list(piecewise_list.values())[0], normalised_arglist) #print("Still", len(piecewise_list), 'possible matches') #If there is more than one item in piecewise_list then there is an ambiguity #Often this is due to the symbol matching the name of the constructor as well as the class name itself classes_list = find_url_classes(piecewise_list, symbol) #If there is only one by here we return it. if len(classes_list) is 1: return list(classes_list.values())[0] #print("Still", len(classes_list), 'possible matches') #If we exhaused the list by requiring classes, use the list from before the filter. if len(classes_list) == 0: classes_list = piecewise_list no_templates_list = find_url_remove_templates(classes_list, symbol) if len(no_templates_list) is 1: return return_from_mapping(list(no_templates_list.values())[0], normalised_arglist) #print("Still", len(no_templates_list), 'possible matches') #If not found by now, just return the first one in the list if len(no_templates_list) != 0: #TODO return a warning here? return return_from_mapping(list(no_templates_list.values())[0], normalised_arglist) #Else return None if the list is empty else: LookupError('Could not find a match')
def parse_tag_file(doc: ET.ElementTree) -> dict: """ Takes in an XML tree from a Doxygen tag file and returns a dictionary that looks something like: .. code-block:: python {'PolyVox': Entry(...), 'PolyVox::Array': Entry(...), 'PolyVox::Array1DDouble': Entry(...), 'PolyVox::Array1DFloat': Entry(...), 'PolyVox::Array1DInt16': Entry(...), 'QScriptContext::throwError': FunctionList(...), 'QScriptContext::toString': FunctionList(...) } Note the different form for functions. This is required to allow for 'overloading by argument type'. :Parameters: doc : xml.etree.ElementTree The XML DOM object :return: a dictionary mapping fully qualified symbols to files """ mapping = {} # type: MutableMapping[str, Union[Entry, FunctionList]] function_list = [ ] # This is a list of function to be parsed and inserted into mapping at the end of the function. for compound in doc.findall('./compound'): compound_kind = compound.get('kind') if compound_kind not in { 'namespace', 'class', 'struct', 'file', 'define', 'group' }: continue compound_name = compound.findtext('name') compound_filename = compound.findtext('filename') # TODO The following is a hack bug fix I think # Doxygen doesn't seem to include the file extension to <compound kind="file"><filename> entries # If it's a 'file' type, check if it _does_ have an extension, if not append '.html' if compound_kind == 'file' and not os.path.splitext( compound_filename)[1]: compound_filename = compound_filename + '.html' # If it's a compound we can simply add it mapping[compound_name] = Entry(kind=compound_kind, file=compound_filename) for member in compound.findall('member'): # If the member doesn't have an <anchorfile> element, use the parent compounds <filename> instead # This is the way it is in the qt.tag and is perhaps an artefact of old Doxygen anchorfile = member.findtext('anchorfile') or compound_filename member_symbol = compound_name + '::' + member.findtext('name') member_kind = member.get('kind') arglist_text = member.findtext( './arglist' ) # If it has an <arglist> then we assume it's a function. Empty <arglist> returns '', not None. Things like typedefs and enums can have empty arglists if arglist_text and member_kind not in { 'variable', 'typedef', 'enumeration' }: function_list.append((member_symbol, arglist_text, member_kind, join(anchorfile, '#', member.findtext('anchor')))) else: mapping[member_symbol] = Entry(kind=member.get('kind'), file=join( anchorfile, '#', member.findtext('anchor'))) for member_symbol, arglist, kind, anchor_link in function_list: try: normalised_arglist = normalise(member_symbol + arglist)[1] except ParseException as e: print('Skipping %s %s%s. Error reported from parser was: %s' % (kind, member_symbol, arglist, e)) else: if mapping.get(member_symbol) and isinstance( mapping[member_symbol], FunctionList): mapping[member_symbol].add_overload(normalised_arglist, anchor_link) else: mapping[member_symbol] = FunctionList() mapping[member_symbol].add_overload(normalised_arglist, anchor_link) return mapping
def parse_tag_file(doc): """ Takes in an XML tree from a Doxygen tag file and returns a dictionary that looks something like: .. code-block:: python {'PolyVox': {'file': 'namespace_poly_vox.html', 'kind': 'namespace'}, 'PolyVox::Array': {'file': 'class_poly_vox_1_1_array.html', 'kind': 'class'}, 'PolyVox::Array1DDouble': {'file': 'namespace_poly_vox.html#a7a1f5fd5c4f7fbb4258a495d707b5c13', 'kind': 'typedef'}, 'PolyVox::Array1DFloat': {'file': 'namespace_poly_vox.html#a879a120e49733eba1905c33f8a7f131b', 'kind': 'typedef'}, 'PolyVox::Array1DInt16': {'file': 'namespace_poly_vox.html#aa1463ece448c6ebed55ab429d6ae3e43', 'kind': 'typedef'}, 'QScriptContext::throwError': {'arglist': {'( Error error, const QString & text )': 'qscriptcontext.html#throwError', '( const QString & text )': 'qscriptcontext.html#throwError-2'}, 'kind': 'function'}, 'QScriptContext::toString': {'arglist': {'()': 'qscriptcontext.html#toString'}, 'kind': 'function'}} Note the different form for functions. This is required to allow for 'overloading by argument type'. To access a filename for a symbol you do: .. code-block:: python symbol_mapping = mapping[symbol] if symbol_mapping['kind'] == 'function': url = symbol_mapping['arglist'][argument_string] else: url = symbol_mapping['file'] :Parameters: doc : xml.etree.ElementTree The XML DOM object :return: a dictionary mapping fully qualified symbols to files """ mapping = {} function_list = [ ] # This is a list of function to be parsed and inserted into mapping at the end of the function. for compound in doc.findall('./compound'): compound_kind = compound.get('kind') if compound_kind not in { 'namespace', 'class', 'struct', 'file', 'group' }: continue # Skip everything that isn't a namespace, class, struct or file compound_name = compound.findtext('name') compound_filename = compound.findtext('filename') # TODO The following is a hack bug fix I think # Doxygen doesn't seem to include the file extension to <compound kind="file"><filename> entries # If it's a 'file' type, check if it _does_ have an extension, if not append '.html' if compound_kind == 'file' and not os.path.splitext( compound_filename)[1]: compound_filename = compound_filename + '.html' # If it's a compound we can simply add it mapping[compound_name] = { 'kind': compound_kind, 'file': compound_filename } for member in compound.findall('member'): # If the member doesn't have an <anchorfile> element, use the parent compounds <filename> instead # This is the way it is in the qt.tag and is perhaps an artefact of old Doxygen anchorfile = member.findtext('anchorfile') or compound_filename member_symbol = compound_name + '::' + member.findtext('name') member_kind = member.get('kind') arglist_text = member.findtext( './arglist' ) # If it has an <arglist> then we assume it's a function. Empty <arglist> returns '', not None. Things like typedefs and enums can have empty arglists if arglist_text and member_kind not in { 'variable', 'typedef', 'enumeration' }: function_list.append((member_symbol, arglist_text, member_kind, join(anchorfile, '#', member.findtext('anchor')))) else: mapping[member_symbol] = { 'kind': member.get('kind'), 'file': join(anchorfile, '#', member.findtext('anchor')) } for f in function_list: member_symbol = f[0] kind = f[2] anchor_link = f[3] try: normalised_tuple = normalise(f[0] + f[1]) except ParseException as e: print('Skipping %s %s%s. Error reported from parser was: %s' % (f[2], f[0], f[1], e)) else: normalised_arglist = normalised_tuple[1] if mapping.get(member_symbol ) and mapping[member_symbol]['kind'] == 'function': mapping[member_symbol]['arglist'][ normalised_arglist] = anchor_link else: mapping[member_symbol] = { 'kind': kind, 'arglist': { normalised_arglist: anchor_link } } return mapping