chiark / gitweb /
doxygen: initial support for modules.
authorVladimír Vondruš <mosra@centrum.cz>
Mon, 29 Jan 2018 17:50:12 +0000 (18:50 +0100)
committerVladimír Vondruš <mosra@centrum.cz>
Mon, 29 Jan 2018 17:55:34 +0000 (18:55 +0100)
Module reference, module index, detecting module hierarchy for
breadcrumb and index.

13 files changed:
doc/doxygen.rst
doxygen/dox2html5.py
doxygen/templates/base-reference.html
doxygen/templates/entry-module.html [new file with mode: 0644]
doxygen/templates/group.html [new file with mode: 0644]
doxygen/templates/modules.html [new file with mode: 0644]
doxygen/test/compound_modules/Doxyfile [new file with mode: 0644]
doxygen/test/compound_modules/group__group.html [new file with mode: 0644]
doxygen/test/compound_modules/group__group2.html [new file with mode: 0644]
doxygen/test/compound_modules/group__subgroup.html [new file with mode: 0644]
doxygen/test/compound_modules/input.h [new file with mode: 0644]
doxygen/test/compound_modules/modules.html [new file with mode: 0644]
doxygen/test/test_compound.py

index f52b5f86d9fbdf75619559b4ea63b215a2f503b2..b41e52189b17758ebbfd1fa585dd7e4c7d6ce9bf 100644 (file)
@@ -141,6 +141,8 @@ just open generated ``index.html`` to see the result.
 
 -   Detailed description is put first and foremost on a page, *before* the
     member listing
+-   Files, directories and symbols that don't have any documentation are not
+    present in the output at all.
 -   Table of contents is generated for compound references as well, containing
     all sections of detailed description together with anchors to member
     listings.
@@ -182,7 +184,7 @@ amount of generated content for no added value.
     this unnecessary)
 -   Verbatim listing of parsed headers, "Includes" and "Included By" lists are
     not present (use your IDE or GitHub instead)
--   Undocumented or private members and content of anonymous namespaces are
+-   Undocumented or private members and contents of anonymous namespaces are
     ignored (if things are undocumented or intentionally hidden, why put them
     in the documentation)
 -   Brief description for enum values is ignored (only the detailed description
@@ -331,14 +333,27 @@ CSS files.
 The :ini:`M_LINKS_NAVBAR1` and :ini:`M_LINKS_NAVBAR2` options define which
 links are shown on the top navbar, split into left and right column on small
 screen sizes. These options take a whitespace-separated list of compound IDs
-and additionally the special ``pages``, ``namespaces``,  ``annotated``,
-``files`` IDs. By default the variables are defined like following:
+and additionally the special ``pages``, ``modules``, ``namespaces``,
+``annotated``, ``files`` IDs. By default the variables are defined like
+following:
 
 .. code:: ini
 
     M_LINKS_NAVBAR1 = pages namespaces
     M_LINKS_NAVBAR2 = annotated files
 
+.. note-info::
+
+    The theme by default assumes that the project is grouping symbols in
+    namespaces. If you use modules (``@addtogroup`` and related commands) and
+    you want to show their index in the navbar, add ``modules`` to one of
+    the :ini:`M_LINKS_NAVBAR*` options, for example:
+
+    .. code:: ini
+
+        M_LINKS_NAVBAR1 = pages modules
+        M_LINKS_NAVBAR2 = annotated files
+
 Titles for the links are taken implicitly. Empty :ini:`M_LINKS_NAVBAR2` will
 cause the navigation appear in a single column, setting both empty will cause
 the navbar links to not be rendered at all.
@@ -385,7 +400,8 @@ Options:
     files. Defaults to ``*.xml`` if not set.
 -   ``--index-pages INDEX_PAGES [INDEX_PAGES ...]`` --- index page templates.
     By default, if not set, the index pages are matching stock Doxygen, i.e.
-    ``annotated.html``, ``files.html``, ``namespaces.html`` and ``pages.html``.
+    ``annotated.html``, ``files.html``, ``modules.html``, ``namespaces.html``
+    and ``pages.html``.
     See `Navigation page templates`_ section below for more information.
 -   ``--no-doxygen`` --- don't run Doxygen before. By default Doxygen is run
     before the script to refresh the generated XML output.
@@ -778,6 +794,8 @@ Filename                Use
                         ``*.html``
 ``namespace.html``      Namespace documentation, read fron ``namespace*.xml``
                         and saved as ``namespace*.html``
+``group.html``          Module documentation, read fron ``group_*.xml``
+                        and saved as ``group_*.html``
 ``page.html``           Page, read from ``*.xml``/``indexpage.xml`` and saved
                         as ``*.html``/``index.html``
 ``struct.html``         Struct documentation, read from ``struct*.xml`` and
@@ -832,9 +850,10 @@ Property                                Description
 ======================================= =======================================
 :py:`compound.kind`                     One of :py:`'class'`, :py:`'dir'`,
                                         :py:`'example'`, :py:`'file'`,
-                                        :py:`'namespace'`, :py:`'page'`,
-                                        :py:`'struct'`, :py:`'union'`, used to
-                                        choose a template file from above
+                                        :py:`'group'`, :py:`'namespace'`,
+                                        :py:`'page'`, :py:`'struct'`,
+                                        :py:`'union'`, used to choose a
+                                        template file from above
 :py:`compound.id`                       Unique compound identifier, usually
                                         corresponding to output file name
 :py:`compound.name`                     Compound name
@@ -849,6 +868,9 @@ Property                                Description
                                         `Navigation properties`_ for details.
 :py:`compound.brief`                    Brief description. Can be empty. [1]_
 :py:`compound.description`              Detailed description. Can be empty. [2]_
+:py:`compound.modules`                  List of submodules in this compound.
+                                        Set only for modules. See
+                                        `Module properties`_ for details.
 :py:`compound.dirs`                     List of directories in this compound.
                                         Set only for directories. See
                                         `Directory properties`_ for details.
@@ -963,6 +985,22 @@ of :py:`(url, title)` for a page that's either previous in the defined order,
 one level up or next. For starting/ending page the :py:`prev`/:py:`next` is
 :py:`None`.
 
+`Module properties`_
+````````````````````
+
+The :py:`compound.modules` property contains a list of modules, where every
+item has the following properties:
+
+.. class:: m-table m-fullwidth
+
+=========================== ===================================================
+Property                    Description
+=========================== ===================================================
+:py:`module.url`            URL of the file containing detailed module docs
+:py:`module.name`           Module name (just the leaf)
+:py:`module.brief`          Brief description. Can be empty. [1]_
+=========================== ===================================================
+
 `Directory properties`_
 ```````````````````````
 
@@ -1325,13 +1363,13 @@ Filename                Use
 ======================= =======================================================
 ``annotated.html``      Class listing
 ``files.html``          File and directory listing
+``modules.html``        Module listing
 ``namespaces.html``     Namespace listing
 ``pages.html``          Page listing
 ======================= =======================================================
 
-By default it's those four pages, but you can configure any other pages via
-the ``--index-pages`` option as mentioned in the `Command-line options`_
-section.
+By default it's those five pages, but you can configure any other pages via the
+``--index-pages`` option as mentioned in the `Command-line options`_ section.
 
 Each template is passed a subset of the ``Doxyfile`` configuration values from
 the above table and in addition the :py:`FILENAME` and :py:`DOXYGEN_VERSION`
@@ -1346,6 +1384,7 @@ Property                    Description
 :py:`index.symbols`         List of all namespaces + classes
 :py:`index.files`           List of all dirs + files
 :py:`index.pages`           List of all pages
+:py:`index.modules`         List of all modules
 =========================== ===================================================
 
 The form of each list entry is the same:
@@ -1356,8 +1395,9 @@ The form of each list entry is the same:
 Property                        Description
 =============================== ===============================================
 :py:`i.kind`                    Entry kind (one of :py:`'namespace'`,
-                                :py:`'class'`, :py:`'struct'`, :py:`'union'`,
-                                :py:`'dir'`, :py:`'file'`, :py:`'page'`)
+                                :py:`'group'`, :py:`'class'`, :py:`'struct'`,
+                                :py:`'union'`, :py:`'dir'`, :py:`'file'`,
+                                :py:`'page'`)
 :py:`i.name`                    Name
 :py:`i.url`                     URL of the file with detailed documentation
 :py:`i.brief`                   Brief documentation
index f55676b782d783347576c829569e40096cb3cf2d..87bea9dd075efcaa912d7df2e64e31d53d94bf46 100755 (executable)
@@ -1161,18 +1161,21 @@ def extract_metadata(state: State, xml):
 
     compounddef: ET.Element = root.find('compounddef')
 
-    if compounddef.attrib['kind'] not in ['namespace', 'class', 'struct', 'union', 'dir', 'file', 'page']:
+    if compounddef.attrib['kind'] not in ['namespace', 'group', 'class', 'struct', 'union', 'dir', 'file', 'page']:
         logging.debug("No useful info in {}, skipping".format(os.path.basename(xml)))
         return
 
     compound = Empty()
     compound.id  = compounddef.attrib['id']
     compound.kind = compounddef.attrib['kind']
-    # Compound name is page filename, so we have to use title there
-    compound.name = html.escape(compounddef.find('title').text if compound.kind == 'page' else compounddef.find('compoundname').text)
+    # Compound name is page filename, so we have to use title there. The same
+    # is for groups.
+    compound.name = html.escape(compounddef.find('title').text if compound.kind in ['page', 'group'] else compounddef.find('compoundname').text)
     compound.url = compound.id + '.html'
     compound.brief = parse_desc(state, compounddef.find('briefdescription'))
-    compound.has_details = compound.brief or compounddef.find('detaileddescription')
+    # Groups are explicitly created so they *have details*, other things need
+    # to have at least some documentation
+    compound.has_details = compound.kind == 'group' or compound.brief or compounddef.find('detaileddescription')
     compound.children = []
     compound.parent = None # is filled in by postprocess_state()
 
@@ -1198,6 +1201,9 @@ def extract_metadata(state: State, xml):
     elif compounddef.attrib['kind'] == 'page':
         for i in compounddef.findall('innerpage'):
             compound.children += [i.attrib['refid']]
+    elif compounddef.attrib['kind'] == 'group':
+        for i in compounddef.findall('innergroup'):
+            compound.children += [i.attrib['refid']]
 
     state.compounds[compound.id] = compound
 
@@ -1209,7 +1215,7 @@ def postprocess_state(state: State):
 
     # Strip name of parent symbols from names to get leaf names
     for _, compound in state.compounds.items():
-        if not compound.parent or compound.kind in ['file', 'page']:
+        if not compound.parent or compound.kind in ['file', 'page', 'group']:
             compound.leaf_name = compound.name
             continue
 
@@ -1232,6 +1238,7 @@ def postprocess_state(state: State):
     predefined = {
         'pages': ("Pages", 'pages.html'),
         'namespaces': ("Namespaces", 'namespaces.html'),
+        'modules': ("Modules", 'modules.html'),
         'annotated': ("Classes", 'annotated.html'),
         'files': ("Files", 'files.html')
     }
@@ -1294,14 +1301,16 @@ def parse_xml(state: State, xml: str):
     compound = Empty()
     compound.kind = compounddef.attrib['kind']
     compound.id = compounddef.attrib['id']
-    # Compound name is page filename, so we have to use title there
-    compound.name = compounddef.find('title').text if compound.kind == 'page' else compounddef.find('compoundname').text
+    # Compound name is page filename, so we have to use title there. The same
+    # is for groups.
+    compound.name = compounddef.find('title').text if compound.kind in ['page', 'group'] else compounddef.find('compoundname').text
     compound.has_template_details = False
     compound.templates = None
     compound.brief = parse_desc(state, compounddef.find('briefdescription'))
     compound.description, templates, compound.sections, footer_navigation, example_navigation = parse_toplevel_desc(state, compounddef.find('detaileddescription'))
     compound.example_navigation = None
     compound.footer_navigation = None
+    compound.modules = []
     compound.dirs = []
     compound.files = []
     compound.namespaces = []
@@ -1335,7 +1344,7 @@ def parse_xml(state: State, xml: str):
 
     # Build breadcrumb. Breadcrumb for example pages is built after everything
     # is parsed.
-    if compound.kind in ['namespace', 'struct', 'class', 'union', 'file', 'dir', 'page']:
+    if compound.kind in ['namespace', 'group', 'struct', 'class', 'union', 'file', 'dir', 'page']:
         # Gather parent compounds
         path_reverse = [compound.id]
         while path_reverse[-1] in state.compounds and state.compounds[path_reverse[-1]].parent:
@@ -1487,6 +1496,17 @@ def parse_xml(state: State, xml: str):
 
                     compound.derived_classes += [class_]
 
+        # Module (*not* member group)
+        elif compounddef_child.tag == 'innergroup':
+            assert compound.kind == 'group'
+
+            group = state.compounds[compounddef_child.attrib['refid']]
+            g = Empty()
+            g.url = group.url
+            g.name = group.leaf_name
+            g.brief = group.brief
+            compound.modules += [g]
+
         # Other, grouped in sections
         elif compounddef_child.tag == 'sectiondef':
             if compounddef_child.attrib['kind'] == 'enum':
@@ -1717,7 +1737,7 @@ def parse_xml(state: State, xml: str):
                                             'collaborationgraph',
                                             'listofallmembers',
                                             'tableofcontents'] and
-            not (compounddef.attrib['kind'] == 'page' and compounddef_child.tag == 'title')): # pragma: no cover
+            not (compounddef.attrib['kind'] in ['page', 'group'] and compounddef_child.tag == 'title')): # pragma: no cover
             logging.warning("{}: ignoring <{}> in <compounddef>".format(state.current, compounddef_child.tag))
 
     # Decide about the prefix (it may contain template parameters, so we
@@ -1804,12 +1824,13 @@ def parse_index_xml(state: State, xml):
     assert root.tag == 'doxygenindex'
 
     # Top-level symbols, files and pages. Separated to nestable (namespaces,
-    # dirs) and non-nestable so we have these listed first.
+    # groups, dirs) and non-nestable so we have these listed first.
     top_level_namespaces = []
     top_level_classes = []
     top_level_dirs = []
     top_level_files = []
     top_level_pages = []
+    top_level_modules = []
 
     # Non-top-level symbols, files and pages, assigned later
     orphans_nestable = {}
@@ -1842,6 +1863,8 @@ def parse_index_xml(state: State, xml):
         if not compound.parent:
             if compound.kind == 'namespace':
                 top_level_namespaces += [entry]
+            elif compound.kind == 'group':
+                top_level_modules += [entry]
             elif compound.kind in ['class', 'struct', 'union']:
                 top_level_classes += [entry]
             elif compound.kind == 'dir':
@@ -1856,7 +1879,7 @@ def parse_index_xml(state: State, xml):
 
         # Otherwise put it into orphan map
         else:
-            if compound.kind in ['namespace', 'dir']:
+            if compound.kind in ['namespace', 'group', 'dir']:
                 if not compound.parent in orphans_nestable:
                     orphans_nestable[compound.parent] = []
                 orphans_nestable[compound.parent] += [entry]
@@ -1882,6 +1905,7 @@ def parse_index_xml(state: State, xml):
     parsed.index.symbols = top_level_namespaces + top_level_classes
     parsed.index.files = top_level_dirs + top_level_files
     parsed.index.pages = top_level_pages
+    parsed.index.modules = top_level_modules
 
     # Assign nestable children to their parents first, if the parents exist
     for parent, children in orphans_nestable.items():
@@ -2030,7 +2054,7 @@ def parse_doxyfile(state: State, doxyfile, config = None):
         if i in config:
             state.doxyfile[i] = [line for line in config[i] if line]
 
-default_index_pages = ['pages', 'files', 'namespaces', 'annotated']
+default_index_pages = ['pages', 'files', 'namespaces', 'modules', 'annotated']
 default_wildcard = '*.xml'
 default_templates = 'templates/'
 
index c3efe9503397b27f573d9245ac67e3280e05d75b..811fe5715bdcdd7ac9e6e617942b6aabb2b0b7a3 100644 (file)
@@ -2,6 +2,7 @@
 
 {% set prefix = compound.prefix_wbr %}
 
+{% macro entry_module(module) %}{% include 'entry-module.html' %}{% endmacro %}
 {% macro entry_dir(dir) %}{% include 'entry-dir.html' %}{% endmacro %}
 {% macro entry_file(file) %}{% include 'entry-file.html' %}{% endmacro %}
 {% macro entry_namespace(namespace) %}{% include 'entry-namespace.html' %}{% endmacro %}
@@ -24,7 +25,7 @@
         {% if compound.brief %}
         <p>{{ compound.brief }}</p>
         {% endif %}
-        {% if compound.sections or compound.dirs or compound.files or compound.namespaces or compound.classes or compound.typedefs or compound.funcs or compound.vars or compound.defines or compound.groups %}
+        {% if compound.sections or compound.modules or compound.dirs or compound.files or compound.namespaces or compound.classes or compound.typedefs or compound.funcs or compound.vars or compound.defines or compound.groups %}
         <div class="m-block m-default">
           <h3>Contents</h3>
           <ul>
@@ -43,6 +44,9 @@
             <li>
               Reference
               <ul>
+                {% if compound.modules %}
+                <li><a href="#groups">Modules</a></li>
+                {% endif %}
                 {% if compound.dirs %}
                 <li><a href="#subdirs">Directories</a></li>
                 {% endif %}
 {% if compound.description %}
 {{ compound.description }}
 {% endif %}
+        {% if compound.modules %}
+        <section id="groups">
+          <h2><a href="#groups">Modules</a></h3>
+          <dl class="m-dox">
+            {% for module in compound.modules %}
+{{ entry_module(module) }}
+            {% endfor %}
+          </dl>
+        </section>
+        {% endif %}
         {% if compound.dirs %}
         <section id="subdirs">
           <h2><a href="#subdirs">Directories</a></h3>
diff --git a/doxygen/templates/entry-module.html b/doxygen/templates/entry-module.html
new file mode 100644 (file)
index 0000000..aac45ea
--- /dev/null
@@ -0,0 +1,2 @@
+            <dt>module <a href="{{ module.url }}" class="m-dox">{{ module.name }}</a></dt>
+            <dd>{{ module.brief }}</dd>
diff --git a/doxygen/templates/group.html b/doxygen/templates/group.html
new file mode 100644 (file)
index 0000000..991657f
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends 'base-reference.html' %}
+
+{% block title %}{% set j = joiner(' &raquo; ') %}{% for name, _ in compound.breadcrumb %}{{ j() }}{{ name }}{% endfor %} module | {{ super() }}{% endblock %}
+
+{% block header %}
+        <h1>
+          {% for name, target in compound.breadcrumb[:-1] %}
+          <span class="m-breadcrumb"><a href="{{ target }}">{{ name }}</a> &raquo;</span>
+          {% endfor %}
+          {{ compound.breadcrumb[-1][0] }} <span class="m-thin">module</span></h1>
+{% endblock %}
diff --git a/doxygen/templates/modules.html b/doxygen/templates/modules.html
new file mode 100644 (file)
index 0000000..5cb48e9
--- /dev/null
@@ -0,0 +1,21 @@
+{% set navbar_current = 'modules' %}
+{% extends 'base-index.html' %}
+
+{% block main %}
+        <h1>Modules</h2>
+        <ul class="m-dox">
+          {% for i in index.modules recursive %}
+          {% if i.has_nestable_children %}
+          <li class="m-dox-collapsible">
+            <a href="#" onclick="return toggle(this)">module</a> <a href="{{ i.url }}" class="m-dox">{{ i.name }}</a> <span class="m-dox">{{ i.brief }}</span>
+            <ul class="m-dox">
+{{ loop(i.children)|indent(4, true) }}
+            </ul>
+          </li>
+          {% else %}
+          <li>module <a href="{{ i.url }}" class="m-dox">{{ i.name }}</a> <span class="m-dox">{{ i.brief }}</span></li>
+          {% endif %}
+          {% endfor %}
+        </ul>
+{{ super() -}}
+{% endblock %}
diff --git a/doxygen/test/compound_modules/Doxyfile b/doxygen/test/compound_modules/Doxyfile
new file mode 100644 (file)
index 0000000..0a336c9
--- /dev/null
@@ -0,0 +1,10 @@
+INPUT                   = input.h
+QUIET                   = YES
+GENERATE_HTML           = NO
+GENERATE_LATEX          = NO
+GENERATE_XML            = YES
+
+M_PAGE_FINE_PRINT       =
+M_THEME_COLOR           =
+M_LINKS_NAVBAR1         = modules
+M_LINKS_NAVBAR2         =
diff --git a/doxygen/test/compound_modules/group__group.html b/doxygen/test/compound_modules/group__group.html
new file mode 100644 (file)
index 0000000..62db666
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>A group module | 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-9 m-col-m-none m-left-m">My Project</a>
+      <a id="m-navbar-show" href="#navigation" title="Show navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <a id="m-navbar-hide" href="#" title="Hide navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </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>
+          A group <span class="m-thin">module</span></h1>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#groups">Modules</a></li>
+                <li><a href="#func-members">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+<p>Detailed description.</p>
+        <section id="groups">
+          <h2><a href="#groups">Modules</a></h3>
+          <dl class="m-dox">
+            <dt>module <a href="group__subgroup.html" class="m-dox">A subgroup</a></dt>
+            <dd>Subgroup brief description.</dd>
+          </dl>
+        </section>
+        <section id="func-members">
+          <h2><a href="#func-members">Functions</a></h3>
+          <dl class="m-dox">
+            <dt>
+              <span class="m-dox-wrap-bumper">void <a href="#gac07863d69ae41a4e395b31f73b35fbcd" class="m-dox-self" name="gac07863d69ae41a4e395b31f73b35fbcd">foo</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>A foo.</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/compound_modules/group__group2.html b/doxygen/test/compound_modules/group__group2.html
new file mode 100644 (file)
index 0000000..6b5c124
--- /dev/null
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Another group module | 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-9 m-col-m-none m-left-m">My Project</a>
+      <a id="m-navbar-show" href="#navigation" title="Show navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <a id="m-navbar-hide" href="#" title="Hide navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </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>
+          Another group <span class="m-thin">module</span></h1>
+        <p>Brief description.</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#func-members">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="func-members">
+          <h2><a href="#func-members">Functions</a></h3>
+          <dl class="m-dox">
+            <dt>
+              <span class="m-dox-wrap-bumper">void <a href="#ga49a4b11e50430aa0a78de989ea99e082" class="m-dox-self" name="ga49a4b11e50430aa0a78de989ea99e082">bar</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>A bar.</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/compound_modules/group__subgroup.html b/doxygen/test/compound_modules/group__subgroup.html
new file mode 100644 (file)
index 0000000..73269df
--- /dev/null
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>A group &raquo; A subgroup module | 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-9 m-col-m-none m-left-m">My Project</a>
+      <a id="m-navbar-show" href="#navigation" title="Show navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <a id="m-navbar-hide" href="#" title="Hide navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </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>
+          <span class="m-breadcrumb"><a href="group__group.html">A group</a> &raquo;</span>
+          A subgroup <span class="m-thin">module</span></h1>
+        <p>Subgroup brief description.</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#func-members">Functions</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="func-members">
+          <h2><a href="#func-members">Functions</a></h3>
+          <dl class="m-dox">
+            <dt>
+              <span class="m-dox-wrap-bumper">auto <a href="#gac0124d4b60010824c0b73bd5f27759c6" class="m-dox">fizzbuzz</a>(</span><span class="m-dox-wrap">) -&gt; int</span>
+            </dt>
+            <dd>Returns 5 every 5 runs or something.</dd>
+          </dl>
+        </section>
+        <section>
+          <h2>Function documentation</h2>
+          <section class="m-dox-details" id="gac0124d4b60010824c0b73bd5f27759c6"><div>
+            <h3>
+              <span class="m-dox-wrap-bumper">int </span><span class="m-dox-wrap"><span class="m-dox-wrap-bumper"><a href="#gac0124d4b60010824c0b73bd5f27759c6" class="m-dox-self">fizzbuzz</a>(</span><span class="m-dox-wrap">)</span></span>
+            </h3>
+            <p>Returns 5 every 5 runs or something.</p>
+<p>Did I pass the interview now?</p>
+          </div></section>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/compound_modules/input.h b/doxygen/test/compound_modules/input.h
new file mode 100644 (file)
index 0000000..753fe3a
--- /dev/null
@@ -0,0 +1,33 @@
+/** @defgroup group A group
+
+Detailed description.
+@{ */
+
+/** @brief A foo */
+void foo();
+
+/*@}*/
+
+/** @defgroup group2 Another group
+@brief Brief description
+@{ */
+
+/** @brief A bar */
+void bar();
+
+/*@}*/
+
+/**
+@defgroup subgroup A subgroup
+@brief Subgroup brief description
+@ingroup group
+@{ */
+
+/**
+@brief Returns 5 every 5 runs or something
+
+Did I pass the interview now?
+*/
+int fizzbuzz();
+
+/*@}*/
diff --git a/doxygen/test/compound_modules/modules.html b/doxygen/test/compound_modules/modules.html
new file mode 100644 (file)
index 0000000..3a6ea8e
--- /dev/null
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>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-9 m-col-m-none m-left-m">My Project</a>
+      <a id="m-navbar-show" href="#navigation" title="Show navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <a id="m-navbar-hide" href="#" title="Hide navigation" class="m-col-t-3 m-hide-m m-text-right"></a>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html" id="m-navbar-current">Modules</a></li>
+          </ol>
+        </div>
+      </div>
+    </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>Modules</h2>
+        <ul class="m-dox">
+          <li class="m-dox-collapsible">
+            <a href="#" onclick="return toggle(this)">module</a> <a href="group__group.html" class="m-dox">A group</a> <span class="m-dox"></span>
+            <ul class="m-dox">
+              <li>module <a href="group__subgroup.html" class="m-dox">A subgroup</a> <span class="m-dox">Subgroup brief description.</span></li>
+            </ul>
+          </li>
+          <li>module <a href="group__group2.html" class="m-dox">Another group</a> <span class="m-dox">Brief description.</span></li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-dox-collapsible' ?
+                'm-dox-expansible' : 'm-dox-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of directly in
+           markup so disabling it doesn't harm usability. The list is somehow
+           regenerated on every iteration and shrinks as I change the classes. It's not
+           documented anywhere and I'm not sure if this is the same across browsers, so
+           I am going backwards in that list to be sure.  */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-dox-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index 7d5ae41b78a25db6e0fd29b5c8848df85930d13e..6e22791b934daa8a9025eda79064a83dbcf4a318 100644 (file)
@@ -138,3 +138,14 @@ class Warnings(IntegrationTestCase):
         # Should warn that an export macro is present in the XML
         self.run_dox2html5(wildcard='namespaceMagnum.xml')
         self.assertEqual(*self.actual_expected_contents('namespaceMagnum.html'))
+
+class Modules(IntegrationTestCase):
+    def __init__(self, *args, **kwargs):
+        super().__init__(__file__, 'modules', *args, **kwargs)
+
+    def test(self):
+        self.run_dox2html5(wildcard='*.xml')
+        self.assertEqual(*self.actual_expected_contents('group__group.html'))
+        self.assertEqual(*self.actual_expected_contents('group__group2.html'))
+        self.assertEqual(*self.actual_expected_contents('group__subgroup.html'))
+        self.assertEqual(*self.actual_expected_contents('modules.html'))