Skip to content

Commit 63ac382

Browse files
committed
refactor: MrDox uses C++ handlebars
This PR refactors the generators to use the C++ implementation of handlebars. Javascript helpers are loaded with duktape.
1 parent d7c0c4e commit 63ac382

File tree

8 files changed

+352
-212
lines changed

8 files changed

+352
-212
lines changed

include/mrdox/Support/JavaScript.hpp

+46-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <mrdox/Support/Error.hpp>
1717
#include <cstdlib>
1818
#include <string_view>
19+
#include <span>
1920

2021
namespace clang {
2122
namespace mrdox {
@@ -42,7 +43,9 @@ enum class Type
4243
boolean,
4344
number,
4445
string,
45-
object
46+
object,
47+
function,
48+
array
4649
};
4750

4851
//------------------------------------------------
@@ -120,12 +123,18 @@ class Scope
120123
MRDOX_DECL
121124
~Scope();
122125

123-
/** Run a script.
126+
/** Compile and run a script.
124127
*/
125128
MRDOX_DECL
126129
Error
127130
script(std::string_view jsCode);
128131

132+
/** Compile a script and push results to stack.
133+
*/
134+
MRDOX_DECL
135+
Expected<Value>
136+
compile(std::string_view jsCode);
137+
129138
/** Return the global object.
130139
*/
131140
MRDOX_DECL
@@ -170,10 +179,12 @@ class Value
170179
bool isString() const noexcept;
171180
bool isArray() const noexcept;
172181
bool isObject() const noexcept;
182+
bool isFunction() const noexcept;
173183

174184
std::string getString() const;
185+
dom::Value getDom() const;
175186

176-
void setlog();
187+
void setlog();
177188

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

198+
/** Call a function with variadic arguments.
199+
*/
200+
template<class... Args>
201+
Expected<Value>
202+
apply(std::span<dom::Value> args) const
203+
{
204+
return callImpl(args);
205+
}
206+
187207
/** Call a function.
188208
*/
189209
template<class... Args>
@@ -205,12 +225,30 @@ void setlog();
205225
{ dom::Value(std::forward<Args>(args))... });
206226
}
207227

228+
/** Set a key.
229+
*/
230+
MRDOX_DECL
231+
void
232+
set(
233+
std::string_view key,
234+
Value value) const;
235+
236+
/** Get a key.
237+
*/
238+
MRDOX_DECL
239+
Value
240+
get(std::string_view key) const;
241+
208242
private:
209243
MRDOX_DECL
210244
Expected<Value>
211245
callImpl(
212246
std::initializer_list<dom::Value> args) const;
213247

248+
MRDOX_DECL
249+
Expected<Value>
250+
callImpl(std::span<dom::Value> args) const;
251+
214252
MRDOX_DECL
215253
Expected<Value>
216254
callPropImpl(
@@ -248,6 +286,11 @@ inline bool Value::isObject() const noexcept
248286
return type() == Type::object;
249287
}
250288

289+
inline bool Value::isFunction() const noexcept
290+
{
291+
return type() == Type::function;
292+
}
293+
251294
} // js
252295
} // mrdox
253296
} // clang
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function add(a, b) {
2+
return a + b;
3+
}

src/lib/-HTML/Builder.cpp

+101-103
Original file line numberDiff line numberDiff line change
@@ -38,112 +38,105 @@ Builder(
3838

3939
Config const& config = corpus_.config;
4040

41-
js::Scope scope(ctx_);
42-
43-
scope.script(files::getFileText(
44-
files::appendPath(
45-
config->addonsDir, "js", "handlebars.js")
46-
).value()).maybeThrow();
47-
auto Handlebars = scope.getGlobal("Handlebars").value();
48-
49-
// VFALCO refactor this
50-
Handlebars.setlog();
51-
52-
// load templates
53-
#if 0
54-
err = forEachFile(options_.template_dir,
55-
[&](std::string_view fileName)
41+
// load partials
42+
std::string partialsPath = files::appendPath(
43+
config->addonsDir, "generator", "html", "partials");
44+
forEachFile(partialsPath,
45+
[&](std::string_view pathName) -> Error
46+
{
47+
constexpr std::string_view ext = ".html.hbs";
48+
if (!pathName.ends_with(ext))
5649
{
57-
if(! fileName.ends_with(".html.hbs"))
58-
return Error::success();
5950
return Error::success();
60-
});
61-
#endif
62-
63-
Error err;
64-
65-
// load partials
66-
forEachFile(
67-
files::appendPath(config->addonsDir,
68-
"generator", "html", "partials"),
69-
[&](std::string_view pathName)
51+
}
52+
auto name = files::getFileName(pathName);
53+
name.remove_suffix(ext.size());
54+
auto text = files::getFileText(pathName);
55+
if (!text)
7056
{
71-
constexpr std::string_view ext = ".html.hbs";
72-
if(! pathName.ends_with(ext))
73-
return Error::success();
74-
auto name = files::getFileName(pathName);
75-
name.remove_suffix(ext.size());
76-
auto text = files::getFileText(pathName);
77-
if(! text)
78-
return text.error();
79-
return Handlebars.callProp(
80-
"registerPartial", name, *text)
81-
.error_or(Error::success());
82-
}).maybeThrow();
57+
return text.error();
58+
}
59+
hbs_.registerPartial(name, *text);
60+
return Error::success();
61+
}).maybeThrow();
8362

8463
// load helpers
85-
#if 0
86-
err = forEachFile(
87-
files::appendPath(config->addonsDir,
88-
"generator", "js", "helpers"),
89-
[&](std::string_view pathName)
64+
js::Scope scope(ctx_);
65+
std::string helpersPath = files::appendPath(
66+
config->addonsDir, "generator", "asciidoc", "helpers");
67+
forEachFile(helpersPath,
68+
[&](std::string_view pathName)
69+
{
70+
// Register JS helper function in the global object
71+
constexpr std::string_view ext = ".js";
72+
if (!pathName.ends_with(ext))
9073
{
91-
constexpr std::string_view ext = ".js";
92-
if(! pathName.ends_with(ext))
93-
return Error::success();
94-
auto name = files::getFileName(pathName);
95-
name.remove_suffix(ext.size());
96-
//Handlebars.callProp("registerHelper", name, *text);
97-
auto err = ctx_.scriptFromFile(pathName);
9874
return Error::success();
99-
}).maybeThrow();
100-
#endif
101-
102-
scope.script(fmt::format(
103-
R"(Handlebars.registerHelper(
104-
'is_multipage', function()
105-
{{
106-
return {};
107-
}});
108-
)", config->multiPage)).maybeThrow();
109-
110-
scope.script(R"(
111-
Handlebars.registerHelper(
112-
'to_string', function(context, depth)
113-
{
114-
return JSON.stringify(context, null, 2);
115-
});
116-
117-
Handlebars.registerHelper(
118-
'eq', function(a, b)
75+
}
76+
auto name = files::getFileName(pathName);
77+
name.remove_suffix(ext.size());
78+
auto text = files::getFileText(pathName);
79+
if (!text)
11980
{
120-
return a === b;
121-
});
122-
123-
Handlebars.registerHelper(
124-
'neq', function(a, b)
81+
return text.error();
82+
}
83+
auto JSFn = scope.compile(*text);
84+
if (!JSFn)
12585
{
126-
return a !== b;
127-
});
128-
129-
Handlebars.registerHelper(
130-
'not', function(a)
131-
{
132-
return ! a;
133-
});
134-
135-
Handlebars.registerHelper(
136-
'or', function(a, b)
86+
return JSFn.error();
87+
}
88+
scope.getGlobalObject().set(name, *JSFn);
89+
90+
// Register C++ helper that retrieves the helper
91+
// from the global object, converts the arguments,
92+
// and invokes the JS function.
93+
hbs_.registerHelper(name, dom::makeVariadicInvocable(
94+
[this, name=std::string(name)](
95+
dom::Array const& args) -> Expected<dom::Value>
13796
{
138-
return a || b;
139-
});
140-
141-
Handlebars.registerHelper(
142-
'and', function(a, b)
143-
{
144-
return a && b;
145-
});
146-
)").maybeThrow();
97+
// Get function from global scope
98+
js::Scope scope(ctx_);
99+
js::Value fn = scope.getGlobalObject().get(name);
100+
if (fn.isUndefined())
101+
{
102+
return Unexpected(Error("helper not found"));
103+
}
104+
if (!fn.isFunction())
105+
{
106+
return Unexpected(Error("helper is not a function"));
107+
}
108+
109+
// Call function
110+
std::vector<dom::Value> arg_span;
111+
arg_span.reserve(args.size());
112+
for (auto const& arg : args)
113+
{
114+
arg_span.push_back(arg);
115+
}
116+
auto result = fn.apply(arg_span);
117+
if (!result)
118+
{
119+
return dom::Kind::Undefined;
120+
}
121+
122+
// Convert result to dom::Value
123+
return result->getDom();
124+
}));
125+
return Error::success();
126+
}).maybeThrow();
127+
hbs_.registerHelper(
128+
"is_multipage",
129+
dom::makeInvocable([res = config->multiPage]() -> Expected<dom::Value> {
130+
return res;
131+
}));
132+
helpers::registerAntoraHelpers(hbs_);
133+
hbs_.registerHelper("neq", dom::makeVariadicInvocable(helpers::ne_fn));
134+
hbs_.registerHelper(
135+
"to_string",
136+
dom::makeInvocable(
137+
[](dom::Value const& context) -> Expected<dom::Value> {
138+
return dom::JSON::stringify(context);
139+
}));
147140
}
148141

149142
//------------------------------------------------
@@ -157,18 +150,23 @@ callTemplate(
157150
Config const& config = corpus_.config;
158151

159152
js::Scope scope(ctx_);
153+
154+
160155
auto Handlebars = scope.getGlobal("Handlebars");
161156
auto layoutDir = files::appendPath(config->addonsDir,
162157
"generator", "html", "layouts");
163158
auto pathName = files::appendPath(layoutDir, name);
164159
MRDOX_TRY(auto fileText, files::getFileText(pathName));
165-
dom::Object options;
166-
options.set("allowProtoPropertiesByDefault", true);
167-
// VFALCO This makes Proxy objects stop working
168-
//options.set("allowProtoMethodsByDefault", true);
169-
MRDOX_TRY(auto templateFn, Handlebars->callProp("compile", fileText, options));
170-
MRDOX_TRY(auto result, templateFn.call(context, options));
171-
return result.getString();
160+
HandlebarsOptions options;
161+
options.noEscape = true;
162+
163+
Expected<std::string, HandlebarsError> exp =
164+
hbs_.try_render(fileText, context, options);
165+
if (!exp)
166+
{
167+
return Unexpected(Error(exp.error().what()));
168+
}
169+
return *exp;
172170
}
173171

174172
Expected<std::string>

src/lib/-HTML/Builder.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "lib/Support/Radix.hpp"
1616
#include <mrdox/Metadata/DomMetadata.hpp>
1717
#include <mrdox/Support/Error.hpp>
18+
#include <mrdox/Support/Handlebars.hpp>
1819
#include <mrdox/Support/JavaScript.hpp>
1920
#include <ostream>
2021

@@ -33,6 +34,7 @@ class Builder
3334
Corpus const& corpus_;
3435
Options options_;
3536
js::Context ctx_;
37+
Handlebars hbs_;
3638

3739
public:
3840
Builder(

src/lib/-adoc/AdocGenerator.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ buildOne(
103103
});
104104
errors = ex->wait();
105105
if(! errors.empty())
106-
return Error(errors);
106+
return {errors};
107107

108108
SinglePageVisitor visitor(*ex, corpus, os);
109109
visitor(corpus.globalNamespace());
110110
errors = ex->wait();
111111
if(! errors.empty())
112-
return Error(errors);
112+
return {errors};
113113

114114
ex->async(
115115
[&os](Builder& builder)
@@ -119,7 +119,7 @@ buildOne(
119119
});
120120
errors = ex->wait();
121121
if(! errors.empty())
122-
return Error(errors);
122+
return {errors};
123123

124124
return Error::success();
125125
}

0 commit comments

Comments
 (0)