diff --git a/docs/mrdocs.schema.json b/docs/mrdocs.schema.json index 8424e59b8..a8f9bd3ab 100644 --- a/docs/mrdocs.schema.json +++ b/docs/mrdocs.schema.json @@ -297,6 +297,16 @@ "title": "Detect and reduce SFINAE expressions", "type": "boolean" }, + "show-namespaces": { + "default": true, + "description": "When set to true, MrDocs creates a page for each namespace in the documentation.", + "enum": [ + true, + false + ], + "title": "Show namespace pages in the documentation", + "type": "boolean" + }, "sort-members": { "default": true, "description": "When set to `true`, sort the members of a record or namespace by name and parameters. When set to `false`, the members are included in the declaration order they are extracted.", @@ -385,7 +395,7 @@ "type": "array" }, "tagfile": { - "default": "/reference.tag.xml", + "default": "/reference.tag.xml", "description": "Specifies the full path (filename) where the generated tagfile should be saved. If left empty, no tagfile will be generated.", "title": "Path for the tagfile", "type": "string" diff --git a/docs/website/render.js b/docs/website/render.js index 0d07db040..f5c7724a6 100644 --- a/docs/website/render.js +++ b/docs/website/render.js @@ -67,15 +67,17 @@ target_compile_features(${sourceBasename} PRIVATE cxx_std_23) // Run mrdocs to generate documentation const mrdocsConfig = path.join(absSnippetsDir, 'mrdocs.yml') const mrdocsInput = cmakeListsPath - const mrdocsOutput = path.join(absSnippetsDir, 'output') + const mrdocsOutput = path.join(absSnippetsDir, 'output', 'reference.html') const args = [ mrdocsExecutable, `--config=${mrdocsConfig}`, mrdocsInput, `--output=${mrdocsOutput}`, - '--multipage=true', + '--multipage=false', '--generator=html', '--embedded=true', + '--show-namespaces=false', + '--tagfile=', ]; const command = args.join(' '); console.log(`Running command: ${command}`) @@ -87,22 +89,19 @@ target_compile_features(${sourceBasename} PRIVATE cxx_std_23) } // Look load symbol page in the output directory - const documentationFilename = `${sourceBasename}.html` - const documentationPath = path.join(mrdocsOutput, documentationFilename) - if (!fs.existsSync(documentationPath)) { - console.log(`Documentation file ${documentationFilename} not found in ${mrdocsOutput}`) + if (!fs.existsSync(mrdocsOutput)) { + console.log(`Documentation file not found in ${mrdocsOutput}`) console.log('Failed to generate website panel documentation') process.exit(1) } - panel.documentation = fs.readFileSync(documentationPath, 'utf8'); + panel.documentation = fs.readFileSync(mrdocsOutput, 'utf8'); // Also inject the contents of the source file as highlighted C++ const snippetContents = fs.readFileSync(sourcePath, 'utf8'); - const highlightedSnippet = hljs.highlight(snippetContents, {language: 'cpp'}).value; - panel.snippet = highlightedSnippet; + panel.snippet = hljs.highlight(snippetContents, {language: 'cpp'}).value; // Delete these temporary files - fs.rmSync(mrdocsOutput, {recursive: true}); + fs.unlinkSync(mrdocsOutput); fs.unlinkSync(cmakeListsPath); console.log(`Documentation generated successfully for panel ${panel.source}`) diff --git a/docs/website/snippets/mrdocs.yml b/docs/website/snippets/mrdocs.yml index 255a15647..134883102 100644 --- a/docs/website/snippets/mrdocs.yml +++ b/docs/website/snippets/mrdocs.yml @@ -1,3 +1,5 @@ source-root: . input: - . +show-namespaces: false +multipage: false \ No newline at end of file diff --git a/src/lib/Gen/hbs/HandlebarsCorpus.cpp b/src/lib/Gen/hbs/HandlebarsCorpus.cpp index 0f9617b8c..18edf08b7 100644 --- a/src/lib/Gen/hbs/HandlebarsCorpus.cpp +++ b/src/lib/Gen/hbs/HandlebarsCorpus.cpp @@ -120,7 +120,7 @@ HandlebarsCorpus:: construct(Info const& I) const { dom::Object obj = this->DomCorpus::construct(I); - if (shouldGenerate(I)) + if (shouldGenerate(I, getCorpus().config)) { obj.set("url", getURL(I)); obj.set("anchor", names_.getQualified(I.id, '-')); @@ -132,6 +132,7 @@ construct(Info const& I) const // for the primary template if it's part of the corpus. if (Info const* primaryInfo = findAlternativeURLInfo(getCorpus(), I)) { + MRDOCS_ASSERT(shouldGenerate(*primaryInfo, getCorpus().config)); obj.set("url", getURL(*primaryInfo)); obj.set("anchor", names_.getQualified(primaryInfo->id, '-')); } diff --git a/src/lib/Gen/hbs/MultiPageVisitor.cpp b/src/lib/Gen/hbs/MultiPageVisitor.cpp index 88985a101..519a0f2b8 100644 --- a/src/lib/Gen/hbs/MultiPageVisitor.cpp +++ b/src/lib/Gen/hbs/MultiPageVisitor.cpp @@ -21,47 +21,47 @@ void MultiPageVisitor:: operator()(T const& I) { - MRDOCS_CHECK_OR(shouldGenerate(I)); - - // Increment the count - count_.fetch_add(1, std::memory_order_relaxed); - ex_.async([this, &I](Builder& builder) { - // =================================== - // Open the output file - // =================================== - std::string const path = files::appendPath(outputPath_, builder.domCorpus.getURL(I)); - std::string const dir = files::getParentDir(path); - if (auto exp = files::createDirectory(dir); !exp) + if (shouldGenerate(I, corpus_.config)) { - exp.error().Throw(); - } - std::ofstream os; - try - { - os.open(path, - std::ios_base::binary | - std::ios_base::out | - std::ios_base::trunc // | std::ios_base::noreplace - ); - if (!os.is_open()) { - formatError(R"(std::ofstream("{}") failed)", path) + // =================================== + // Open the output file + // =================================== + std::string const path = files::appendPath(outputPath_, builder.domCorpus.getURL(I)); + std::string const dir = files::getParentDir(path); + if (auto exp = files::createDirectory(dir); !exp) + { + exp.error().Throw(); + } + std::ofstream os; + try + { + os.open(path, + std::ios_base::binary | + std::ios_base::out | + std::ios_base::trunc // | std::ios_base::noreplace + ); + if (!os.is_open()) { + formatError(R"(std::ofstream("{}") failed)", path) + .Throw(); + } + } + catch (std::exception const& ex) + { + formatError(R"(std::ofstream("{}") threw "{}")", path, ex.what()) .Throw(); } - } - catch (std::exception const& ex) - { - formatError(R"(std::ofstream("{}") threw "{}")", path, ex.what()) - .Throw(); - } - // =================================== - // Generate the output - // =================================== - if (auto exp = builder(os, I); !exp) - { - exp.error().Throw(); + // =================================== + // Generate the output + // =================================== + if (auto exp = builder(os, I); !exp) + { + exp.error().Throw(); + } + + count_.fetch_add(1, std::memory_order_relaxed); } // =================================== diff --git a/src/lib/Gen/hbs/SinglePageVisitor.cpp b/src/lib/Gen/hbs/SinglePageVisitor.cpp index 66d89a4c5..e53d8ee75 100644 --- a/src/lib/Gen/hbs/SinglePageVisitor.cpp +++ b/src/lib/Gen/hbs/SinglePageVisitor.cpp @@ -21,21 +21,24 @@ void SinglePageVisitor:: operator()(T const& I) { - MRDOCS_CHECK_OR(shouldGenerate(I)); - ex_.async([this, &I, symbolIdx = numSymbols_++](Builder& builder) + if (shouldGenerate(I, corpus_.config)) { - // Output to an independent string first (async), then write to - // the shared stream (sync) - std::stringstream ss; - if(auto r = builder(ss, I)) + ex_.async([this, &I, symbolIdx = numSymbols_++](Builder& builder) { - writePage(ss.str(), symbolIdx); - } - else - { - r.error().Throw(); - } - }); + + // Output to an independent string first (async), then write to + // the shared stream (sync) + std::stringstream ss; + if(auto r = builder(ss, I)) + { + writePage(ss.str(), symbolIdx); + } + else + { + r.error().Throw(); + } + }); + } Corpus::TraverseOptions opts = {.skipInherited = std::same_as}; corpus_.traverse(opts, I, *this); } diff --git a/src/lib/Gen/hbs/VisitorHelpers.cpp b/src/lib/Gen/hbs/VisitorHelpers.cpp index b46994792..dc2ec802e 100644 --- a/src/lib/Gen/hbs/VisitorHelpers.cpp +++ b/src/lib/Gen/hbs/VisitorHelpers.cpp @@ -17,7 +17,7 @@ namespace clang::mrdocs::hbs { bool -shouldGenerate(Info const& I) +shouldGenerate(Info const& I, Config const& config) { if (I.isSpecialization()) { @@ -38,6 +38,10 @@ shouldGenerate(Info const& I) // See the requirements in ConfigOptions.json. return false; } + if (!config->showNamespaces && I.isNamespace()) + { + return false; + } return true; } @@ -91,7 +95,7 @@ findPrimarySiblingWithUrl(Corpus const& c, Info const& I, Info const& parent) for (Info const* sibling: sameNameSiblings) { if (!sibling || - !shouldGenerate(*sibling)) + !shouldGenerate(*sibling, c.config)) { continue; } @@ -135,7 +139,7 @@ findDirectPrimarySiblingWithUrl(Corpus const& c, Info const& I) // in the parent scope for which we want to generate the URL Info const* parent = c.find(I.Parent); MRDOCS_CHECK_OR(parent, nullptr); - if (!shouldGenerate(*parent)) + if (!shouldGenerate(*parent, c.config)) { parent = findPrimarySiblingWithUrl(c, *parent); MRDOCS_CHECK_OR(parent, nullptr); @@ -198,7 +202,7 @@ findResolvedPrimarySiblingWithUrl(Corpus const& c, Info const& I) // a dependency for which there's no URL, we attempt to // find the primary sibling for the parent so we take // the URL from it. - if (!shouldGenerate(*parent)) + if (!shouldGenerate(*parent, c.config)) { parent = findPrimarySiblingWithUrl(c, *parent); MRDOCS_CHECK_OR(parent, nullptr); @@ -236,7 +240,7 @@ findParentWithUrl(Corpus const& c, Info const& I) Info const* parent = c.find(I.Parent); MRDOCS_CHECK_OR(parent, nullptr); MRDOCS_CHECK_OR(!parent->isNamespace(), nullptr); - if (shouldGenerate(*parent)) + if (shouldGenerate(*parent, c.config)) { return parent; } diff --git a/src/lib/Gen/hbs/VisitorHelpers.hpp b/src/lib/Gen/hbs/VisitorHelpers.hpp index faeda0bfa..8a1341ae1 100644 --- a/src/lib/Gen/hbs/VisitorHelpers.hpp +++ b/src/lib/Gen/hbs/VisitorHelpers.hpp @@ -12,6 +12,7 @@ #define MRDOCS_LIB_GEN_HBS_VISITORHELPERS_HPP #include +#include namespace clang::mrdocs::hbs { @@ -22,7 +23,7 @@ namespace clang::mrdocs::hbs { */ MRDOCS_DECL bool -shouldGenerate(Info const& I); +shouldGenerate(Info const& I, Config const& config); /** Find an Info type whose URL we can use for the specified Info diff --git a/src/lib/Lib/Config.cpp b/src/lib/Lib/Config.cpp index dca6e966a..fd7f230f2 100644 --- a/src/lib/Lib/Config.cpp +++ b/src/lib/Lib/Config.cpp @@ -427,7 +427,23 @@ struct PublicSettingsVisitor { std::string_view valueSv(value); if (!value.empty()) { - res = files::getParentDir(value); + bool const valueIsDir = + [&value]() { + if (files::exists(value)) + { + return files::isDirectory(value); + } + std::string_view const filename = files::getFileName(value); + return filename.find('.') == std::string::npos; + }(); + if (valueIsDir) + { + res = value; + } + else + { + res = files::getParentDir(value); + } found = true; return; } diff --git a/src/lib/Lib/ConfigOptions.json b/src/lib/Lib/ConfigOptions.json index 62bfd2f1d..ef70ba59d 100644 --- a/src/lib/Lib/ConfigOptions.json +++ b/src/lib/Lib/ConfigOptions.json @@ -300,8 +300,8 @@ "brief": "Path for the tagfile", "details": "Specifies the full path (filename) where the generated tagfile should be saved. If left empty, no tagfile will be generated.", "type": "file-path", - "default": "/reference.tag.xml", - "relative-to": "", + "default": "/reference.tag.xml", + "relative-to": "", "must-exist": false, "should-exist": false }, @@ -318,6 +318,13 @@ "details": "Output an embeddable document, which excludes the header, the footer, and everything outside the body of the document. This option is useful for producing documents that can be inserted into an external template.", "type": "bool", "default": false + }, + { + "name": "show-namespaces", + "brief": "Show namespace pages in the documentation", + "details": "When set to true, MrDocs creates a page for each namespace in the documentation.", + "type": "bool", + "default": true } ] }, diff --git a/src/lib/Lib/TagfileWriter.cpp b/src/lib/Lib/TagfileWriter.cpp index 03799ac1c..7ca098e3e 100644 --- a/src/lib/Lib/TagfileWriter.cpp +++ b/src/lib/Lib/TagfileWriter.cpp @@ -19,8 +19,7 @@ #include -namespace clang { -namespace mrdocs { +namespace clang::mrdocs { //------------------------------------------------ // @@ -82,7 +81,7 @@ operator()(T const& I) { if constexpr (std::derived_from) { - if (!hbs::shouldGenerate(I)) + if (!hbs::shouldGenerate(I, corpus_.getCorpus().config)) { return; } @@ -113,14 +112,14 @@ writeNamespace( { // Check if this namespace contains only other namespaces bool onlyNamespaces = true; - corpus_->traverse(I, [&](Info const& I) + corpus_->traverse(I, [&](Info const& U) { - if (!hbs::shouldGenerate(I)) + if (!hbs::shouldGenerate(U, corpus_.getCorpus().config)) { return; } - if (I.Kind != InfoKind::Namespace) + if (U.Kind != InfoKind::Namespace) { onlyNamespaces = false; } @@ -139,7 +138,7 @@ writeNamespace( // Write the class-like members of this namespace corpus_->traverse(I, [this](U const& J) { - if (!hbs::shouldGenerate(J)) + if (!hbs::shouldGenerate(J, corpus_.getCorpus().config)) { return; } @@ -281,5 +280,4 @@ generateFileAndAnchor(T const& I) return {url.substr(0, pos), url.substr(pos + 1)}; } -} // mrdocs -} // clang \ No newline at end of file +} // clang::mrdocs \ No newline at end of file diff --git a/src/lib/Metadata/Finalizers/ReferenceFinalizer.cpp b/src/lib/Metadata/Finalizers/ReferenceFinalizer.cpp index 7be748e6b..593782477 100644 --- a/src/lib/Metadata/Finalizers/ReferenceFinalizer.cpp +++ b/src/lib/Metadata/Finalizers/ReferenceFinalizer.cpp @@ -221,6 +221,36 @@ finalize(NameInfo& name) }); } +namespace { +void +qualifiedName(InfoSet& info, Info const& I, std::string& result) +{ + if (I.Parent && + I.Parent != SymbolID::global) + { + Info const& PI = *info.find(I.Parent)->get(); + qualifiedName(info, PI, result); + result += "::"; + } + if (!I.Name.empty()) + { + result += I.Name; + } + else + { + result += ""; + } +} + +std::string +qualifiedName(InfoSet& info, Info const& I) +{ + std::string res; + qualifiedName(info, I, res); + return res; +} +} // (anonymous) + void ReferenceFinalizer:: finalize(doc::Node& node) @@ -237,10 +267,11 @@ finalize(doc::Node& node) if (!resolveReference(N) && !warned_.contains({N.string, current_->Name})) { + MRDOCS_ASSERT(current_); report::warn( - "Failed to resolve reference to '{}' from '{}'", - N.string, - current_->Name); + "{}: Failed to resolve reference to '{}'", + qualifiedName(info_, *current_), + N.string); warned_.insert({N.string, current_->Name}); } } diff --git a/test-files/golden-tests/config/show-namespaces/show-namespaces.adoc b/test-files/golden-tests/config/show-namespaces/show-namespaces.adoc new file mode 100644 index 000000000..b38ba0c50 --- /dev/null +++ b/test-files/golden-tests/config/show-namespaces/show-namespaces.adoc @@ -0,0 +1,51 @@ += Reference +:mrdocs: + +[#A-B-c] +== A::B::c + + +=== Synopsis + + +Declared in `<show‐namespaces.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +c(); +---- + +[#A-b] +== A::b + + +=== Synopsis + + +Declared in `<show‐namespaces.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +b(); +---- + +[#a] +== a + + +=== Synopsis + + +Declared in `<show‐namespaces.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +a(); +---- + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/config/show-namespaces/show-namespaces.cpp b/test-files/golden-tests/config/show-namespaces/show-namespaces.cpp new file mode 100644 index 000000000..26f5db01b --- /dev/null +++ b/test-files/golden-tests/config/show-namespaces/show-namespaces.cpp @@ -0,0 +1,9 @@ +void a(); + +namespace A { + void b(); + namespace B { + void c(); + } +} + diff --git a/test-files/golden-tests/config/show-namespaces/show-namespaces.html b/test-files/golden-tests/config/show-namespaces/show-namespaces.html new file mode 100644 index 000000000..f36e1dfbd --- /dev/null +++ b/test-files/golden-tests/config/show-namespaces/show-namespaces.html @@ -0,0 +1,62 @@ + + +Reference + + +
+

Reference

+
+
+

A::B::c

+
+
+

Synopsis

+
+Declared in <show-namespaces.cpp>
+
+
+void
+c();
+
+
+
+
+
+
+

A::b

+
+
+

Synopsis

+
+Declared in <show-namespaces.cpp>
+
+
+void
+b();
+
+
+
+
+
+
+

a

+
+
+

Synopsis

+
+Declared in <show-namespaces.cpp>
+
+
+void
+a();
+
+
+
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/config/show-namespaces/show-namespaces.xml b/test-files/golden-tests/config/show-namespaces/show-namespaces.xml new file mode 100644 index 000000000..4413d9345 --- /dev/null +++ b/test-files/golden-tests/config/show-namespaces/show-namespaces.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/test-files/golden-tests/config/show-namespaces/show-namespaces.yml b/test-files/golden-tests/config/show-namespaces/show-namespaces.yml new file mode 100644 index 000000000..0048c268a --- /dev/null +++ b/test-files/golden-tests/config/show-namespaces/show-namespaces.yml @@ -0,0 +1 @@ +show-namespaces: false \ No newline at end of file diff --git a/test-files/golden-tests/snippets/distance.adoc b/test-files/golden-tests/snippets/distance.adoc index 6954242a6..277c5d51b 100644 --- a/test-files/golden-tests/snippets/distance.adoc +++ b/test-files/golden-tests/snippets/distance.adoc @@ -1,21 +1,6 @@ = Reference :mrdocs: -[#index] -== Global namespace - - -=== Functions - -[cols=2] -|=== -| Name | Description - -| <> -| Return the distance between two points - -|=== - [#distance] == distance diff --git a/test-files/golden-tests/snippets/distance.html b/test-files/golden-tests/snippets/distance.html index a77fe0d13..2b8461575 100644 --- a/test-files/golden-tests/snippets/distance.html +++ b/test-files/golden-tests/snippets/distance.html @@ -7,25 +7,6 @@

Reference

-

Global namespace

-
-

Functions

- - - - - - - - - - -
NameDescription
distance Return the distance between two points - -
-
-
-

distance

Return the distance between two points diff --git a/test-files/golden-tests/snippets/is_prime.adoc b/test-files/golden-tests/snippets/is_prime.adoc index f433931ae..ee3e72b40 100644 --- a/test-files/golden-tests/snippets/is_prime.adoc +++ b/test-files/golden-tests/snippets/is_prime.adoc @@ -1,21 +1,6 @@ = Reference :mrdocs: -[#index] -== Global namespace - - -=== Functions - -[cols=2] -|=== -| Name | Description - -| <> -| Return true if a number is prime. - -|=== - [#is_prime] == is_prime diff --git a/test-files/golden-tests/snippets/is_prime.html b/test-files/golden-tests/snippets/is_prime.html index ae40541aa..6ca16902d 100644 --- a/test-files/golden-tests/snippets/is_prime.html +++ b/test-files/golden-tests/snippets/is_prime.html @@ -7,25 +7,6 @@

Reference

-

Global namespace

-
-

Functions

- - - - - - - - - - -
NameDescription
is_prime Return true if a number is prime. - -
-
-
-

is_prime

Return true if a number is prime. diff --git a/test-files/golden-tests/snippets/mrdocs.yml b/test-files/golden-tests/snippets/mrdocs.yml index 255a15647..11a371ee4 100644 --- a/test-files/golden-tests/snippets/mrdocs.yml +++ b/test-files/golden-tests/snippets/mrdocs.yml @@ -1,3 +1,4 @@ source-root: . input: - . +show-namespaces: false \ No newline at end of file diff --git a/test-files/golden-tests/snippets/sqrt.adoc b/test-files/golden-tests/snippets/sqrt.adoc index bdd814338..72e2d054a 100644 --- a/test-files/golden-tests/snippets/sqrt.adoc +++ b/test-files/golden-tests/snippets/sqrt.adoc @@ -1,21 +1,6 @@ = Reference :mrdocs: -[#index] -== Global namespace - - -=== Functions - -[cols=2] -|=== -| Name | Description - -| <> -| Computes the square root of an integral value. - -|=== - [#sqrt] == sqrt diff --git a/test-files/golden-tests/snippets/sqrt.html b/test-files/golden-tests/snippets/sqrt.html index 48dc20a98..986f67ba4 100644 --- a/test-files/golden-tests/snippets/sqrt.html +++ b/test-files/golden-tests/snippets/sqrt.html @@ -7,25 +7,6 @@

Reference

-

Global namespace

-
-

Functions

- - - - - - - - - - -
NameDescription
sqrt Computes the square root of an integral value. - -
-
-
-

sqrt

Computes the square root of an integral value. diff --git a/test-files/golden-tests/snippets/terminate.adoc b/test-files/golden-tests/snippets/terminate.adoc index baeb1edd6..5f77b1912 100644 --- a/test-files/golden-tests/snippets/terminate.adoc +++ b/test-files/golden-tests/snippets/terminate.adoc @@ -1,21 +1,6 @@ = Reference :mrdocs: -[#index] -== Global namespace - - -=== Functions - -[cols=2] -|=== -| Name | Description - -| <> -| Exit the program. - -|=== - [#terminate] == terminate diff --git a/test-files/golden-tests/snippets/terminate.html b/test-files/golden-tests/snippets/terminate.html index c85b32e33..405e9df2b 100644 --- a/test-files/golden-tests/snippets/terminate.html +++ b/test-files/golden-tests/snippets/terminate.html @@ -7,25 +7,6 @@

Reference

-

Global namespace

-
-

Functions

- - - - - - - - - - -
NameDescription
terminate Exit the program. - -
-
-
-

terminate

Exit the program. diff --git a/util/generate-config-info.py b/util/generate-config-info.py index 7f48934cd..db1e15fb9 100644 --- a/util/generate-config-info.py +++ b/util/generate-config-info.py @@ -170,6 +170,7 @@ def validate_and_normalize_option(option, flat_options): # If option is unique directory option of some form if flat_option['type'] in ['path', 'dir-path', 'file-path']: reference_directories.append(f'<{flat_option["name"]}>') + reference_directories.append(f'<{flat_option["name"]}-dir>') if option['relative-to'] not in reference_directories: raise ValueError(f'Option "{option["name"]}" has an invalid value for "relative-to"') default_paths = option['default']