"m_span{1}=@xmlonly<mcss:span xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" mcss:class=\"\1\">@endxmlonly" \
"m_endspan=@xmlonly</mcss:span>@endxmlonly" \
"m_class{1}=@xmlonly<mcss:class xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" mcss:class=\"\1\" />@endxmlonly" \
- "m_footernavigation=@xmlonly<mcss:footernavigation xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" />@endxmlonly"
+ "m_footernavigation=@xmlonly<mcss:footernavigation xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" />@endxmlonly" \
+ "m_examplenavigation{2}=@xmlonly<mcss:examplenavigation xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" mcss:page=\"\1\" mcss:prefix=\"\2\" />@endxmlonly"
If you need backwards compatibility with stock Doxygen HTML output, just make
the aliases empty in your original ``Doxyfile``. Note that you can rename the
"m_span{1}=" \
"m_endspan=" \
"m_class{1}=" \
- "m_footernavigation="
+ "m_footernavigation=" \
+ "m_examplenavigation{2}"
With ``@m_div`` and ``@m_span`` it's possible to wrap individual paragraphs or
inline text in :html:`<div>` / :html:`<span>` and add CSS classes to them.
page to contain a link to previous, parent and next page according to defined
page order.
+The ``@m_examplenavigation`` command is able to put breadcrumb navigation to
+parent page(s) of ``@example`` listings in order to make it easier for users to
+return back from example source code to a tutorial page, for example. When used
+in combination with ``@m_footernavigation``, navigation to parent page and to
+prev/next file of the same example is put at the bottom of the page. The
+``@m_examplenavigation`` command takes two arguments, first is the parent page
+for this example (used to build the breadcrumb and footer navigation), second
+is example path prefix (which is then stripped from page title and is also used
+to discover which example files belong together). Example usage --- the
+``@m_examplenavigation`` and ``@m_footernavigation`` commands are simply
+appended the an existing ``@example`` command.
+
+.. code-figure::
+
+ .. code:: c++
+
+ /**
+ @example helloworld/CMakeLists.txt @m_examplenavigation{example,helloworld/} @m_footernavigation
+ @example helloworld/configure.h.cmake @m_examplenavigation{example,helloworld/} @m_footernavigation
+ @example helloworld/main.cpp @m_examplenavigation{example,helloworld/} @m_footernavigation
+ */
+
`Customizing the template`_
===========================
def __init__(self):
self.basedir = ''
self.compounds: Dict[str, Any] = {}
+ self.examples: List[Any] = []
self.doxyfile: Dict[str, str] = {}
self.images: List[str] = []
self.current = ''
out.return_value = None
out.add_css_class = None
out.footer_navigation = False
+ out.example_navigation = None
# DOXYGEN <PARA> PATCHING 1/4
#
# resetting here explicitly.
add_css_class = parsed.add_css_class
- # Bubble up also the footer navigation
+ # Bubble up also footer / example navigation
if parsed.footer_navigation: out.footer_navigation = True
+ if parsed.example_navigation: out.example_navigation = parsed.example_navigation
# Assert we didn't miss anything important
assert not parsed.section
elif i.tag == '{http://mcss.mosra.cz/doxygen/}footernavigation':
out.footer_navigation = True
+ # Enabling navigation for an example
+ elif i.tag == '{http://mcss.mosra.cz/doxygen/}examplenavigation':
+ out.example_navigation = (i.attrib['{http://mcss.mosra.cz/doxygen/}page'],
+ i.attrib['{http://mcss.mosra.cz/doxygen/}prefix'])
+
# Either block or inline
elif i.tag == 'programlisting':
assert element.tag == 'para' # is inside a paragraph :/
assert not parsed.return_value
if parsed.params:
logging.warning("{}: use @tparam instead of @param for documenting class templates, @param is ignored".format(state.current))
- return (parsed.parsed, parsed.templates, parsed.section[2] if parsed.section else '', parsed.footer_navigation)
+ return (parsed.parsed, parsed.templates, parsed.section[2] if parsed.section else '', parsed.footer_navigation, parsed.example_navigation)
def parse_typedef_desc(state: State, element: ET.Element):
# Verify that we didn't ignore any important info by accident
return define if define.brief or define.has_details else None
def extract_metadata(state: State, xml):
- # index.xml will be parsed later in parse_index_xml()
- if os.path.basename(xml) == 'index.xml': return
-
logging.debug("Extracting metadata from {}".format(os.path.basename(xml)))
tree = ET.parse(xml)
root = tree.getroot()
+ # We need just list of all example files in correct order, nothing else
+ if os.path.basename(xml) == 'index.xml':
+ for i in root:
+ if i.attrib['kind'] == 'example':
+ compound = Empty()
+ compound.id = i.attrib['refid']
+ compound.url = compound.id + '.html'
+ compound.name = i.find('name').text
+ state.examples += [compound]
+ return
+
compounddef: ET.Element = root.find('compounddef')
+
if compounddef.attrib['kind'] not in ['namespace', 'class', 'struct', 'union', 'dir', 'file', 'page']:
logging.debug("No useful info in {}, skipping".format(os.path.basename(xml)))
return
compound.has_template_details = False
compound.templates = None
compound.brief = parse_desc(state, compounddef.find('briefdescription'))
- compound.description, templates, compound.sections, footer_navigation = parse_toplevel_desc(state, compounddef.find('detaileddescription'))
+ 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.dirs = []
compound.files = []
compound.has_var_details = False
compound.has_define_details = False
- # Build breadcrumb
+ # Build breadcrumb. Breadcrumb for example pages is built after everything
+ # is parsed.
if compound.kind in ['namespace', 'struct', 'class', 'union', 'file', 'dir', 'page']:
# Gather parent compounds
path_reverse = [compound.id]
if footer_navigation:
up = state.compounds[compound.id].parent
- # Go through all parent children and
+ # Go through all parent children and find previous and next
if up:
up = state.compounds[up]
compound.prefix_wbr += '::<wbr />'
+ # Example pages
+ if compound.kind == 'example':
+ # Build breadcrumb navigation
+ if example_navigation:
+ if not compound.name.startswith(example_navigation[1]):
+ logging.critical("{}: example filename is not prefixed with {}".format(state.current, example_navigation[1]))
+ assert False
+
+ prefix_length = len(example_navigation[1])
+
+ path_reverse = [example_navigation[0]]
+ while path_reverse[-1] in state.compounds and state.compounds[path_reverse[-1]].parent:
+ path_reverse += [state.compounds[path_reverse[-1]].parent]
+
+ # Fill breadcrumb with leaf names and URLs
+ compound.breadcrumb = []
+ for i in reversed(path_reverse):
+ compound.breadcrumb += [(state.compounds[i].leaf_name, state.compounds[i].url)]
+
+ # Add example filename as leaf item
+ compound.breadcrumb += [(compound.name[prefix_length:], compound.id + '.html')]
+
+ # Enable footer navigation, if requested
+ if footer_navigation:
+ up = state.compounds[example_navigation[0]]
+
+ prev = None
+ next = None
+ prev_child = None
+ for example in state.examples:
+ if example.id == compound.id:
+ if prev_child: prev = prev_child
+ elif prev_child and prev_child.id == compound.id:
+ if example.name.startswith(example_navigation[1]):
+ next = example
+ break
+
+ if example.name.startswith(example_navigation[1]):
+ prev_child = example
+
+ compound.footer_navigation = ((prev.url, prev.name[prefix_length:]) if prev else None,
+ (up.url, up.name),
+ (next.url, next.name[prefix_length:]) if next else None)
+
+ else:
+ compound.breadcrumb = [(compound.name, compound.id + '.html')]
+
parsed = Empty()
parsed.version = root.attrib['version']
{% extends 'base.html' %}
-{% block title %}{{ compound.name }} example | {{ super() }}{% endblock %}
+{% block title %}{% set j = joiner(' » ') %}{% for name, _ in compound.breadcrumb %}{{ j() }}{{ name }}{% endfor %} source | {{ super() }}{% endblock %}
+
+{% block header_links %}
+{% if compound.footer_navigation and compound.footer_navigation[0] %}
+ <link rel="prev" href="{{ compound.footer_navigation[0][0] }}" />
+{% endif %}
+{% if compound.footer_navigation and compound.footer_navigation[2] %}
+ <link rel="next" href="{{ compound.footer_navigation[2][0] }}" />
+{% endif %}
+{% endblock %}
{% block main %}
- <h1>{{ compound.name }} <span class="m-thin">example</span></h1>
- {% if compound.brief %}
- <p>{{ compound.brief }}</p>
- {% endif %}
-{% if compound.description %}
+ <h1>
+ {% for name, target in compound.breadcrumb[:-1] %}
+ <span class="m-breadcrumb"><a href="{{ target }}">{{ name }}</a> »</span>
+ {% endfor %}
+ {{ compound.breadcrumb[-1][0] }} <span class="m-thin">source</span>
+ </h1>
+ {% if compound.brief %}
+ <p>{{ compound.brief }}</p>
+ {% endif %}
+ {% if compound.description %}
{{ compound.description }}
-{% endif %}
+ {% endif %}
+ {% if compound.footer_navigation %}
+ <div class="m-note m-dim m-thin m-text-center">{% if compound.footer_navigation[0] %}<a href="{{ compound.footer_navigation[0][0] }}" class="m-dox">« {{ compound.footer_navigation[0][1] }}</a> | {% endif %}<a href="{{ compound.footer_navigation[1][0] }}" class="m-dox">{{ compound.footer_navigation[1][1] }}</a>{% if compound.footer_navigation[2] %} | <a href="{{ compound.footer_navigation[2][0] }}" class="m-dox">{{ compound.footer_navigation[2][1] }} »</a>{% endif %}</div>
+ {% endif %}
{% endblock %}
--- /dev/null
+INPUT = input.dox
+QUIET = YES
+GENERATE_HTML = NO
+GENERATE_LATEX = NO
+GENERATE_XML = YES
+EXAMPLE_PATH = .
+ALIASES = \
+ "m_footernavigation=@xmlonly<mcss:footernavigation xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" />@endxmlonly" \
+ "m_examplenavigation{2}=@xmlonly<mcss:examplenavigation xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" mcss:page=\"\1\" mcss:prefix=\"\2\" />@endxmlonly"
+
+M_PAGE_FINE_PRINT =
+M_THEME_COLOR =
+M_LINKS_NAVBAR1 =
+M_LINKS_NAVBAR2 =
--- /dev/null
+/** @page page A page
+
+Go here: @subpage example
+*/
+
+/** @page example The Example
+
+See here:
+
+- @ref path-prefix/CMakeLists.txt
+- @ref path-prefix/configure.h.cmake
+- @ref path-prefix/main.cpp
+
+@example path-prefix/CMakeLists.txt @m_examplenavigation{example,path-prefix/} @m_footernavigation
+@example path-prefix/configure.h.cmake @m_examplenavigation{example,path-prefix/} @m_footernavigation
+@example path-prefix/main.cpp @m_examplenavigation{example,path-prefix/} @m_footernavigation
+*/
--- /dev/null
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/configure.h)
+
+add_executable(app main.cpp)
+target_include_directories(app ${CMAKE_CURRENT_BINARY_DIR})
--- /dev/null
+#cmakedefine SAY_HELLO
--- /dev/null
+#include <iostream>
+
+#include "configure.h"
+
+int main() {
+ #ifdef SAY_HELLO
+ std::cout << "hello?" << std::endl;
+ #endif
+}
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>A page » The Example » CMakeLists.txt source | 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" />
+ <link rel="next" href="path-prefix_2configure_8h_8cmake-example.html" />
+ <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>
+ </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="page.html">A page</a> »</span>
+ <span class="m-breadcrumb"><a href="example.html">The Example</a> »</span>
+ CMakeLists.txt <span class="m-thin">source</span>
+ </h1>
+<pre class="m-code"><span class="nb">configure_file</span><span class="p">(</span><span class="o">${</span><span class="nv">CMAKE_CURRENT_SOURCE_DIR</span><span class="o">}</span><span class="s">/configure.h.cmake</span>
+ <span class="o">${</span><span class="nv">CMAKE_CURRENT_BINARY_DIR</span><span class="o">}</span><span class="s">/configure.h</span><span class="p">)</span>
+
+<span class="nb">add_executable</span><span class="p">(</span><span class="s">app</span> <span class="s">main.cpp</span><span class="p">)</span>
+<span class="nb">target_include_directories</span><span class="p">(</span><span class="s">app</span> <span class="o">${</span><span class="nv">CMAKE_CURRENT_BINARY_DIR</span><span class="o">}</span><span class="p">)</span></pre>
+ <div class="m-note m-dim m-thin m-text-center"><a href="example.html" class="m-dox">The Example</a> | <a href="path-prefix_2configure_8h_8cmake-example.html" class="m-dox">configure.h.cmake »</a></div>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>A page » The Example » configure.h.cmake source | 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" />
+ <link rel="prev" href="path-prefix_2CMakeLists_8txt-example.html" />
+ <link rel="next" href="path-prefix_2main_8cpp-example.html" />
+ <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>
+ </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="page.html">A page</a> »</span>
+ <span class="m-breadcrumb"><a href="example.html">The Example</a> »</span>
+ configure.h.cmake <span class="m-thin">source</span>
+ </h1>
+<pre class="m-code"><span class="cp">#cmakedefine SAY_HELLO</span></pre>
+ <div class="m-note m-dim m-thin m-text-center"><a href="path-prefix_2CMakeLists_8txt-example.html" class="m-dox">« CMakeLists.txt</a> | <a href="example.html" class="m-dox">The Example</a> | <a href="path-prefix_2main_8cpp-example.html" class="m-dox">main.cpp »</a></div>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>A page » The Example » main.cpp source | 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" />
+ <link rel="prev" href="path-prefix_2configure_8h_8cmake-example.html" />
+ <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>
+ </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="page.html">A page</a> »</span>
+ <span class="m-breadcrumb"><a href="example.html">The Example</a> »</span>
+ main.cpp <span class="m-thin">source</span>
+ </h1>
+<pre class="m-code"><span class="cp">#include</span> <span class="cpf"><iostream></span><span class="cp"></span>
+
+<span class="cp">#include</span> <span class="cpf">"configure.h"</span><span class="cp"></span>
+
+<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
+ <span class="cp">#ifdef SAY_HELLO</span>
+ <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="s">"hello?"</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
+ <span class="cp">#endif</span>
+<span class="p">}</span></pre>
+ <div class="m-note m-dim m-thin m-text-center"><a href="path-prefix_2configure_8h_8cmake-example.html" class="m-dox">« configure.h.cmake</a> | <a href="example.html" class="m-dox">The Example</a></div>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+#
+# This file is part of m.css.
+#
+# Copyright © 2017, 2018 Vladimír Vondruš <mosra@centrum.cz>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+
+import unittest
+
+from distutils.version import LooseVersion
+
+from test import IntegrationTestCase, doxygen_version
+
+class Example(IntegrationTestCase):
+ def __init__(self, *args, **kwargs):
+ super().__init__(__file__, '', *args, **kwargs)
+
+ def test(self):
+ self.run_dox2html5(index_pages=[], wildcard='*.xml')
+ self.assertEqual(*self.actual_expected_contents('path-prefix_2CMakeLists_8txt-example.html'))
+ self.assertEqual(*self.actual_expected_contents('path-prefix_2configure_8h_8cmake-example.html'))
+ self.assertEqual(*self.actual_expected_contents('path-prefix_2main_8cpp-example.html'))