pantor / inja Goto Github PK
View Code? Open in Web Editor NEWA Template Engine for Modern C++
Home Page: https://pantor.github.io/inja/
License: MIT License
A Template Engine for Modern C++
Home Page: https://pantor.github.io/inja/
License: MIT License
Currently, when json element is empty and we're trying to iterate it application throws the error.
I believe that in this situations loop should be skipped but not stopped.
Fix is pretty easy, it's necessary to test for null in eval_expression
function
template<typename T = json>
T eval_expression(const Parsed::ElementExpression& element, const json& data) {
const json var = eval_function(element, data);
if (var.is_null()) // << test if element is empty
return T(); // << return empty
try {
return var.get<T>();
} catch (json::type_error& e) {
inja_throw("json_error", e.what());
throw;
}
}
The use case is that I'm filling database data to the json context and generating some stats from such data. But when array of data is empty the generator throw error instead of generating empty data.
in microsoft vs , use short characters ,the string will use char buff array,vs string implement like this and thatwill make move construct has problem,the string_view in Bytecode will have problem
Assuming I have a file foo.txt with this single line:
{{ bar }}
And I have a data.json file in the same directory like so:
{ "bar": "example" }
Why would a binary compiled from this code:
int main() {
inja::Environment env;
env.write( "./foo.txt", "./data.json", "./bar.txt");
}
in the same directory produce:
libc++abi.dylib: terminating with uncaught exception of type std::runtime_error: [inja.exception.render_error] variable 'bar' not found Abort trap: 6
Also how can I use the .hpp files with defined body of the functions.
If I include your files in multiple .cpp files I will get the redefinition of these functions in final liking.
How did you avoid this?
Hello!
This is a really lovely library. So I created a package for it so folks can install it with Conan. I'd love your feedback!
for (const auto e : regexes) {
to
for (const auto &e : regexes) {
Maybe it would be great to be able to store parsed template in some binary format for some kind of caching.
Based on my first measures it seems that a lot of time is spent in parser:
total : 2942.00ms
Initialization: 45.70ms
Parser: 2882.51ms
Render:: 13.61ms
Do you think it would be possible to serialize template file and back?
use inja v2 version ,the include method has a problem, the include template file result has messy code
I want to do this:
{% for key in keys %}
{{key}} = {{dict.key}}
{% endfor %}
i.e. for each key in a keys array, use it to look up a value in an dictionary. Haven't been able to figure out how to do what with existing notation. Is it possible, or can it be added?
Hi,
I just found very strange bug which I'm not able to fix.
With this template application halted on invalid pointers and a lot of asserts.
{% if 1 >= 18 %}…{% endif %}
{% for v in vals %}
{% if v > 0%}+{%else%}-{%endif%}
{% endfor %}
Here is test snippet:
nlohmann::json data;
data["vals"] = { "1","2" };
InjaStringType res = inja::render("{% if 1 >= 18 %}…{% endif %}{% for v in vals %}{% if v > 0%}+{%else%}-{%endif%}{% endfor %}", data);
Error:
Debug Assertion Failed!
Program: C:\WINDOWS\SYSTEM32\MSVCP140D.dll
File: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility
Line: 1115
Expression: invalid null pointer
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
A better regex for LineStatement would be:
- {Parsed::Delimiter::LineStatement, Regex{"(?:^|\\n)## *(.+?) *(?:\\n|$)"}},
+ {Parsed::Delimiter::LineStatement, Regex{"[^\\r\\n]* *## *(.+?) *(?:\\n|$)"}},
This will allow you to:
so this template:
## for uniform in uniforms
cbuffer {{ uniform.name }}
{
## for constant in uniform.constants
{{ constant.hlslType }} {{ constant.name }};
## endfor
};
## endfor
cbuffer Light
{
float3 direction;
float4 color;
matrix shadowViewMatrix;
matrix shadowProjMatrix;
float invShadowMapSize;
float multiplier;
};
cbuffer Material
{
float4 diffuseFactor;
float3 specularFactor;
float glossiness;
float3 specularFactor;
matrix dummy;
};
During my development I find following bug. When I wrongly defined second condition as if
instead of else if
, parser completely crashed. Here is snippet:
{% if context.filterBy == "groupId" %}xxxx
{% if context.filterBy == "ip" %}xxx
{% else %} xxxx
{% endif %}
Error
Expression: invalid null pointer
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
Hi,
just updating my code to use your latest callbacks and I have a question. Wouldn't be bettter to pass json data as const reference instead of copying it on every callback?
This means use
std::map<std::string, std::function<json(Parsed::Arguments, const json&)>> map_callbacks;
instead of
std::map<std::string, std::function<json(Parsed::Arguments, json)>> map_callbacks;
and
env.add_callback("double", 1, [&env](inja::Parsed::Arguments args, const json &data) {..}
instead
env.add_callback("double", 1, [&env](inja::Parsed::Arguments args, json data) {..}
and here
void add_callback(std::string name, int number_arguments, std::function<json(Parsed::Arguments, const json&)> callback) {
instead
void add_callback(std::string name, int number_arguments, std::function<json(Parsed::Arguments, json)> callback) {...
Ludek
Hi,
would be great if add_callback() method allow adding functions with "up-to" number of params, not necessary with the equal number.
After that, it would be possible to do following logic:
inja::Environment env = inja::Environment();
env.add_callback("test", 2, [&env](inja::Parsed::Arguments args, const nlohmann::json &data) {
if (env.get_arguments_count() == 2)
{
int n1 = env.get_argument<int>(args, 0, data);
int n2 = env.get_argument<int>(args, 1, data);
return n1 + n2;
}
else if (env.get_arguments_count() == 1)
{
int n1 = env.get_argument<int>(args, 0, data);
return n1;
}
else if (env.get_arguments_count() == 0)
{
return 42;
}
});
nlohmann::json data;
InjaStringType res = env.render("{{ test(1,2) }}", data);
TEST_CHECK_EQUAL(res, "3");
res = env.render("{{ test(2) }}", data);
TEST_CHECK_EQUAL(res, "2");
res = env.render("{{ test() }}", data);
TEST_CHECK_EQUAL(res, "42");
The default ctor for Environment sets global_path to "./" ( environment.hpp:40 ) . Which breaks absolute paths in calls to render_file(..) or rather parse_template(..).
Breaking absolute paths is quite unexpected behavior for a default, especially because it is not documented.
I suggest to change the default to "" (empty string) this still allows relative paths and does not break absolute paths.
for (auto element : included_template.parsed_template.children) {
to
for (auto &element : included_template.parsed_template.children) {
when I call function with following syntax:
test("hello.world")
I get this error
Error [json.exception.out_of_range.403] key '"hello' not found
It's because Inja is trying to evaluate passed string as Json pointer. Is there any way, how to pass string as text and not pointer?
Hi,
my tests just reveal strange error on OSX (I have several multi platform app on Win/Lin/OSX).
I have these two tests to ensure Inja is working fine in my apps:
AX_TEST_CASE(inja_callback7)
{
inja::Environment env = inja::Environment();
env.add_callback("fce", 0, [&env](inja::Parsed::Arguments /*args*/, const axJson &/*data*/) {return 42; });
nlohmann::json data;
InjaStringType res = env.render("{{ fce }}", data);
TEST_CHECK_EQUAL(res, "42");
}
AX_TEST_CASE(inja_callback8)
{
inja::Environment env = inja::Environment();
env.add_callback("fce", 0, [&env](inja::Parsed::Arguments /*args*/, const axJson &/*data*/) {return 42;});
nlohmann::json data;
InjaStringType res = env.render("{{ fce() }}", data);
TEST_CHECK_EQUAL(res, "42");
}
First test (callback7) works on any platform without any issues. Second test (callback8) works on Win32/64 as same as Linux 32/64 but on OSX this test ends with Segmentation fault: 11 without any additional info.
The only difference is in () for zero argument callback.
Do you have any idea what can be wrong? I will try to debug it but my primary development environment is Windows so I'm not sure if I will be able to detect the issue by myself.
When endfor
(not sure about other functions) is followed by spaces in ## format inja parser crashed.
## for f in functions
.....
## endfor <EMPTY SPACES HERE>
Debug Assertion Failed!
Program: C:\WINDOWS\SYSTEM32\MSVCP140D.dll
File: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility
Line: 1115
Expression: invalid null pointer
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
In function Match::position()
Would it be possible to add the ability to manage whether we want a new line after a condition statement. I know Jinja has this capability via the trim blocks options or by changing the {%....%} pattern to {%.... - %}. This would tremendously help with the readability of the generated result.
Currently
//Start
{% if length(classes)>0 %}
{% for cl in classes %}
{% if cl.is_viable %}
//dosomething
{% endif %}
{% endfor %}
{% endif %}
//End
Becomes this
//Start
//dosomething
//End
And it would be nice if it could be this
//Start
//dosomething
//End
The below example fails with a runtime exception.
If I switch back to the default '{{' and '}}' it works.
$ clang++ -std=c++17 -I. main.cpp
$ ./a.out
This is a simple template
## for l in list
terminate called after throwing an instance of 'std::runtime_error'
what(): [inja.exception.render_error] variable 'l.name' not found
Aborted (core dumped)
#include "inja/inja.hpp"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
json data = R"DELIM( {
"list" : [
{
"name" : "n1",
"value" : "v1"
},
{
"name" : "n2",
"value" : "v2"
}
] }
)DELIM"_json;
std::string template_string = R"DELIM(
This is a simple template
## for l in list
<%l.name%> : <%l.value%>
##endfor
)DELIM";
int main() {
inja::Environment env;
env.set_expression("<%", "%>");
auto tmpl = env.parse(template_string);
env.render_to(std::cout, tmpl, data);
return 0;
}
Using inja v2.0.1
clang
clang version 7.0.1 (tags/RELEASE_701/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
I believe there is a bug in empty space handling inside and after ## commands
for example:
##for i in items
<space><space>value
##endfor
generates value directly from the start of the line, not intended via spaces:
value
Also, in case there are newlines or empty characters after ## command, newlines and spaces are ignored too:
##for i in items
value
##endfor
\n
\r\n
<space><space>\n
##for i in items
value2
#endfor
generates :
value
value2
I'm trying to find a way how to test "it's not last item" but without success
{% for i in items %}
{% if not(is_last) %} .... doesn't work, throws "[inja.exception.render_error] unknown function in renderer: "
{% if is_last ==0 %} doesn't work too, throws "[inja.exception.render_error] variable '/is_last==0' not found"
{% endfor %}
For now I introduced new variable not_last to solve it.
data_loop["not_last"] = (i != list.size() - 1);
But I'm curious what is the correct way. Thanks
Because Inja now offers several useful function it would be great to add also functions to convert string to number.
I currently have these functions named double(x)
and integer(x)
with following test
nlohmann::json data;
data["val1"] = "5";
data["val2"] = "-5";
InjaStringType res = inja::render("{%if integer(val1) >= 0%}yes{%else%}no{%endif%}", data);
TEST_CHECK_EQUAL(res, "yes");
res = inja::render("{%if integer(val1) < 0%}yes{%else%}no{%endif%}", data);
TEST_CHECK_EQUAL(res, "no");
res = inja::render("{%if double(val2) >= 0%}yes{%else%}no{%endif%}", data);
TEST_CHECK_EQUAL(res, "no");
res = inja::render("{%if double(val2) < 0%}yes{%else%}no{%endif%}", data);
TEST_CHECK_EQUAL(res, "yes");
Are you interested in such PR?
in function std::string render(Template temp, const json& data) {
for (auto element: temp.parsed_template.children) {
to
for (const auto &element: temp.parsed_template.children) {
for (auto branch: element_condition->children) {
to
for (const auto& branch: element_condition->children) {
In function used for match regexes, there is missing reference so every time expression is evaluated whole map is created again.
Also in for loop, there is missing reference, so this means another copy
template<typename T, typename S>
inline MatchType<T> match(const std::string& input, std::map<T, Regex, S> regexes) { << Missing reference
MatchType<T> match;
for (const auto e : regexes) { << Missing reference
if (std::regex_match(input.cbegin(), input.cend(), match, e.second)) {
match.set_type(e.first);
match.set_regex(e.second);
return match;
}
}
return match;
}
This will be much faster:
template<typename T, typename S>
inline MatchType<T> match(const std::string& input, const std::map<T, Regex, S> ®exes) {
MatchType<T> match;
for (const auto &e : regexes) {
if (std::regex_match(input.cbegin(), input.cend(), match, e.second)) {
match.set_type(e.first);
match.set_regex(e.second);
return match;
}
}
return match;
}
OK: {% for name in _ONE_SAPCE_HERE_ names %}
fail : {% for name _TWO_SPACE_HERE_ in names %}
is this a bug or a design?
In the readme here: https://github.com/pantor/inja/blame/master/README.md#L83
We define Environment
with two path but Environment
allow only one path in it's constructor
I created a callback function "setValue":
``
env->add_callback("setValue", 2, [this](Parsed::Arguments args, json x) {
std::string key = env->get_argument<std::string>(args, 0, x);
std::string value = env->get_argument<std::string>(args, 1, x);
std::cout << "setValue key: " << key << std::endl;
std::cout << "setValue value: " << value << std::endl;
this->values[key] = value;
return "";
});
``
I am trying to call it like this:
{{ setValue("need_comma", ", ") }}
However, I keep getting an error:
terminate called after throwing an instance of 'std::runtime_error' what(): [inja.exception.render_error] variable '/"need_comma", "' not found
How can I pass two string arguments to a callback function?
Hi there,
As first let me thanks for great work you did on Inja. It's the best of templating libraries for C++ I tried. I 'm just integrating your library to my projects and have a question.
I have a system where I need to generate several templates and then merge it into one result. Because it's not stored in FileSystem I'm doing that in memory.
Unfortunately, I didn't find an alternative for {% include "footer.html" %}
for in-memory templates.
Would be great to have some callback where I can call {% include "identifier" } which get data from the app and put it back to generated result.
Currently, I doing that via variables {{ templates.xxx }}
but it doesn't seem to be an optimal way because such variables can be very large.
Is there any way how to do that better? And if not, would you be so kind and add this feature?
Thanks!
Is there a way to determine the type of an element? I'd like to know if something is a bool, numeric, string, object or array.
When nlohmann folder and inja.hpp are present in the directory, there are dependency issues in regards to where the namespaces and classes reside. So the example in this repository is no longer valid. Could you please provide a valid example to include your library. I've had problems while trying to get it to work.
such as the template file contents are <div>{{no_value}}</div>
, I want the result is <div>{{no_value}}</div>
,What shall I do?
## for sidename, sideObject in sides
## for elementName, elementObject in sideObject
## endfor
## endfor
Should that work?
I get the error when I try to nest in the key value loop.
terminate called after throwing an instance of 'nlohmann::detail::type_error'
what(): [json.exception.type_error.304] cannot use at() with object
Hi @pantor , thanks for the great library!
I am trying it for a ~100 lines template, and I am experiencing errors such as:
what(): Unknown function in renderer.
or
what(): Unknown loop statement.
Do you think there is a way to enrich this error with some information that would help to debug the problem, for example the line number in the original template file? Thanks in advance.
I find the bug form class Template:
Template& operator=(Template&& oth) {
bytecodes = std::move(oth.bytecodes);
content = std::move(oth.content);
return *this;
}
the
struct Bytecode {
...
std::string_view str;
...
}
str references Template::content
, when move the Template::content
, Template::content
will be changed, the Bytecode::str
is invalid.
You can reference basic_string_view#Notes
Why does render_to(...)
take std::stringstream&
as a parameter? It should be std::ostream&
.
Also it would be nice if it were exposed as a global function with a default environment like render()
is.
I have a list that looks like this
[ { "x" : 12, "y": 8 }, { "x" : 5, "y": 19 }, { "x" : 2, "y": 0 }]
and I want to get the value of x in the last item.
I tried this
{{ last(data).x }}
or
{{ last(data).y }}
.
My program terminates with an runtime_error. What is wrong with it?
Hi,
just figured out that data are passed to renderer as new copy. This cause a lot of overhead.
It's sufficint to update it to
std::string render(Template temp, const json& data)
the same for
Environment::render_template(const Template& temp, const json& data)
Environment::render(const std::string& input, const json& data)
Environment::render_file(const std::string& filename, const json& data)
inline std::string render(const std::string& input, const json& data) {
Do you want me to prepare PR ?
Building error if using VS2015+string-view-lite.
How to use vs2015 to be successful??
or Whether to use vs2017 can solve the problem??
Hi, would you be interested in having your library added to the hunter package management? I could file a PR to make this repository comply with the format required by https://github.com/ruslo/hunter . You can see my current changes here master...jowr:hunter
can regist a user define function to inja,and the template can invoke the function.
In case I have json with variable structure, currently there is no way how to check if node exists before accessing it.
For example:
{
{"includes" : [.... ] }
{"data": [....] }
}
where element "includes" is optional. In such situations there is no way how to correctly generate template.
{% for i in includes %} .... {% endfor %}
because in eval_function()
in Parsed::Function::ReadJson
the exception will be thrown. There are two options how to solve it:
change inja_throw("render_error", "variable '" + element.command + "' not found");
to return json();
and together with my previous proposal #27 inja correctly skip such statement.
introduce new function for test elements.
{% if exists(includes) %}{%for i in includes%}....{%endfor%}{%endif%}.
or better:
{%for i in default(includes,null)%}....{%endfor%}
but second one also needs change proposed here #27 to be able to proceed null
correctly
Currently it's not possible to test last/first elements if we have two (or more) nested loops . Would be better to have it as function which enable us to ask for specific item:
{% for i in array1 %}
{% for i2 in i.array2 %}
.....
{% if is_last(i2) %} ... {%endif %}
{% if is_last(i2) %} ... {%endif %}
...
{% endfor %}
{% endfor %}
Have you planned to add Inja to vcpkg system ?
https://github.com/Microsoft/vcpkg
or can I add it ?
It is this a feature?How to get length of string?
Hi, I'm not sure which update did it but now following test throwing exception
inja::Environment env = inja::Environment();
env.add_callback("xxx", 0, [&env](inja::Parsed::Arguments args, const json &data) {return 42;});
nlohmann::json data;
InjaStringType res = env.render("{{ xxx() }}", data);
TEST_CHECK_EQUAL(res, "42");
Callbacks with 1 and more params works ok.
Problem is probably in parsing empty braces "()" because Parsed::ElementExpression& element
passed to json eval_function(const Parsed::ElementExpression& element, const json& data) {
has invalid elem.function value
.
Hi,
I installed the library using vcpkg (VS2017) and some the examples result in the program crashing.
Here is some example code:
Environment env;
//Template temp = env.parse_template("./count.html");
json data;
// Reply with data
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< env.render("{% for i in range(4) %}{{ loop.index1 }}{% endfor %}", data);
It also doesn't with the following:
{{ guests.1 }}
where guests is an array:
data["guests"] = {"a", "b", "c"};
However, the following does work:
{{ guests }}
This code causes a crash, throwing a variable /loop.index1 not found
render_error exception.
Not sure if I am doing something wrong?
Thanks,
James.
When I try to render 200 data with inja, the inja processing takes a long time. It takes about 9 seconds. I hope inja can improve the performance problem.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.