Skip to content

Commit

Permalink
refactor: MrDox uses C++ handlebars
Browse files Browse the repository at this point in the history
This PR refactors the generators to use the C++ implementation of handlebars. Javascript helpers are loaded with duktape.
  • Loading branch information
alandefreitas committed Oct 13, 2023
1 parent d7c0c4e commit 63ac382
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 212 deletions.
49 changes: 46 additions & 3 deletions include/mrdox/Support/JavaScript.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <mrdox/Support/Error.hpp>
#include <cstdlib>
#include <string_view>
#include <span>

namespace clang {
namespace mrdox {
Expand All @@ -42,7 +43,9 @@ enum class Type
boolean,
number,
string,
object
object,
function,
array
};

//------------------------------------------------
Expand Down Expand Up @@ -120,12 +123,18 @@ class Scope
MRDOX_DECL
~Scope();

/** Run a script.
/** Compile and run a script.
*/
MRDOX_DECL
Error
script(std::string_view jsCode);

/** Compile a script and push results to stack.
*/
MRDOX_DECL
Expected<Value>
compile(std::string_view jsCode);

/** Return the global object.
*/
MRDOX_DECL
Expand Down Expand Up @@ -170,10 +179,12 @@ class Value
bool isString() const noexcept;
bool isArray() const noexcept;
bool isObject() const noexcept;
bool isFunction() const noexcept;

std::string getString() const;
dom::Value getDom() const;

void setlog();
void setlog();

/** Call a function.
*/
Expand All @@ -184,6 +195,15 @@ void setlog();
return callImpl({ dom::Value(std::forward<Args>(args))... });
}

/** Call a function with variadic arguments.
*/
template<class... Args>
Expected<Value>
apply(std::span<dom::Value> args) const
{
return callImpl(args);
}

/** Call a function.
*/
template<class... Args>
Expand All @@ -205,12 +225,30 @@ void setlog();
{ dom::Value(std::forward<Args>(args))... });
}

/** Set a key.
*/
MRDOX_DECL
void
set(
std::string_view key,
Value value) const;

/** Get a key.
*/
MRDOX_DECL
Value
get(std::string_view key) const;

private:
MRDOX_DECL
Expected<Value>
callImpl(
std::initializer_list<dom::Value> args) const;

MRDOX_DECL
Expected<Value>
callImpl(std::span<dom::Value> args) const;

MRDOX_DECL
Expected<Value>
callPropImpl(
Expand Down Expand Up @@ -248,6 +286,11 @@ inline bool Value::isObject() const noexcept
return type() == Type::object;
}

inline bool Value::isFunction() const noexcept
{
return type() == Type::function;
}

} // js
} // mrdox
} // clang
Expand Down
3 changes: 3 additions & 0 deletions share/mrdox/addons/generator/asciidoc/helpers/add.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function add(a, b) {
return a + b;
}
204 changes: 101 additions & 103 deletions src/lib/-HTML/Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,112 +38,105 @@ Builder(

Config const& config = corpus_.config;

js::Scope scope(ctx_);

scope.script(files::getFileText(
files::appendPath(
config->addonsDir, "js", "handlebars.js")
).value()).maybeThrow();
auto Handlebars = scope.getGlobal("Handlebars").value();

// VFALCO refactor this
Handlebars.setlog();

// load templates
#if 0
err = forEachFile(options_.template_dir,
[&](std::string_view fileName)
// load partials
std::string partialsPath = files::appendPath(
config->addonsDir, "generator", "html", "partials");
forEachFile(partialsPath,
[&](std::string_view pathName) -> Error
{
constexpr std::string_view ext = ".html.hbs";
if (!pathName.ends_with(ext))
{
if(! fileName.ends_with(".html.hbs"))
return Error::success();
return Error::success();
});
#endif

Error err;

// load partials
forEachFile(
files::appendPath(config->addonsDir,
"generator", "html", "partials"),
[&](std::string_view pathName)
}
auto name = files::getFileName(pathName);
name.remove_suffix(ext.size());
auto text = files::getFileText(pathName);
if (!text)
{
constexpr std::string_view ext = ".html.hbs";
if(! pathName.ends_with(ext))
return Error::success();
auto name = files::getFileName(pathName);
name.remove_suffix(ext.size());
auto text = files::getFileText(pathName);
if(! text)
return text.error();
return Handlebars.callProp(
"registerPartial", name, *text)
.error_or(Error::success());
}).maybeThrow();
return text.error();
}
hbs_.registerPartial(name, *text);
return Error::success();
}).maybeThrow();

// load helpers
#if 0
err = forEachFile(
files::appendPath(config->addonsDir,
"generator", "js", "helpers"),
[&](std::string_view pathName)
js::Scope scope(ctx_);
std::string helpersPath = files::appendPath(
config->addonsDir, "generator", "asciidoc", "helpers");
forEachFile(helpersPath,
[&](std::string_view pathName)
{
// Register JS helper function in the global object
constexpr std::string_view ext = ".js";
if (!pathName.ends_with(ext))
{
constexpr std::string_view ext = ".js";
if(! pathName.ends_with(ext))
return Error::success();
auto name = files::getFileName(pathName);
name.remove_suffix(ext.size());
//Handlebars.callProp("registerHelper", name, *text);
auto err = ctx_.scriptFromFile(pathName);
return Error::success();
}).maybeThrow();
#endif

scope.script(fmt::format(
R"(Handlebars.registerHelper(
'is_multipage', function()
{{
return {};
}});
)", config->multiPage)).maybeThrow();

scope.script(R"(
Handlebars.registerHelper(
'to_string', function(context, depth)
{
return JSON.stringify(context, null, 2);
});
Handlebars.registerHelper(
'eq', function(a, b)
}
auto name = files::getFileName(pathName);
name.remove_suffix(ext.size());
auto text = files::getFileText(pathName);
if (!text)
{
return a === b;
});
Handlebars.registerHelper(
'neq', function(a, b)
return text.error();
}
auto JSFn = scope.compile(*text);
if (!JSFn)
{
return a !== b;
});
Handlebars.registerHelper(
'not', function(a)
{
return ! a;
});
Handlebars.registerHelper(
'or', function(a, b)
return JSFn.error();
}
scope.getGlobalObject().set(name, *JSFn);

// Register C++ helper that retrieves the helper
// from the global object, converts the arguments,
// and invokes the JS function.
hbs_.registerHelper(name, dom::makeVariadicInvocable(
[this, name=std::string(name)](
dom::Array const& args) -> Expected<dom::Value>
{
return a || b;
});
Handlebars.registerHelper(
'and', function(a, b)
{
return a && b;
});
)").maybeThrow();
// Get function from global scope
js::Scope scope(ctx_);
js::Value fn = scope.getGlobalObject().get(name);
if (fn.isUndefined())
{
return Unexpected(Error("helper not found"));
}
if (!fn.isFunction())
{
return Unexpected(Error("helper is not a function"));
}

// Call function
std::vector<dom::Value> arg_span;
arg_span.reserve(args.size());
for (auto const& arg : args)
{
arg_span.push_back(arg);
}
auto result = fn.apply(arg_span);
if (!result)
{
return dom::Kind::Undefined;
}

// Convert result to dom::Value
return result->getDom();
}));
return Error::success();
}).maybeThrow();
hbs_.registerHelper(
"is_multipage",
dom::makeInvocable([res = config->multiPage]() -> Expected<dom::Value> {
return res;
}));
helpers::registerAntoraHelpers(hbs_);
hbs_.registerHelper("neq", dom::makeVariadicInvocable(helpers::ne_fn));
hbs_.registerHelper(
"to_string",
dom::makeInvocable(
[](dom::Value const& context) -> Expected<dom::Value> {
return dom::JSON::stringify(context);
}));
}

//------------------------------------------------
Expand All @@ -157,18 +150,23 @@ callTemplate(
Config const& config = corpus_.config;

js::Scope scope(ctx_);


auto Handlebars = scope.getGlobal("Handlebars");
auto layoutDir = files::appendPath(config->addonsDir,
"generator", "html", "layouts");
auto pathName = files::appendPath(layoutDir, name);
MRDOX_TRY(auto fileText, files::getFileText(pathName));
dom::Object options;
options.set("allowProtoPropertiesByDefault", true);
// VFALCO This makes Proxy objects stop working
//options.set("allowProtoMethodsByDefault", true);
MRDOX_TRY(auto templateFn, Handlebars->callProp("compile", fileText, options));
MRDOX_TRY(auto result, templateFn.call(context, options));
return result.getString();
HandlebarsOptions options;
options.noEscape = true;

Expected<std::string, HandlebarsError> exp =
hbs_.try_render(fileText, context, options);
if (!exp)
{
return Unexpected(Error(exp.error().what()));
}
return *exp;
}

Expected<std::string>
Expand Down
2 changes: 2 additions & 0 deletions src/lib/-HTML/Builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "lib/Support/Radix.hpp"
#include <mrdox/Metadata/DomMetadata.hpp>
#include <mrdox/Support/Error.hpp>
#include <mrdox/Support/Handlebars.hpp>
#include <mrdox/Support/JavaScript.hpp>
#include <ostream>

Expand All @@ -33,6 +34,7 @@ class Builder
Corpus const& corpus_;
Options options_;
js::Context ctx_;
Handlebars hbs_;

public:
Builder(
Expand Down
6 changes: 3 additions & 3 deletions src/lib/-adoc/AdocGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ buildOne(
});
errors = ex->wait();
if(! errors.empty())
return Error(errors);
return {errors};

SinglePageVisitor visitor(*ex, corpus, os);
visitor(corpus.globalNamespace());
errors = ex->wait();
if(! errors.empty())
return Error(errors);
return {errors};

ex->async(
[&os](Builder& builder)
Expand All @@ -119,7 +119,7 @@ buildOne(
});
errors = ex->wait();
if(! errors.empty())
return Error(errors);
return {errors};

return Error::success();
}
Expand Down
Loading

0 comments on commit 63ac382

Please sign in to comment.