chiark / gitweb /
doxygen: support rendering namespace members in file scope.
authorVladimír Vondruš <mosra@centrum.cz>
Sat, 10 Feb 2018 16:11:46 +0000 (17:11 +0100)
committerVladimír Vondruš <mosra@centrum.cz>
Sat, 10 Feb 2018 16:32:49 +0000 (17:32 +0100)
12 files changed:
doc/doxygen.rst
doxygen/dox2html5.py
doxygen/templates/entry-enum.html
doxygen/templates/entry-func.html
doxygen/templates/entry-typedef.html
doxygen/templates/entry-var.html
doxygen/test/compound_namespace_members_in_file_scope/Doxyfile [new file with mode: 0644]
doxygen/test/compound_namespace_members_in_file_scope/File.h [new file with mode: 0644]
doxygen/test/compound_namespace_members_in_file_scope/File_8h.html [new file with mode: 0644]
doxygen/test/compound_namespace_members_in_file_scope/namespaceNamespace.html [new file with mode: 0644]
doxygen/test/search/Doxyfile
doxygen/test/test_compound.py

index 105a7502e3202a658cdc8b3af5c6d5b03a7873a9..4cd7a096164ec63d8ad00796ef77fde5da64aaf3 100644 (file)
@@ -69,6 +69,10 @@ with the stock output to avoid broken links once you switch.
     It may work reasonably well with older versions, but I can't guarantee
     that. Upgrade to the latest version to have the best experience.
 
+    Some features depend on patches that are not yet integrated in Doxygen, in
+    that case the documentation mentions which revision to use or which patch
+    you need to apply.
+
 Everything you need apart from Doxygen itself is a Python script and a bunch of
 template files. You can get that by cloning :gh:`the m.css GitHub repository <mosra/m.css$master/doxygen>`
 and looking into the ``doxygen/`` directory:
@@ -504,6 +508,23 @@ the ``@section`` command instead.
 Table of contents for pages is generated only if they specify
 ``@tableofcontents`` in their documentation block.
 
+`Namespace members in file scope`_
+----------------------------------
+
+Doxygen by default doesn't render namespace members for file documentation in
+its XML output. To match the behavior of stock HTML output, enable the
+:ini:`XML_NAMESPACE_MEMBERS_IN_FILE_SCOPE` option:
+
+.. code:: ini
+
+    # Requires a patch to Doxygen 1.8.14, see below
+    XML_NAMESPACE_MEMBERS_IN_FILE_SCOPE = YES
+
+.. note-warning:: Doxygen patches
+
+    In order to use the :ini:`XML_NAMESPACE_MEMBERS_IN_FILE_SCOPE` option, you
+    need Doxygen with :gh:`doxygen/doxygen#653` applied.
+
 `Code highlighting`_
 --------------------
 
@@ -1190,6 +1211,8 @@ has the following properties:
 =============================== ===============================================
 Property                        Description
 =============================== ===============================================
+:py:`enum.base_url`             Base URL of file containing detailed
+                                description [3]_
 :py:`enum.id`                   Identifier hash [3]_
 :py:`enum.type`                 Enum type or empty if implicitly typed [6]_
 :py:`enum.is_strong`            If the enum is strong
@@ -1212,6 +1235,8 @@ Every item of :py:`enum.values` has the following properties:
 =========================== ===================================================
 Property                    Description
 =========================== ===================================================
+:py:`value.base_url`        Base URL of file containing detailed description
+                            [3]_
 :py:`value.id`              Identifier hash [3]_
 :py:`value.name`            Value name [4]_
 :py:`value.initializer`     Value initializer. Can be empty. [1]_
@@ -1230,6 +1255,8 @@ item has the following properties:
 =========================== ===================================================
 Property                    Description
 =========================== ===================================================
+:py:`typedef.base_url`      Base URL of file containing detailed description
+                            [3]_
 :py:`typedef.id`            Identifier hash [3]_
 :py:`typedef.is_using`      Whether it is a :cpp:`typedef` or an :cpp:`using`
 :py:`typedef.type`          Typedef type, or what all goes before the name for
@@ -1263,6 +1290,8 @@ every item has the following properties:
 =============================== ===============================================
 Property                        Description
 =============================== ===============================================
+:py:`func.base_url`             Base URL of file containing detailed
+                                description [3]_
 :py:`func.id`                   Identifier hash [3]_
 :py:`func.type`                 Function return type [6]_
 :py:`func.name`                 Function name [4]_
@@ -1345,6 +1374,8 @@ every item has the following properties:
 =========================== ===================================================
 Property                    Description
 =========================== ===================================================
+:py:`var.base_url`          Base URL of file containing detailed description
+                            [3]_
 :py:`var.id`                Identifier hash [3]_
 :py:`var.type`              Variable type [6]_
 :py:`var.name`              Variable name [4]_
@@ -1521,15 +1552,19 @@ all directories are before all files.
 .. [2] :py:`i.description` is HTML code with the full description, containing
     paragraphs, notes, code blocks, images etc. Can be empty in case just the
     brief description is present.
-.. [3] :py:`i.id` is a hash used to link to the member on the page, usually
-    appearing after ``#`` in page URL
+.. [3] :py:`i.base_url`, joined using ``#`` with :py:`i.id` form a unique URL
+    for given symbol. If the :py:`i.base_url` is not the same as
+    :py:`compound.url`, it means given symbol is just referenced from given
+    compound and its detailed documentation resides elsewhere.
 .. [4] :py:`i.name` is just the member name, not qualified. Prepend
     :py:`compound.prefix_wbr` to it to get the fully qualified name.
 .. [5] :py:`compound.has_*_details` and :py:`i.has_details` are :py:`True` if
     there is detailed description, function/template/macro parameter
     documentation or enum value listing that makes it worth to render the full
     description block. If :py:`False`, the member should be included only in
-    the brief listing on top of the page to avoid unnecessary repetition.
+    the brief listing on top of the page to avoid unnecessary repetition. If
+    :py:`i.base_url` is not the same as :py:`compound.url`, its
+    :py:`i.has_details` is always set to :py:`False`.
 .. [6] :py:`i.type` and :py:`param.default` is rendered as HTML and usually
     contains links to related documentation
 .. [7] :py:`i.is_deprecated` is set to :py:`True` if detailed docs of given
index 5096a7a50452f58baefb13a041e7407ad685d074..e2f58b77a4848b4bc9b5e646e245815375f837a8 100755 (executable)
@@ -1314,7 +1314,7 @@ def parse_enum(state: State, element: ET.Element):
     assert element.tag == 'memberdef' and element.attrib['kind'] == 'enum'
 
     enum = Empty()
-    enum.id = extract_id_hash(state, element)
+    enum.base_url, enum.id = parse_id(state, element)
     enum.type = parse_type(state, element.find('type'))
     enum.name = element.find('name').text
     if enum.name.startswith('@'): enum.name = '(anonymous)'
@@ -1330,7 +1330,9 @@ def parse_enum(state: State, element: ET.Element):
     enumvalue: ET.Element
     for enumvalue in element.findall('enumvalue'):
         value = Empty()
-        value.id = extract_id_hash(state, enumvalue)
+        # The base_url might be different, but should be the same as enum.base_url
+        value_base_url, value.id = parse_id(state, enumvalue)
+        assert value_base_url == enum.base_url
         value.name = enumvalue.find('name').text
         # There can be an implicit initializer for enum value
         value.initializer = html.escape(enumvalue.findtext('initializer', ''))
@@ -1339,10 +1341,10 @@ def parse_enum(state: State, element: ET.Element):
         value.description, value_search_keywords, value.is_deprecated = parse_enum_value_desc(state, enumvalue)
         if value.description:
             enum.has_value_details = True
-            if not state.doxyfile['M_SEARCH_DISABLED']:
+            if enum.base_url == state.current_url and not state.doxyfile['M_SEARCH_DISABLED']:
                 result = Empty()
                 result.flags = ResultFlag.ENUM_VALUE|(ResultFlag.DEPRECATED if value.is_deprecated else ResultFlag(0))
-                result.url = state.current_url + '#' + value.id
+                result.url = enum.base_url + '#' + value.id
                 result.prefix = state.current_prefix + [enum.name]
                 result.name = value.name
                 result.keywords = value_search_keywords
@@ -1351,12 +1353,12 @@ def parse_enum(state: State, element: ET.Element):
                 state.search += [result]
         enum.values += [value]
 
-    enum.has_details = enum.description or enum.has_value_details
+    enum.has_details = enum.base_url == state.current_url and (enum.description or enum.has_value_details)
     if enum.brief or enum.has_details or enum.has_value_details:
-        if not state.doxyfile['M_SEARCH_DISABLED']:
+        if enum.base_url == state.current_url and not state.doxyfile['M_SEARCH_DISABLED']:
             result = Empty()
             result.flags = ResultFlag.ENUM|(ResultFlag.DEPRECATED if enum.is_deprecated else ResultFlag(0))
-            result.url = state.current_url + '#' + enum.id
+            result.url = enum.base_url + '#' + enum.id
             result.prefix = state.current_prefix
             result.name = enum.name
             result.keywords = search_keywords
@@ -1405,7 +1407,7 @@ def parse_typedef(state: State, element: ET.Element):
     assert element.tag == 'memberdef' and element.attrib['kind'] == 'typedef'
 
     typedef = Empty()
-    typedef.id = extract_id_hash(state, element)
+    typedef.base_url, typedef.id = parse_id(state, element)
     typedef.is_using = element.findtext('definition', '').startswith('using')
     typedef.type = parse_type(state, element.find('type'))
     typedef.args = parse_type(state, element.find('argsstring'))
@@ -1415,12 +1417,13 @@ def parse_typedef(state: State, element: ET.Element):
     typedef.is_protected = element.attrib['prot'] == 'protected'
     typedef.has_template_details, typedef.templates = parse_template_params(state, element.find('templateparamlist'), templates)
 
-    typedef.has_details = typedef.description or typedef.has_template_details
+    typedef.has_details = typedef.base_url == state.current_url and (typedef.description or typedef.has_template_details)
     if typedef.brief or typedef.has_details:
-        if not state.doxyfile['M_SEARCH_DISABLED']:
+        # Avoid duplicates in search
+        if typedef.base_url == state.current_url and not state.doxyfile['M_SEARCH_DISABLED']:
             result = Empty()
             result.flags = ResultFlag.TYPEDEF|(ResultFlag.DEPRECATED if typedef.is_deprecated else ResultFlag(0))
-            result.url = state.current_url + '#' + typedef.id
+            result.url = typedef.base_url + '#' + typedef.id
             result.prefix = state.current_prefix
             result.name = typedef.name
             result.keywords = search_keywords
@@ -1432,7 +1435,7 @@ def parse_func(state: State, element: ET.Element):
     assert element.tag == 'memberdef' and element.attrib['kind'] == 'function'
 
     func = Empty()
-    func.id = extract_id_hash(state, element)
+    func.base_url, func.id = parse_id(state, element)
     func.type = parse_type(state, element.find('type'))
     func.name = fix_type_spacing(html.escape(element.find('name').text))
     func.brief = parse_desc(state, element.find('briefdescription'))
@@ -1519,12 +1522,13 @@ def parse_func(state: State, element: ET.Element):
     # Some param description got unused
     if params: logging.warning("{}: function parameter description doesn't match parameter names: {}".format(state.current, repr(params)))
 
-    func.has_details = func.description or func.has_template_details or func.has_param_details or func.return_value or func.return_values
+    func.has_details = func.base_url == state.current_url and (func.description or func.has_template_details or func.has_param_details or func.return_value or func.return_values)
     if func.brief or func.has_details:
-        if not state.doxyfile['M_SEARCH_DISABLED']:
+        # Avoid duplicates in search
+        if func.base_url == state.current_url and not state.doxyfile['M_SEARCH_DISABLED']:
             result = Empty()
             result.flags = ResultFlag.FUNC|(ResultFlag.DEPRECATED if func.is_deprecated else ResultFlag(0))|(ResultFlag.DELETED if func.is_deleted else ResultFlag(0))
-            result.url = state.current_url + '#' + func.id
+            result.url = func.base_url + '#' + func.id
             result.prefix = state.current_prefix
             result.name = func.name
             result.keywords = search_keywords
@@ -1538,7 +1542,7 @@ def parse_var(state: State, element: ET.Element):
     assert element.tag == 'memberdef' and element.attrib['kind'] == 'variable'
 
     var = Empty()
-    var.id = extract_id_hash(state, element)
+    var.base_url, var.id = parse_id(state, element)
     var.type = parse_type(state, element.find('type'))
     if var.type.startswith('constexpr'):
         var.type = var.type[10:]
@@ -1552,12 +1556,13 @@ def parse_var(state: State, element: ET.Element):
     var.brief = parse_desc(state, element.find('briefdescription'))
     var.description, search_keywords, var.is_deprecated = parse_var_desc(state, element)
 
-    var.has_details = not not var.description
+    var.has_details = var.base_url == state.current_url and var.description
     if var.brief or var.has_details:
-        if not state.doxyfile['M_SEARCH_DISABLED']:
+        # Avoid duplicates in search
+        if var.base_url == state.current_url and not state.doxyfile['M_SEARCH_DISABLED']:
             result = Empty()
             result.flags = ResultFlag.VAR|(ResultFlag.DEPRECATED if var.is_deprecated else ResultFlag(0))
-            result.url = state.current_url + '#' + var.id
+            result.url = var.base_url + '#' + var.id
             result.prefix = state.current_prefix
             result.name = var.name
             result.keywords = search_keywords
index 61a078eb45018ecff34d15bf9b0ef7ba0ff651ea..60ee6a460ba448c2ecfe8350aff7be1627d6e7d3 100644 (file)
@@ -1,5 +1,5 @@
             <dt>
               {% set j = joiner(',\n              ') %}
-              <span class="m-dox-wrap-bumper">enum {% if enum.is_strong %}class {% endif %}<a href="#{{ enum.id }}" {% if enum.has_details %}class="m-dox"{% else %}class="m-dox-self" name="{{ enum.id }}"{% endif %}>{{ enum.name }}</a>{% if enum.type %}: {{ enum.type }}{% endif %} { </span><span class="m-dox-wrap">{% for value in enum.values %}{{ j() }}<a href="#{{ value.id }}" class="m-dox">{{ value.name }}</a>{% if value.initializer %} {{ value.initializer }}{% endif %}{% if value.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% endfor %} }{% if enum.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if mark_nonpublic and enum.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% endif %}</span>
+              <span class="m-dox-wrap-bumper">enum {% if enum.is_strong %}class {% endif %}<a href="{% if enum.base_url != compound.url %}{{ enum.base_url }}{% endif %}#{{ enum.id }}" {% if enum.has_details or enum.base_url != compound.url %}class="m-dox"{% else %}class="m-dox-self" name="{{ enum.id }}"{% endif %}>{{ enum.name }}</a>{% if enum.type %}: {{ enum.type }}{% endif %} { </span><span class="m-dox-wrap">{% for value in enum.values %}{{ j() }}<a href="#{{ value.id }}" class="m-dox">{{ value.name }}</a>{% if value.initializer %} {{ value.initializer }}{% endif %}{% if value.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% endfor %} }{% if enum.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if mark_nonpublic and enum.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% endif %}</span>
             </dt>
             <dd>{{ enum.brief }}</dd>
index a5fc72d9513e753995cf76155070167c0c85c0c0..bb9b680558bcade23be296a46e44a1a61fb541e7 100644 (file)
@@ -4,6 +4,6 @@
               <div class="m-dox-template">template&lt;{% for t in func.templates %}{{ j() }}{{ t.type }}{% if t.name %} {{ t.name }}{% endif %}{% if t.default %} = {{ t.default }}{% endif%}{% endfor %}&gt;</div>
               {% endif %}
               {% set j = joiner(',\n              ') %}
-              <span class="m-dox-wrap-bumper">{{ func.prefix }}{% if func.type == 'void' %}void {% elif func.type %}auto {% endif %}<a href="#{{ func.id }}" {% if func.has_details %}class="m-dox"{% else %}class="m-dox-self" name="{{ func.id }}"{% endif %}>{{ func.name }}</a>(</span><span class="m-dox-wrap">{% for param in func.params %}{{ j() }}{{ param.type_name }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}){{ func.suffix }}{% if func.type and func.type != 'void' %} -&gt; {{ func.type }}{% endif %}{% if func.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if not func.type or mark_nonpublic %}{% if func.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% elif func.is_private %} <span class="m-label m-flat m-danger">private</span>{% endif %}{% endif %}{% if func.is_defaulted %} <span class="m-label m-flat m-info">defaulted</span>{% endif %}{% if func.is_deleted %} <span class="m-label m-flat m-danger">deleted</span>{% endif %}{% if func.is_explicit %} <span class="m-label m-flat m-info">explicit</span> {% endif %}{% if func.is_pure_virtual %} <span class="m-label m-flat m-warning">pure virtual</span>{% elif func.is_virtual %} <span class="m-label m-flat m-warning">virtual</span>{% endif %}{% if func.is_constexpr %} <span class="m-label m-flat m-primary">constexpr</span>{% endif %}{% if func.is_noexcept %} <span class="m-label m-flat m-success">noexcept</span>{% endif %}</span>
+              <span class="m-dox-wrap-bumper">{{ func.prefix }}{% if func.type == 'void' %}void {% elif func.type %}auto {% endif %}<a href="{% if func.base_url != compound.url %}{{ func.base_url }}{% endif %}#{{ func.id }}" {% if func.has_details or func.base_url != compound.url %}class="m-dox"{% else %}class="m-dox-self" name="{{ func.id }}"{% endif %}>{{ func.name }}</a>(</span><span class="m-dox-wrap">{% for param in func.params %}{{ j() }}{{ param.type_name }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}){{ func.suffix }}{% if func.type and func.type != 'void' %} -&gt; {{ func.type }}{% endif %}{% if func.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if not func.type or mark_nonpublic %}{% if func.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% elif func.is_private %} <span class="m-label m-flat m-danger">private</span>{% endif %}{% endif %}{% if func.is_defaulted %} <span class="m-label m-flat m-info">defaulted</span>{% endif %}{% if func.is_deleted %} <span class="m-label m-flat m-danger">deleted</span>{% endif %}{% if func.is_explicit %} <span class="m-label m-flat m-info">explicit</span> {% endif %}{% if func.is_pure_virtual %} <span class="m-label m-flat m-warning">pure virtual</span>{% elif func.is_virtual %} <span class="m-label m-flat m-warning">virtual</span>{% endif %}{% if func.is_constexpr %} <span class="m-label m-flat m-primary">constexpr</span>{% endif %}{% if func.is_noexcept %} <span class="m-label m-flat m-success">noexcept</span>{% endif %}</span>
             </dt>
             <dd>{{ func.brief }}</dd>
index f370c1ab3bf5bbfce533d1ec83fcd059ba2dca73..cbcb4b570c48c2a6f4cf1b08de60286ee3b6f3e9 100644 (file)
@@ -3,7 +3,7 @@
               {% set j = joiner(', ') %}
               <div class="m-dox-template">template&lt;{% for t in typedef.templates %}{{ j() }}{{ t.type }}{% if t.name %} {{ t.name }}{% endif %}{% if t.default %} = {{ t.default }}{% endif%}{% endfor %}&gt;</div>
               {% endif %}
-              using <a href="#{{ typedef.id }}" {% if typedef.has_details %}class="m-dox"{% else %}class="m-dox-self" name="{{ typedef.id }}"{% endif %}>{{ typedef.name }}</a> = {{ typedef.type }}{{ typedef.args }}{% if typedef.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if mark_nonpublic and typedef.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% endif %}
+              using <a href="{% if typedef.base_url != compound.url %}{{ typedef.base_url }}{% endif %}#{{ typedef.id }}" {% if typedef.has_details or typedef.base_url != compound.url %}class="m-dox"{% else %}class="m-dox-self" name="{{ typedef.id }}"{% endif %}>{{ typedef.name }}</a> = {{ typedef.type }}{{ typedef.args }}{% if typedef.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if mark_nonpublic and typedef.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% endif %}
               {# This empty line needs to be there otherwise it's eaten #}
 
             </dt>
index 50fb95ef5d655df36e4fcbd3224c05895cdba93c..820cab85868860e93b076adffc628c213188453a 100644 (file)
@@ -1,2 +1,2 @@
-            <dt>{% if var.is_static %}static {% endif %}{{ var.type }} <a href="#{{ var.id }}" {% if var.has_details %}class="m-dox"{% else %}class="m-dox-self" name="{{ var.id }}"{% endif %}>{{ var.name }}</a>{% if var.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if mark_nonpublic and var.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% endif %}{% if var.is_constexpr %} <span class="m-label m-flat m-primary">constexpr</span>{% endif %}</dt>
+            <dt>{% if var.is_static %}static {% endif %}{{ var.type }} <a href="{% if var.base_url != compound.url %}{{ var.base_url }}{% endif %}#{{ var.id }}" {% if var.has_details or var.base_url != compound.url %}class="m-dox"{% else %}class="m-dox-self" name="{{ var.id }}"{% endif %}>{{ var.name }}</a>{% if var.is_deprecated %} <span class="m-label m-danger">deprecated</span>{% endif %}{% if mark_nonpublic and var.is_protected %} <span class="m-label m-flat m-warning">protected</span>{% endif %}{% if var.is_constexpr %} <span class="m-label m-flat m-primary">constexpr</span>{% endif %}</dt>
             <dd>{{ var.brief }}</dd>
diff --git a/doxygen/test/compound_namespace_members_in_file_scope/Doxyfile b/doxygen/test/compound_namespace_members_in_file_scope/Doxyfile
new file mode 100644 (file)
index 0000000..8663c0b
--- /dev/null
@@ -0,0 +1,13 @@
+INPUT                   = File.h
+QUIET                   = YES
+GENERATE_HTML           = NO
+GENERATE_LATEX          = NO
+GENERATE_XML            = YES
+XML_PROGRAMLISTING      = NO
+XML_NAMESPACE_MEMBERS_IN_FILE_SCOPE = YES
+
+M_PAGE_FINE_PRINT       =
+M_THEME_COLOR           =
+M_LINKS_NAVBAR1         =
+M_LINKS_NAVBAR2         =
+M_SEARCH_DISABLED       = YES
diff --git a/doxygen/test/compound_namespace_members_in_file_scope/File.h b/doxygen/test/compound_namespace_members_in_file_scope/File.h
new file mode 100644 (file)
index 0000000..94fcbec
--- /dev/null
@@ -0,0 +1,104 @@
+/** @file
+ * @brief A file
+ */
+
+/** @brief A namespace */
+namespace Namespace {
+
+/**
+@brief A function
+
+Detailed function docs.
+*/
+void foo();
+
+/** @brief Function with just a brief */
+void fooBrief();
+
+/**
+@brief An enum
+
+Detailed enum docs.
+*/
+enum Enum {
+    Value /**< A value */
+};
+
+/** @brief Enum with just a brief */
+enum EnumBrief {};
+
+/**
+@brief A typedef
+
+Detailed typedef docs.
+*/
+typedef int Typedef;
+
+/** @brief Typedef with just a brief */
+typedef int TypedefBrief;
+
+/**
+@brief A variable
+
+Detailed variable docs.
+*/
+constexpr int Variable = 5;
+
+/** @brief Variable with just a brief */
+constexpr int VariableBrief = 5;
+
+/**
+@brief A macro
+
+This appears only in the file docs and fully expanded.
+*/
+#define A_MACRO
+
+}
+
+/* For the undocumented namespace, everything should appear in file docs */
+namespace UndocumentedNamespace {
+
+/**
+@brief A function
+
+Detailed function docs.
+*/
+void foo();
+
+/** @brief Function with just a brief */
+void fooBrief();
+
+/**
+@brief An enum
+
+Detailed enum docs.
+*/
+enum Enum {
+    Value /**< A value */
+};
+
+/** @brief Enum with just a brief */
+enum EnumBrief {};
+
+/**
+@brief A typedef
+
+Detailed typedef docs.
+*/
+typedef int Typedef;
+
+/** @brief Typedef with just a brief */
+typedef int TypedefBrief;
+
+/**
+@brief A variable
+
+Detailed variable docs.
+*/
+constexpr int Variable = 5;
+
+/** @brief Variable with just a brief */
+constexpr int VariableBrief = 5;
+
+}
diff --git a/doxygen/test/compound_namespace_members_in_file_scope/File_8h.html b/doxygen/test/compound_namespace_members_in_file_scope/File_8h.html
new file mode 100644 (file)
index 0000000..ca9663c
--- /dev/null
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>File.h file | 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>
+          File.h <span class="m-thin">file</span>
+        </h1>
+        <p>A file.</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#namespaces">Namespaces</a></li>
+                <li><a href="#enum-members">Enums</a></li>
+                <li><a href="#typedef-members">Typedefs</a></li>
+                <li><a href="#func-members">Functions</a></li>
+                <li><a href="#var-members">Variables</a></li>
+                <li><a href="#defines">Defines</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="namespaces">
+          <h2><a href="#namespaces">Namespaces</a></h3>
+          <dl class="m-dox">
+            <dt>namespace <a href="namespaceNamespace.html" class="m-dox">Namespace</a></dt>
+            <dd>A namespace.</dd>
+          </dl>
+        </section>
+        <section id="enum-members">
+          <h2><a href="#enum-members">Enums</a></h3>
+          <dl class="m-dox">
+            <dt>
+              <span class="m-dox-wrap-bumper">enum <a href="namespaceNamespace.html#add172b93283b1ab7612c3ca6cc5dcfea" class="m-dox">Enum</a> { </span><span class="m-dox-wrap"><a href="#add172b93283b1ab7612c3ca6cc5dcfeaa2ef662d81d3f82682b3f993eba87420e" class="m-dox">Value</a> }</span>
+            </dt>
+            <dd>An enum.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">enum <a href="namespaceNamespace.html#abddfdbdba5c5512af93fad488e874a6e" class="m-dox">EnumBrief</a> { </span><span class="m-dox-wrap"> }</span>
+            </dt>
+            <dd>Enum with just a brief.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">enum <a href="#aeefb6576381a49c1c6e3b7352fe4472f" class="m-dox">Enum</a> { </span><span class="m-dox-wrap"><a href="#aeefb6576381a49c1c6e3b7352fe4472fafbbe8f667f7bfa0603c82d017bc304da" class="m-dox">Value</a> }</span>
+            </dt>
+            <dd>An enum.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">enum <a href="#a2632ae5b1d4434dc63030d1343378099" class="m-dox-self" name="a2632ae5b1d4434dc63030d1343378099">EnumBrief</a> { </span><span class="m-dox-wrap"> }</span>
+            </dt>
+            <dd>Enum with just a brief.</dd>
+          </dl>
+        </section>
+        <section id="typedef-members">
+          <h2><a href="#typedef-members">Typedefs</a></h3>
+          <dl class="m-dox">
+            <dt>
+              using <a href="namespaceNamespace.html#abe2a245304bc2234927ef33175646e08" class="m-dox">Typedef</a> = int
+            </dt>
+            <dd>A typedef.</dd>
+            <dt>
+              using <a href="namespaceNamespace.html#a3528713a2cb2fc8e4b0c8fab2e3142e6" class="m-dox">TypedefBrief</a> = int
+            </dt>
+            <dd>Typedef with just a brief.</dd>
+            <dt>
+              using <a href="#ad339d36696a203dd41ce1b4967eaff0a" class="m-dox">Typedef</a> = int
+            </dt>
+            <dd>A typedef.</dd>
+            <dt>
+              using <a href="#a023e422700677703873bc1e7372e7400" class="m-dox-self" name="a023e422700677703873bc1e7372e7400">TypedefBrief</a> = int
+            </dt>
+            <dd>Typedef with just a brief.</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="namespaceNamespace.html#a0f1fe1a972c7c4196988a1bdde63ec77" class="m-dox">foo</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>A function.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">void <a href="namespaceNamespace.html#a25c0cb6154508312ca2f28fdab944741" class="m-dox">fooBrief</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>Function with just a brief.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">void <a href="#a3d859e95e6eb8b4da1c10b6417ab8e9b" class="m-dox">foo</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>A function.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">void <a href="#a9ca3f6e800be14b033c364187b444b2f" class="m-dox-self" name="a9ca3f6e800be14b033c364187b444b2f">fooBrief</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>Function with just a brief.</dd>
+          </dl>
+        </section>
+        <section id="var-members">
+          <h2><a href="#var-members">Variables</a></h3>
+          <dl class="m-dox">
+            <dt>int <a href="namespaceNamespace.html#ad3121960d8665ab045ca1bfa1480a86d" class="m-dox">Variable</a> <span class="m-label m-flat m-primary">constexpr</span></dt>
+            <dd>A variable.</dd>
+            <dt>int <a href="namespaceNamespace.html#aa8b31b63b2a5e71fe1734212a093bdc3" class="m-dox">VariableBrief</a> <span class="m-label m-flat m-primary">constexpr</span></dt>
+            <dd>Variable with just a brief.</dd>
+            <dt>int <a href="#a7dc9e9cdaf8275ac8636d69b90f37045" class="m-dox">Variable</a> <span class="m-label m-flat m-primary">constexpr</span></dt>
+            <dd>A variable.</dd>
+            <dt>int <a href="#a39904e2093f37ccfc2b7ad44ead2420a" class="m-dox-self" name="a39904e2093f37ccfc2b7ad44ead2420a">VariableBrief</a> <span class="m-label m-flat m-primary">constexpr</span></dt>
+            <dd>Variable with just a brief.</dd>
+          </dl>
+        </section>
+        <section id="define-members">
+          <h2><a href="#define-members">Defines</a></h3>
+          <dl class="m-dox">
+            <dt>
+              <span class="m-dox-wrap-bumper">#define <a href="#a1e6e7cc6cc96adc8342c838f55790259" class="m-dox">A_MACRO</a></span>
+            </dt>
+            <dd>A macro.</dd>
+          </dl>
+        </section>
+        <section>
+          <h2>Enum documentation</h2>
+          <section class="m-dox-details" id="aeefb6576381a49c1c6e3b7352fe4472f"><div>
+            <h3>
+              enum <a href="#aeefb6576381a49c1c6e3b7352fe4472f" class="m-dox-self">Enum</a>
+            </h3>
+            <p>An enum.</p>
+<p>Detailed enum docs.</p>
+            <table class="m-table m-fullwidth m-flat m-dox">
+              <thead><tr><th style="width: 1%">Enumerators</th><th></th></tr></thead>
+              <tbody>
+                <tr>
+                  <td><a href="#aeefb6576381a49c1c6e3b7352fe4472fafbbe8f667f7bfa0603c82d017bc304da" class="m-dox-self" name="aeefb6576381a49c1c6e3b7352fe4472fafbbe8f667f7bfa0603c82d017bc304da">Value</a></td>
+                  <td>
+<p>A value</p>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div></section>
+        </section>
+        <section>
+          <h2>Typedef documentation</h2>
+          <section class="m-dox-details" id="ad339d36696a203dd41ce1b4967eaff0a"><div>
+            <h3>
+              typedef int <a href="#ad339d36696a203dd41ce1b4967eaff0a" class="m-dox-self">Typedef</a>
+            </h3>
+            <p>A typedef.</p>
+<p>Detailed typedef docs.</p>
+          </div></section>
+        </section>
+        <section>
+          <h2>Function documentation</h2>
+          <section class="m-dox-details" id="a3d859e95e6eb8b4da1c10b6417ab8e9b"><div>
+            <h3>
+              <span class="m-dox-wrap-bumper">void </span><span class="m-dox-wrap"><span class="m-dox-wrap-bumper"><a href="#a3d859e95e6eb8b4da1c10b6417ab8e9b" class="m-dox-self">foo</a>(</span><span class="m-dox-wrap">)</span></span>
+            </h3>
+            <p>A function.</p>
+<p>Detailed function docs.</p>
+          </div></section>
+        </section>
+        <section>
+          <h2>Variable documentation</h2>
+          <section class="m-dox-details" id="a7dc9e9cdaf8275ac8636d69b90f37045"><div>
+            <h3>
+              int <a href="#a7dc9e9cdaf8275ac8636d69b90f37045" class="m-dox-self">Variable</a> <span class="m-label m-primary">constexpr</span>
+            </h3>
+            <p>A variable.</p>
+<p>Detailed variable docs.</p>
+          </div></section>
+        </section>
+        <section>
+          <h2>Define documentation</h2>
+          <section class="m-dox-details" id="a1e6e7cc6cc96adc8342c838f55790259"><div>
+            <h3>
+              <span class="m-dox-wrap-bumper">#define <a href="#a1e6e7cc6cc96adc8342c838f55790259" class="m-dox-self">A_MACRO</a></span>
+            </h3>
+            <p>A macro.</p>
+<p>This appears only in the file docs and fully expanded.</p>
+          </div></section>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/doxygen/test/compound_namespace_members_in_file_scope/namespaceNamespace.html b/doxygen/test/compound_namespace_members_in_file_scope/namespaceNamespace.html
new file mode 100644 (file)
index 0000000..c471ffe
--- /dev/null
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Namespace namespace | 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>Namespace <span class="m-thin">namespace</span></h1>
+        <p>A namespace.</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#enum-members">Enums</a></li>
+                <li><a href="#typedef-members">Typedefs</a></li>
+                <li><a href="#func-members">Functions</a></li>
+                <li><a href="#var-members">Variables</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="enum-members">
+          <h2><a href="#enum-members">Enums</a></h3>
+          <dl class="m-dox">
+            <dt>
+              <span class="m-dox-wrap-bumper">enum <a href="#add172b93283b1ab7612c3ca6cc5dcfea" class="m-dox">Enum</a> { </span><span class="m-dox-wrap"><a href="#add172b93283b1ab7612c3ca6cc5dcfeaa2ef662d81d3f82682b3f993eba87420e" class="m-dox">Value</a> }</span>
+            </dt>
+            <dd>An enum.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">enum <a href="#abddfdbdba5c5512af93fad488e874a6e" class="m-dox-self" name="abddfdbdba5c5512af93fad488e874a6e">EnumBrief</a> { </span><span class="m-dox-wrap"> }</span>
+            </dt>
+            <dd>Enum with just a brief.</dd>
+          </dl>
+        </section>
+        <section id="typedef-members">
+          <h2><a href="#typedef-members">Typedefs</a></h3>
+          <dl class="m-dox">
+            <dt>
+              using <a href="#abe2a245304bc2234927ef33175646e08" class="m-dox">Typedef</a> = int
+            </dt>
+            <dd>A typedef.</dd>
+            <dt>
+              using <a href="#a3528713a2cb2fc8e4b0c8fab2e3142e6" class="m-dox-self" name="a3528713a2cb2fc8e4b0c8fab2e3142e6">TypedefBrief</a> = int
+            </dt>
+            <dd>Typedef with just a brief.</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="#a0f1fe1a972c7c4196988a1bdde63ec77" class="m-dox">foo</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>A function.</dd>
+            <dt>
+              <span class="m-dox-wrap-bumper">void <a href="#a25c0cb6154508312ca2f28fdab944741" class="m-dox-self" name="a25c0cb6154508312ca2f28fdab944741">fooBrief</a>(</span><span class="m-dox-wrap">)</span>
+            </dt>
+            <dd>Function with just a brief.</dd>
+          </dl>
+        </section>
+        <section id="var-members">
+          <h2><a href="#var-members">Variables</a></h3>
+          <dl class="m-dox">
+            <dt>int <a href="#ad3121960d8665ab045ca1bfa1480a86d" class="m-dox">Variable</a> <span class="m-label m-flat m-primary">constexpr</span></dt>
+            <dd>A variable.</dd>
+            <dt>int <a href="#aa8b31b63b2a5e71fe1734212a093bdc3" class="m-dox-self" name="aa8b31b63b2a5e71fe1734212a093bdc3">VariableBrief</a> <span class="m-label m-flat m-primary">constexpr</span></dt>
+            <dd>Variable with just a brief.</dd>
+          </dl>
+        </section>
+        <section>
+          <h2>Enum documentation</h2>
+          <section class="m-dox-details" id="add172b93283b1ab7612c3ca6cc5dcfea"><div>
+            <h3>
+              enum Namespace::<wbr /><a href="#add172b93283b1ab7612c3ca6cc5dcfea" class="m-dox-self">Enum</a>
+            </h3>
+            <p>An enum.</p>
+<p>Detailed enum docs.</p>
+            <table class="m-table m-fullwidth m-flat m-dox">
+              <thead><tr><th style="width: 1%">Enumerators</th><th></th></tr></thead>
+              <tbody>
+                <tr>
+                  <td><a href="#add172b93283b1ab7612c3ca6cc5dcfeaa2ef662d81d3f82682b3f993eba87420e" class="m-dox-self" name="add172b93283b1ab7612c3ca6cc5dcfeaa2ef662d81d3f82682b3f993eba87420e">Value</a></td>
+                  <td>
+<p>A value</p>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div></section>
+        </section>
+        <section>
+          <h2>Typedef documentation</h2>
+          <section class="m-dox-details" id="abe2a245304bc2234927ef33175646e08"><div>
+            <h3>
+              typedef int Namespace::<wbr /><a href="#abe2a245304bc2234927ef33175646e08" class="m-dox-self">Typedef</a>
+            </h3>
+            <p>A typedef.</p>
+<p>Detailed typedef docs.</p>
+          </div></section>
+        </section>
+        <section>
+          <h2>Function documentation</h2>
+          <section class="m-dox-details" id="a0f1fe1a972c7c4196988a1bdde63ec77"><div>
+            <h3>
+              <span class="m-dox-wrap-bumper">void Namespace::<wbr /></span><span class="m-dox-wrap"><span class="m-dox-wrap-bumper"><a href="#a0f1fe1a972c7c4196988a1bdde63ec77" class="m-dox-self">foo</a>(</span><span class="m-dox-wrap">)</span></span>
+            </h3>
+            <p>A function.</p>
+<p>Detailed function docs.</p>
+          </div></section>
+        </section>
+        <section>
+          <h2>Variable documentation</h2>
+          <section class="m-dox-details" id="ad3121960d8665ab045ca1bfa1480a86d"><div>
+            <h3>
+              int Namespace::<wbr /><a href="#ad3121960d8665ab045ca1bfa1480a86d" class="m-dox-self">Variable</a> <span class="m-label m-primary">constexpr</span>
+            </h3>
+            <p>A variable.</p>
+<p>Detailed variable docs.</p>
+          </div></section>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index 0e77c509fafe0b478abe057914c5052bbc5daa09..afec645fefc258f4ecc6730eab96b04c62ea7edb 100644 (file)
@@ -4,6 +4,10 @@ GENERATE_HTML           = NO
 GENERATE_LATEX          = NO
 GENERATE_XML            = YES
 XML_PROGRAMLISTING      = NO
+
+# Verify that this doesn't cause duplicated items
+XML_NAMESPACE_MEMBERS_IN_FILE_SCOPE = YES
+
 EXAMPLE_PATH            = .
 ALIASES                 = \
     "m_keywords{1}=@xmlonly<mcss:search xmlns:mcss=\"http://mcss.mosra.cz/doxygen/\" mcss:keywords=\"\1\" />@endxmlonly" \
index b390cf9128403838867ee76fbc85ef4d72f106e3..83c91263980c85abed72e4d08629ed0bd84a4852 100644 (file)
@@ -25,7 +25,9 @@
 import os
 import unittest
 
-from test import IntegrationTestCase
+from distutils.version import LooseVersion
+
+from test import IntegrationTestCase, doxygen_version
 
 class Listing(IntegrationTestCase):
     def __init__(self, *args, **kwargs):
@@ -185,3 +187,21 @@ class Deprecated(IntegrationTestCase):
         # Base and derived class listing
         self.assertEqual(*self.actual_expected_contents('structDeprecatedNamespace_1_1BaseDeprecatedClass.html'))
         self.assertEqual(*self.actual_expected_contents('structDeprecatedNamespace_1_1DeprecatedClass.html'))
+
+class NamespaceMembersInFileScope(IntegrationTestCase):
+    def __init__(self, *args, **kwargs):
+        super().__init__(__file__, 'namespace_members_in_file_scope', *args, **kwargs)
+
+    def test(self):
+        self.run_dox2html5(wildcard='namespaceNamespace.xml')
+
+        # The namespace should have the detailed docs
+        self.assertEqual(*self.actual_expected_contents('namespaceNamespace.html'))
+
+    @unittest.skipUnless(LooseVersion(doxygen_version()) > LooseVersion("1.8.14"),
+                         "https://github.com/doxygen/doxygen/pull/653")
+    def test_file(self):
+        self.run_dox2html5(wildcard='File_8h.xml')
+
+        # The file should have just links to detailed docs
+        self.assertEqual(*self.actual_expected_contents('File_8h.html'))