From d5c8023f6bed181e78d1f0f21f575f60ac18707d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 29 Jan 2018 18:50:12 +0100 Subject: [PATCH] doxygen: initial support for modules. Module reference, module index, detecting module hierarchy for breadcrumb and index. --- doc/doxygen.rst | 64 +++++++++++++---- doxygen/dox2html5.py | 48 +++++++++---- doxygen/templates/base-reference.html | 16 ++++- doxygen/templates/entry-module.html | 2 + doxygen/templates/group.html | 11 +++ doxygen/templates/modules.html | 21 ++++++ doxygen/test/compound_modules/Doxyfile | 10 +++ .../test/compound_modules/group__group.html | 67 ++++++++++++++++++ .../test/compound_modules/group__group2.html | 59 ++++++++++++++++ .../compound_modules/group__subgroup.html | 70 +++++++++++++++++++ doxygen/test/compound_modules/input.h | 33 +++++++++ doxygen/test/compound_modules/modules.html | 61 ++++++++++++++++ doxygen/test/test_compound.py | 11 +++ 13 files changed, 448 insertions(+), 25 deletions(-) create mode 100644 doxygen/templates/entry-module.html create mode 100644 doxygen/templates/group.html create mode 100644 doxygen/templates/modules.html create mode 100644 doxygen/test/compound_modules/Doxyfile create mode 100644 doxygen/test/compound_modules/group__group.html create mode 100644 doxygen/test/compound_modules/group__group2.html create mode 100644 doxygen/test/compound_modules/group__subgroup.html create mode 100644 doxygen/test/compound_modules/input.h create mode 100644 doxygen/test/compound_modules/modules.html diff --git a/doc/doxygen.rst b/doc/doxygen.rst index f52b5f86..b41e5218 100644 --- a/doc/doxygen.rst +++ b/doc/doxygen.rst @@ -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 diff --git a/doxygen/dox2html5.py b/doxygen/dox2html5.py index f55676b7..87bea9dd 100755 --- a/doxygen/dox2html5.py +++ b/doxygen/dox2html5.py @@ -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 ".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/' diff --git a/doxygen/templates/base-reference.html b/doxygen/templates/base-reference.html index c3efe950..811fe571 100644 --- a/doxygen/templates/base-reference.html +++ b/doxygen/templates/base-reference.html @@ -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 %}

{{ compound.brief }}

{% 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 %}

Contents

    @@ -43,6 +44,9 @@
  • Reference
      + {% if compound.modules %} +
    • Modules
    • + {% endif %} {% if compound.dirs %}
    • Directories
    • {% endif %} @@ -81,6 +85,16 @@ {% if compound.description %} {{ compound.description }} {% endif %} + {% if compound.modules %} +
      +

      Modules

      +
      + {% for module in compound.modules %} +{{ entry_module(module) }} + {% endfor %} +
      +
      + {% endif %} {% if compound.dirs %}

      Directories

      diff --git a/doxygen/templates/entry-module.html b/doxygen/templates/entry-module.html new file mode 100644 index 00000000..aac45ea8 --- /dev/null +++ b/doxygen/templates/entry-module.html @@ -0,0 +1,2 @@ +
      module {{ module.name }}
      +
      {{ module.brief }}
      diff --git a/doxygen/templates/group.html b/doxygen/templates/group.html new file mode 100644 index 00000000..991657fb --- /dev/null +++ b/doxygen/templates/group.html @@ -0,0 +1,11 @@ +{% extends 'base-reference.html' %} + +{% block title %}{% set j = joiner(' » ') %}{% for name, _ in compound.breadcrumb %}{{ j() }}{{ name }}{% endfor %} module | {{ super() }}{% endblock %} + +{% block header %} +

      + {% for name, target in compound.breadcrumb[:-1] %} + {{ name }} » + {% endfor %} + {{ compound.breadcrumb[-1][0] }} module

      +{% endblock %} diff --git a/doxygen/templates/modules.html b/doxygen/templates/modules.html new file mode 100644 index 00000000..5cb48e95 --- /dev/null +++ b/doxygen/templates/modules.html @@ -0,0 +1,21 @@ +{% set navbar_current = 'modules' %} +{% extends 'base-index.html' %} + +{% block main %} +

      Modules

      +
        + {% for i in index.modules recursive %} + {% if i.has_nestable_children %} +
      • + module {{ i.name }} {{ i.brief }} +
          +{{ loop(i.children)|indent(4, true) }} +
        +
      • + {% else %} +
      • module {{ i.name }} {{ i.brief }}
      • + {% endif %} + {% endfor %} +
      +{{ super() -}} +{% endblock %} diff --git a/doxygen/test/compound_modules/Doxyfile b/doxygen/test/compound_modules/Doxyfile new file mode 100644 index 00000000..0a336c90 --- /dev/null +++ b/doxygen/test/compound_modules/Doxyfile @@ -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 index 00000000..62db666a --- /dev/null +++ b/doxygen/test/compound_modules/group__group.html @@ -0,0 +1,67 @@ + + + + + A group module | My Project + + + + + +
      +
      +
      +
      +
      +

      + A group module

      +
      +

      Contents

      + +
      +

      Detailed description.

      +
      +

      Modules

      +
      +
      module A subgroup
      +
      Subgroup brief description.
      +
      +
      +
      +

      Functions

      +
      +
      + void foo() +
      +
      A foo.
      +
      +
      +
      +
      +
      +
      + + diff --git a/doxygen/test/compound_modules/group__group2.html b/doxygen/test/compound_modules/group__group2.html new file mode 100644 index 00000000..6b5c124e --- /dev/null +++ b/doxygen/test/compound_modules/group__group2.html @@ -0,0 +1,59 @@ + + + + + Another group module | My Project + + + + + +
      +
      +
      +
      +
      +

      + Another group module

      +

      Brief description.

      +
      +

      Contents

      + +
      +
      +

      Functions

      +
      +
      + void bar() +
      +
      A bar.
      +
      +
      +
      +
      +
      +
      + + diff --git a/doxygen/test/compound_modules/group__subgroup.html b/doxygen/test/compound_modules/group__subgroup.html new file mode 100644 index 00000000..73269df6 --- /dev/null +++ b/doxygen/test/compound_modules/group__subgroup.html @@ -0,0 +1,70 @@ + + + + + A group » A subgroup module | My Project + + + + + +
      +
      +
      +
      +
      +

      + A group » + A subgroup module

      +

      Subgroup brief description.

      +
      +

      Contents

      + +
      +
      +

      Functions

      +
      +
      + auto fizzbuzz() -> int +
      +
      Returns 5 every 5 runs or something.
      +
      +
      +
      +

      Function documentation

      +
      +

      + int fizzbuzz() +

      +

      Returns 5 every 5 runs or something.

      +

      Did I pass the interview now?

      +
      +
      +
      +
      +
      +
      + + diff --git a/doxygen/test/compound_modules/input.h b/doxygen/test/compound_modules/input.h new file mode 100644 index 00000000..753fe3a0 --- /dev/null +++ b/doxygen/test/compound_modules/input.h @@ -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 index 00000000..3a6ea8eb --- /dev/null +++ b/doxygen/test/compound_modules/modules.html @@ -0,0 +1,61 @@ + + + + + My Project + + + + + +
      +
      +
      +
      +
      +

      Modules

      + + +
      +
      +
      +
      + + diff --git a/doxygen/test/test_compound.py b/doxygen/test/test_compound.py index 7d5ae41b..6e22791b 100644 --- a/doxygen/test/test_compound.py +++ b/doxygen/test/test_compound.py @@ -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')) -- 2.30.2