diff --git a/docs/mrdocs.schema.json b/docs/mrdocs.schema.json index 8424e59b80..a8f9bd3ab3 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 0d07db0406..f5c7724a6f 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 255a15647f..1348831029 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 0f9617b8cb..18edf08b7b 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 88985a1016..519a0f2b80 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 66d89a4c50..e53d8ee75b 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 b46994792c..dc2ec802e8 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 faeda0bfae..8a1341ae18 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 dca6e966af..fd7f230f2f 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 62bfd2f1dc..ef70ba59d4 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 03799ac1ce..7ca098e3ea 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 7be748e6b5..5937824777 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 0000000000..b38ba0c504 --- /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 0000000000..26f5db01b3 --- /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 0000000000..f36e1dfbd3 --- /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 0000000000..4413d9345e --- /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 0000000000..0048c268a6 --- /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 6954242a6e..277c5d51b8 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 a77fe0d137..2b8461575d 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 f433931aef..ee3e72b406 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 ae40541aa9..6ca16902d7 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 255a15647f..11a371ee40 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 bdd8143380..72e2d054ab 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 48dc20a980..986f67ba4e 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 baeb1edd69..5f77b19129 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 c85b32e33b..405e9df2b2 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 7f48934cd7..db1e15fb98 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']