diff --git a/include/mrdocs/Config.hpp b/include/mrdocs/Config.hpp index 0ea57aaa1..51cd0fac4 100644 --- a/include/mrdocs/Config.hpp +++ b/include/mrdocs/Config.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -68,8 +69,11 @@ class MRDOCS_DECL /** Full path to the working directory - The working directory is used to calculate - full paths from relative paths. + The working directory is the directory + of the mrdocs.yml file. + + It is used to calculate full paths + from relative paths. This string will always be native style and have a trailing directory separator. @@ -124,6 +128,14 @@ class MRDOCS_DECL */ virtual Settings const& settings() const noexcept = 0; + /** Return a DOM object representing the configuration keys. + + The object is invalidated when the configuration + is moved or destroyed. + + */ + virtual dom::Object const& object() const = 0; + /// @copydoc settings() constexpr Settings const* operator->() const noexcept diff --git a/share/mrdocs/addons/generator/asciidoc/partials/source.adoc.hbs b/share/mrdocs/addons/generator/asciidoc/partials/source.adoc.hbs index 0d83a5c3f..fb3301050 100644 --- a/share/mrdocs/addons/generator/asciidoc/partials/source.adoc.hbs +++ b/share/mrdocs/addons/generator/asciidoc/partials/source.adoc.hbs @@ -1 +1 @@ -Declared in header `<{{#unless @root.config.baseURL}}{{dcl.file}}{{else}}{{@root.config.baseURL}}{{dcl.file}}#L{{dcl.line}}[{{dcl.file}},window=blank_]{{/unless}}>` +Declared in header `<{{#unless @root.config.base-url}}{{dcl.file}}{{else}}{{@root.config.base-url}}{{dcl.file}}#L{{dcl.line}}[{{dcl.file}},window=blank_]{{/unless}}>` diff --git a/src/lib/Gen/adoc/Builder.cpp b/src/lib/Gen/adoc/Builder.cpp index aee896c88..cb68ea0ad 100644 --- a/src/lib/Gen/adoc/Builder.cpp +++ b/src/lib/Gen/adoc/Builder.cpp @@ -179,104 +179,6 @@ getRelPrefix(std::size_t depth) return rel_prefix; } -class ConfigObjectImpl : public dom::ObjectImpl -{ - Config const* config_; - -public: - ~ConfigObjectImpl() override = default; - - ConfigObjectImpl(Config const& config) - : config_(&config) - {} - - char const* type_key() const noexcept override - { - return "ConfigObject"; - } - - dom::Value get(std::string_view key) const override - { - if (key == "multiPage") return (*config_)->multiPage; - if (key == "generate") return (*config_)->generate; - if (key == "workingDir") return (*config_)->workingDir; - auto* config_impl = dynamic_cast(config_); - if (config_impl) - { - if (key == "baseURL") return (*config_impl)->baseURL; - if (key == "inaccessibleBases") return (*config_impl)->inaccessibleBases; - if (key == "inaccessibleMembers") return (*config_impl)->inaccessibleMembers; - if (key == "anonymousNamespaces") return (*config_impl)->anonymousNamespaces; - if (key == "ignoreFailures") return (*config_impl)->ignoreFailures; - if (key == "defines") { - dom::Array defines; - for (auto& define: (*config_impl)->defines) - { - defines.emplace_back(define); - } - return defines; - } - } - return {}; - } - - void set(dom::String key, dom::Value value) override - { - // Cannot set values in the config object from templates - } - - bool - visit(std::function fn) const override - { - if (!fn("multiPage", (*config_)->multiPage)) { return false; }; - if (!fn("generate", (*config_)->generate)) { return false; }; - if (!fn("workingDir", (*config_)->workingDir)) { return false; }; - auto* config_impl = dynamic_cast(config_); - if (config_impl) - { - if (!fn("baseURL", (*config_impl)->baseURL)) { return false; }; - if (!fn("inaccessibleBases", (*config_impl)->inaccessibleBases)) { return false; }; - if (!fn("inaccessibleMembers", (*config_impl)->inaccessibleMembers)) { return false; }; - if (!fn("anonymousNamespaces", (*config_impl)->anonymousNamespaces)) { return false; }; - if (!fn("ignoreFailures", (*config_impl)->ignoreFailures)) { return false; }; - dom::Array defines; - for (auto& define: (*config_impl)->defines) - { - defines.emplace_back(define); - } - if (!fn("defines", defines)) { return false; }; - } - return true; - } - - /** Return the number of properties in the object. - */ - std::size_t size() const override { - return 9; - }; - - /** Determine if a key exists. - */ - bool exists(std::string_view key) const override - { - if (key == "multiPage") return true; - if (key == "generate") return true; - if (key == "workingDir") return true; - auto* config_impl = dynamic_cast(config_); - if (config_impl) - { - if (key == "baseURL") return true; - if (key == "inaccessibleBases") return true; - if (key == "inaccessibleMembers") return true; - if (key == "anonymousNamespaces") return true; - if (key == "ignoreFailures") return true; - if (key == "defines") return true; - } - return false; - } -}; - - dom::Value Builder:: createContext( @@ -287,8 +189,7 @@ createContext( domCorpus.get(I.id)); props.emplace_back("relfileprefix", getRelPrefix(I.Namespace.size())); - props.emplace_back("config", - dom::newObject(domCorpus->config)); + props.emplace_back("config", domCorpus->config.object()); return dom::Object(std::move(props)); } diff --git a/src/lib/Lib/Config.cpp b/src/lib/Lib/Config.cpp index 3a7dc971d..9c6ac4085 100644 --- a/src/lib/Lib/Config.cpp +++ b/src/lib/Lib/Config.cpp @@ -9,14 +9,10 @@ // Official repository: https://github.com/cppalliance/mrdocs // -#include "lib/Lib/ConfigImpl.hpp" -#include "lib/Support/Path.hpp" +#include "mrdocs/Config.hpp" #include #include #include -#include -#include -#include #include diff --git a/src/lib/Lib/ConfigImpl.cpp b/src/lib/Lib/ConfigImpl.cpp index a0ea5f692..57198c676 100644 --- a/src/lib/Lib/ConfigImpl.cpp +++ b/src/lib/Lib/ConfigImpl.cpp @@ -139,6 +139,9 @@ parseSymbolFilter( root.mergePattern(parts, excluded); } +dom::Object +toDomObject(std::string_view configYaml); + } // (anon) ConfigImpl:: @@ -174,6 +177,7 @@ ConfigImpl( // Config strings settings_.configYaml = configYaml; settings_.extraYaml = extraYaml; + configObj_ = toDomObject(settings_.configYaml); // Parse the YAML strings YamlReporter reporter; @@ -338,5 +342,142 @@ loadConfigFile( threadPool); } +namespace { +dom::Value +toDom(llvm::yaml::Node* Value); + +dom::Object +toDomObject(llvm::yaml::MappingNode* Object) +{ + dom::Object obj; + for (auto &Pair : *Object) + { + auto *KeyString = dyn_cast(Pair.getKey()); + if (!KeyString) { continue; } + SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + llvm::yaml::Node *Value = Pair.getValue(); + if (!Value) { + obj.set(KeyValue, dom::Kind::Undefined); + continue; + } + dom::Value value = toDom(Value); + obj.set(KeyValue, value); + } + return obj; +} + +dom::Array +toDomArray(llvm::yaml::SequenceNode* Array) +{ + dom::Array arr; + for (auto &Node : *Array) + { + dom::Value value = toDom(&Node); + arr.push_back(value); + } + return arr; +} + +dom::Value +toDomScalar(llvm::yaml::ScalarNode* Scalar) +{ + SmallString<10> ScalarStorage; + StringRef ScalarValue = Scalar->getValue(ScalarStorage); + StringRef RawValue = Scalar->getRawValue(); + bool const isEscaped = RawValue.size() != ScalarValue.size(); + if (isEscaped) + { + return ScalarValue; + } + std::int64_t integer; + auto res = std::from_chars( + ScalarValue.begin(), + ScalarValue.end(), + integer); + if (res.ec == std::errc()) + { + return integer; + } + bool const isBool = ScalarValue == "true" || ScalarValue == "false"; + if (isBool) + { + return ScalarValue == "true"; + } + bool const isNull = ScalarValue == "null"; + if (isNull) + { + return nullptr; + } + return ScalarValue; +} + +dom::Value +toDom(llvm::yaml::Node* Value) +{ + auto *ValueObject = dyn_cast(Value); + if (ValueObject) + { + return toDomObject(ValueObject); + } + auto *ValueArray = dyn_cast(Value); + if (ValueArray) + { + return toDomArray(ValueArray); + } + auto *ValueString = dyn_cast(Value); + if (ValueString) + { + return toDomScalar(ValueString); + } + return nullptr; +} + +/* Convert a YAML string to a DOM object. + + YAML forbids tab characters to use as indentation so + only some JSON files are valid YAML. + + Also instead of providing built-in support for + types such as `bool` or `int`, YAML uses strings + for everything, which the specification defines + as "scalar" values. + + When converting a scalar to a DOM value, only + escaped strings are preserved as strings. + Unescaped strings are converted to numbers + if possible, and then to booleans if possible. + This is done to preserve compatibility with + JSON, allow the user to specify scalars as + boolean or integer values, match the original + intent of the author, and for scalar values + to interoperate with other handlebars templates. + + */ +dom::Object +toDomObject(std::string_view yaml) +{ + llvm::SourceMgr SM; + llvm::yaml::Stream YAMLStream_(yaml, SM); + llvm::yaml::document_iterator I = YAMLStream_.begin(); + if (I == YAMLStream_.end()) + { + return {}; + } + llvm::yaml::Node *Root = I->getRoot(); + auto *Object = dyn_cast(Root); + if (!Object) + { + return {}; + } + return toDomObject(Object); +} +} // (anon) + +dom::Object const& +ConfigImpl::object() const { + return configObj_; +} + } // mrdocs } // clang diff --git a/src/lib/Lib/ConfigImpl.hpp b/src/lib/Lib/ConfigImpl.hpp index 538187309..bb91676df 100644 --- a/src/lib/Lib/ConfigImpl.hpp +++ b/src/lib/Lib/ConfigImpl.hpp @@ -24,6 +24,14 @@ namespace clang { namespace mrdocs { +/* Private configuration implementation. + + This class is used internally to hold the + configuration settings. It is not part of + the public API and plugins should not use + it. + + */ class ConfigImpl : public Config , public std::enable_shared_from_this @@ -142,11 +150,16 @@ class ConfigImpl return &settings_; } + /// @copydoc Config::object() + dom::Object const& + object() const override; + private: SettingsImpl settings_; ThreadPool& threadPool_; llvm::SmallString<0> outputPath_; std::vector inputFileIncludes_; + dom::Object configObj_; friend class Config; friend class Options; diff --git a/src/tool/GenerateAction.cpp b/src/tool/GenerateAction.cpp index edfc8a061..5627fbe1b 100644 --- a/src/tool/GenerateAction.cpp +++ b/src/tool/GenerateAction.cpp @@ -25,6 +25,11 @@ namespace mrdocs { Expected DoGenerateAction() { + // -------------------------------------------------------------- + // + // Load configuration + // + // -------------------------------------------------------------- // Get additional YAML settings from command line std::string extraYaml; { @@ -49,7 +54,11 @@ DoGenerateAction() config = std::move(configFile); } - // Get the generator + // -------------------------------------------------------------- + // + // Load generator + // + // -------------------------------------------------------------- MRDOCS_TRY( Generator const& generator, getGenerators().find(config->settings().generate), @@ -57,43 +66,53 @@ DoGenerateAction() "the Generator \"{}\" was not found", config->settings().generate)); - // Load the compilation database + // -------------------------------------------------------------- + // + // Load the compilation database file + // + // -------------------------------------------------------------- MRDOCS_CHECK(toolArgs.inputPaths, "The compilation database path argument is missing"); MRDOCS_CHECK(toolArgs.inputPaths.size() == 1, formatError( "got {} input paths where 1 was expected", toolArgs.inputPaths.size())); auto compilationsPath = files::normalizePath(toolArgs.inputPaths.front()); + MRDOCS_TRY(compilationsPath, files::makeAbsolute(compilationsPath)); std::string errorMessage; MRDOCS_TRY_MSG( - auto& jsonCompilations, + auto& compileCommands, tooling::JSONCompilationDatabase::loadFromFile( compilationsPath, errorMessage, tooling::JSONCommandLineSyntax::AutoDetect), std::move(errorMessage)); - // Calculate the working directory - MRDOCS_TRY(auto absPath, files::makeAbsolute(compilationsPath)); - auto workingDir = files::getParentDir(absPath); + // Custom compilation database that converts relative paths to absolute + auto compileCommandsDir = files::getParentDir(compilationsPath); + AbsoluteCompilationDatabase absCompileCommands( + compileCommandsDir, compileCommands, config); - // Normalize outputPath + // Normalize outputPath path MRDOCS_CHECK(toolArgs.outputPath, "The output path argument is missing"); toolArgs.outputPath = files::normalizePath( files::makeAbsolute(toolArgs.outputPath, (*config)->workingDir)); - // Convert relative paths to absolute - AbsoluteCompilationDatabase compilations( - workingDir, jsonCompilations, config); - - // Run the tool: this can take a while + // -------------------------------------------------------------- + // + // Build corpus + // + // -------------------------------------------------------------- MRDOCS_TRY( auto corpus, CorpusImpl::build( - report::Level::info, config, compilations)); + report::Level::info, config, absCompileCommands)); - // Run the generator + // -------------------------------------------------------------- + // + // Generate docs + // + // -------------------------------------------------------------- report::info("Generating docs\n"); MRDOCS_TRY(generator.build(toolArgs.outputPath.getValue(), *corpus)); return {};