Example #1
0
	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]
Example #2
0
	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]
Example #3
0
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)
Example #4
0
    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
Example #5
0
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')
Example #6
0
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')
Example #7
0
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
Example #8
0
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