Skip to content

Commit

Permalink
feat: config YAML as dom::Object
Browse files Browse the repository at this point in the history
  • Loading branch information
alandefreitas committed Dec 13, 2023
1 parent 9c0071a commit 0faf7a9
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 122 deletions.
16 changes: 14 additions & 2 deletions include/mrdocs/Config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <mrdocs/Platform.hpp>
#include <mrdocs/Support/Error.hpp>
#include <mrdocs/Dom/Object.hpp>
#include <functional>
#include <memory>
#include <string>
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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}}>`
101 changes: 1 addition & 100 deletions src/lib/Gen/adoc/Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConfigImpl const*>(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<bool(dom::String, dom::Value)> 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<ConfigImpl const*>(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<ConfigImpl const*>(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(
Expand All @@ -287,8 +189,7 @@ createContext(
domCorpus.get(I.id));
props.emplace_back("relfileprefix",
getRelPrefix(I.Namespace.size()));
props.emplace_back("config",
dom::newObject<ConfigObjectImpl>(domCorpus->config));
props.emplace_back("config", domCorpus->config.object());
return dom::Object(std::move(props));
}

Expand Down
6 changes: 1 addition & 5 deletions src/lib/Lib/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <mrdocs/Support/Error.hpp>
#include <llvm/Config/llvm-config.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/Path.h>
#include <llvm/Support/YAMLParser.h>
#include <llvm/Support/YAMLTraits.h>

#include <version>

Expand Down
141 changes: 141 additions & 0 deletions src/lib/Lib/ConfigImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ parseSymbolFilter(
root.mergePattern(parts, excluded);
}

dom::Object
toDomObject(std::string_view configYaml);

} // (anon)

ConfigImpl::
Expand Down Expand Up @@ -174,6 +177,7 @@ ConfigImpl(
// Config strings
settings_.configYaml = configYaml;
settings_.extraYaml = extraYaml;
configObj_ = toDomObject(settings_.configYaml);

// Parse the YAML strings
YamlReporter reporter;
Expand Down Expand Up @@ -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<llvm::yaml::ScalarNode>(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<llvm::yaml::MappingNode>(Value);
if (ValueObject)
{
return toDomObject(ValueObject);
}
auto *ValueArray = dyn_cast<llvm::yaml::SequenceNode>(Value);
if (ValueArray)
{
return toDomArray(ValueArray);
}
auto *ValueString = dyn_cast<llvm::yaml::ScalarNode>(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<llvm::yaml::MappingNode>(Root);
if (!Object)
{
return {};
}
return toDomObject(Object);
}
} // (anon)

dom::Object const&
ConfigImpl::object() const {
return configObj_;
}

} // mrdocs
} // clang
13 changes: 13 additions & 0 deletions src/lib/Lib/ConfigImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConfigImpl>
Expand Down Expand Up @@ -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<std::string> inputFileIncludes_;
dom::Object configObj_;

friend class Config;
friend class Options;
Expand Down
Loading

0 comments on commit 0faf7a9

Please sign in to comment.