- Clickable symbols in code snippets. Doxygen has quite a lot of false
positives while a lot of symbols stay unmatched. I need to find a way
around that.
+- Documented friend classes, structs and unions. Doxygen is unable to
+ cross-link the declarations with the definitions.
+- Proper scoping for friend and related functions/classes/variables etc.
+ Doxygen doesn't provide any namespace scoping for these and at the moment
+ I have no way to deduct that information.
`Configuration`_
================
:py:`compound.private_funcs` List of documented private virtual
functions. Set only for classes. See
`Function properties`_ for details.
+:py:`compound.friend_funcs` List of documented friend functions.
+ Set only for classes. See
+ `Function properties`_ for details.
:py:`compound.related` List of related non-member symbols. Set
only for classes. See
`Related properties`_ for details.
The :py:`commpound.funcs`, :py:`compound.public_static_funcs`,
:py:`compound.public_funcs`, :py:`compound.protected_static_funcs`,
-:py:`compound.protected_funcs`, :py:`compound.private_funcs` and
-:py:`compound.related_funcs` properties contain a list of functions, where
-every item has the following properties:
+:py:`compound.protected_funcs`, :py:`compound.private_funcs`,
+:py:`compound.friend_funcs` and :py:`compound.related_funcs` properties contain
+a list of functions, where every item has the following properties:
.. class:: m-table m-fullwidth
return None
def parse_func(state: State, element: ET.Element):
- assert element.tag == 'memberdef' and element.attrib['kind'] == 'function'
+ assert element.tag == 'memberdef' and element.attrib['kind'] in ['function', 'friend']
func = Empty()
state.current_definition_url_base, func.base_url, func.id = parse_id(element)
func.brief = parse_desc(state, element.find('briefdescription'))
func.description, templates, params, func.return_value, func.return_values, func.exceptions, search_keywords, func.is_deprecated = parse_func_desc(state, element)
+ # Friend functions have friend as type. That's just awful. COME ON.
+ if func.type.startswith('friend '):
+ func.type = func.type[7:]
+
# Extract function signature to prefix, suffix and various flags. Important
# things affecting caller such as static or const (and rvalue overloads)
# are put into signature prefix/suffix, other things to various is_*
func.is_pure_virtual = False
func.suffix = html.escape(signature[signature.rindex(')') + 1:].strip())
if func.suffix: func.suffix = ' ' + func.suffix
- func.is_protected = element.attrib['prot'] == 'protected'
- func.is_private = element.attrib['prot'] == 'private'
+ # Protected / private makes no sense for friend functions
+ if element.attrib['kind'] != 'friend':
+ func.is_protected = element.attrib['prot'] == 'protected'
+ func.is_private = element.attrib['prot'] == 'private'
func.has_template_details, func.templates = parse_template_params(state, element.find('templateparamlist'), templates)
compound.protected_vars = []
compound.private_funcs = []
compound.related = []
+ compound.friend_funcs = []
compound.groups = []
compound.has_enum_details = False
compound.has_typedef_details = False
else: # pragma: no cover
logging.warning("{}: unknown related <memberdef> kind {}".format(state.current, memberdef.attrib['kind']))
+ elif compounddef_child.attrib['kind'] == 'friend':
+ for memberdef in compounddef_child:
+ # Ignore friend classes. This does not ignore friend
+ # classes written as `friend Foo;`, those are parsed as
+ # variables (ugh).
+ if memberdef.find('type').text in ['friend class', 'friend struct', 'friend union']:
+ # Print a warning in case these are documented
+ if (''.join(memberdef.find('briefdescription').itertext()).strip() or ''.join(memberdef.find('detaileddescription').itertext()).strip()):
+ logging.warning("{}: doxygen is unable to cross-link {}, ignoring, sorry".format(state.current, memberdef.find('definition').text))
+ # Only friend functions left, hopefully, parse as a func
+ else:
+ func = parse_func(state, memberdef)
+ if func:
+ compound.friend_funcs += [func]
+ if func.has_details: compound.has_func_details = True
+
elif compounddef_child.attrib['kind'] == 'user-defined':
list = []
if define:
list += [('define', define)]
if define.has_details: compound.has_define_details = True
+ elif memberdef.attrib['kind'] == 'friend':
+ # Ignore friend classes. This does not ignore friend
+ # classes written as `friend Foo;`, those are parsed as
+ # variables (ugh).
+ if memberdef.find('type').text in ['friend class', 'friend struct', 'friend union'] and (memberdef.find('briefdescription').text or memberdef.find('detaileddescription').text):
+ logging.warning("{}: doxygen is unable to cross-link {}, ignoring, sorry".format(state.current, memberdef.find('definition').text))
+ # Only friend functions left, hopefully, parse as a func
+ else:
+ func = parse_func(state, memberdef)
+ if func:
+ list += [('func', func)]
+ if func.has_details: compound.has_func_details = True
else: # pragma: no cover
logging.warning("{}: unknown user-defined <memberdef> kind {}".format(state.current, memberdef.attrib['kind']))
</tbody>
</table>
{% endif %}
- {% if compound.sections or compound.public_types or compound.public_static_funcs or compound.typeless_funcs or compound.public_funcs or compound.public_static_vars or compound.public_vars or compound.protected_types or compound.protected_static_funcs or compound.protected_funcs or compound.protected_static_vars or compound.protected_vars or compound.private_funcs or compound.groups or compound.relate %}
+ {% if compound.sections or compound.public_types or compound.public_static_funcs or compound.typeless_funcs or compound.public_funcs or compound.public_static_vars or compound.public_vars or compound.protected_types or compound.protected_static_funcs or compound.protected_funcs or compound.protected_static_vars or compound.protected_vars or compound.private_funcs or compound.groups or compound.friend_funcs or compound.related %}
<div class="m-block m-default">
<h3>Contents</h3>
<ul>
{% for group in compound.groups %}
<li><a href="#{{ group.id }}">{{ group.name }}</a></li>
{% endfor %}
+ {% if compound.friend_funcs %}
+ <li><a href="#friends">Friends</a></li>
+ {% endif %}
{% if compound.related %}
<li><a href="#related">Related</a></li>
{% endif %}
</dl>
</section>
{% endfor %}
+ {% if compound.friend_funcs %}
+ <section id="friends">
+ <h2><a href="#friends">Friends</a></h2>
+ <dl class="m-dox">
+ {% for func in compound.friend_funcs %}
+{{ entry_func(func) }}
+ {% endfor %}
+ </dl>
+ </section>
+ {% endif %}
{% if compound.related %}
<section id="related">
<h2><a href="#related">Related</a></h2>
{% endif %}
{% endfor %}
{% endfor %}
+ {% for func in compound.friend_funcs %}
+ {% if func.has_details %}
+{{ details_func(func, '') }}
+ {% endif %}
+ {% endfor %}
{% for kind, member in compound.related %}
{% if kind == 'func' and member.has_details %}
{{ details_func(member, '') }}
--- /dev/null
+INPUT = File.h
+AUTOLINK_SUPPORT = NO
+QUIET = YES
+GENERATE_HTML = NO
+GENERATE_LATEX = NO
+GENERATE_XML = YES
+XML_PROGRAMLISTING = NO
+
+##! M_PAGE_FINE_PRINT =
+##! M_THEME_COLOR =
+##! M_FAVICON =
+##! M_LINKS_NAVBAR1 =
+##! M_LINKS_NAVBAR2 =
+##! M_SEARCH_DISABLED = YES
--- /dev/null
+/** @file
+ * @brief A file
+ */
+
+/**
+@brief A friend class
+
+Not displayed among @ref Class friends becase Doxygen doesn't provide any
+useful info for it.
+*/
+class FriendClassWarning {};
+
+/**
+@brief A friend class
+
+Not displayed among @ref Class friends becase Doxygen doesn't provide any
+useful info for it.
+*/
+class GroupedFriendClassWarning {};
+
+/** @brief A class */
+class Class {
+ public:
+ /* Ignored */
+ friend class FriendClass;
+ friend struct FriendStruct;
+ friend union FriendUnion;
+
+ /** @brief Ignored friend class with a warning because it has docs */
+ friend class FriendClassWarning;
+
+ /** @brief A friend function */
+ friend void friendFunction(int a, void* b);
+
+ /** @{ @name Group with friend functions */
+
+ /** @brief Ignored friend class with a warning because it has docs */
+ friend class GroupedFriendClassWarning;
+
+ /** @brief A friend grouped function */
+ friend void friendGroupedFunction();
+
+ /*@}*/
+};
+
+/** @brief Class with template parameters */
+template<class T, class U = void, class = int> class Template {
+ protected: /* Shouldn't matter */
+ /**
+ * @brief Friend function
+ * @tparam V This is a V
+ *
+ * This is broken in doxygen itself as it doesn't include any scope
+ * from the class.
+ */
+ template<class V> friend void foobar();
+};
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>Class class | My Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ Class <span class="m-thin">class</span>
+ </h1>
+ <p>A class.</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#group-with-friend-functions">Group with friend functions</a></li>
+ <li><a href="#friends">Friends</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="group-with-friend-functions">
+ <h2><a href="#group-with-friend-functions">Group with friend functions</a></h2>
+ <dl class="m-dox">
+ <dt>
+ <span class="m-dox-wrap-bumper">void <a href="#ab0183d8a208fd1bc6842dcb37da48eff" class="m-dox-self" name="ab0183d8a208fd1bc6842dcb37da48eff">friendGroupedFunction</a>(</span><span class="m-dox-wrap">)</span>
+ </dt>
+ <dd>A friend grouped function.</dd>
+ </dl>
+ </section>
+ <section id="friends">
+ <h2><a href="#friends">Friends</a></h2>
+ <dl class="m-dox">
+ <dt>
+ <span class="m-dox-wrap-bumper">void <a href="#a70135398344d1e7003e0877018f73c4b" class="m-dox-self" name="a70135398344d1e7003e0877018f73c4b">friendFunction</a>(</span><span class="m-dox-wrap">int a,
+ void* b)</span>
+ </dt>
+ <dd>A friend function.</dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>Template class | My Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+doxygen.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <div class="m-dox-template">template<class T, class U = void, class = int></div>
+ Template <span class="m-thin">class</span>
+ </h1>
+ <p>Class with template parameters.</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#friends">Friends</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="friends">
+ <h2><a href="#friends">Friends</a></h2>
+ <dl class="m-dox">
+ <dt>
+ <div class="m-dox-template">template<class V></div>
+ <span class="m-dox-wrap-bumper">void <a href="#abd44b6f85f143be1e8b81e5ee25ef62c" class="m-dox">foobar</a>(</span><span class="m-dox-wrap">)</span>
+ </dt>
+ <dd>Friend function.</dd>
+ </dl>
+ </section>
+ <section>
+ <h2>Function documentation</h2>
+ <section class="m-dox-details" id="abd44b6f85f143be1e8b81e5ee25ef62c"><div>
+ <h3>
+ <div class="m-dox-template">
+ template<class T, class U, class _3>
+ template<class V>
+ </div>
+ <span class="m-dox-wrap-bumper">void </span><span class="m-dox-wrap"><span class="m-dox-wrap-bumper"><a href="#abd44b6f85f143be1e8b81e5ee25ef62c" class="m-dox-self">foobar</a>(</span><span class="m-dox-wrap">)</span></span>
+ </h3>
+ <p>Friend function.</p>
+ <table class="m-table m-fullwidth m-flat">
+ <thead>
+ <tr><th colspan="2">Template parameters</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="width: 1%">V</td>
+ <td>This is a V</td>
+ </tr>
+ </tbody>
+ </table>
+<p>This is broken in doxygen itself as it doesn't include any scope from the class.</p>
+ </div></section>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
self.assertEqual(*self.actual_expected_contents('classNamespace_1_1VirtualBase.html'))
self.assertEqual(*self.actual_expected_contents('classBaseOutsideANamespace.html'))
self.assertEqual(*self.actual_expected_contents('classDerivedOutsideANamespace.html'))
+
+class Friends(IntegrationTestCase):
+ def __init__(self, *args, **kwargs):
+ super().__init__(__file__, 'friends', *args, **kwargs)
+
+ @unittest.skipUnless(LooseVersion(doxygen_version()) > LooseVersion("1.8.13"),
+ "1.8.13 produces invalid XML for friend declarations")
+ def test(self):
+ self.run_dox2html5(wildcard='class*.xml')
+ self.assertEqual(*self.actual_expected_contents('classClass.html'))
+ self.assertEqual(*self.actual_expected_contents('classTemplate.html'))