Giter VIP home page Giter VIP logo

inja's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inja's Issues

Better json::null handling for loops

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.

Visual Studio short characters problem

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

External json files

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

Idea: Ability to store parsed templates

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?

Indirection for object lookup?

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?

Critical bug causing complete crash

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.

Improvement for LineStatement

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:

  1. indent the line statement for nested cases
  2. do not "eat" the newline from the previous line

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;
};


Parser crash when invalid {% if %} is used

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.

Use const json& rather than "json" in std::function<json(Parsed::Arguments, json)>> ?

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

Extend callbacks to be able to proceed variable number of params

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");

Fix default for Environment global_path

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.

How to pass string with "." to callback function?

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?

Segmentation fault: 11 on OS X

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.

Parser crashes when there is empty space after ## command

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()

New line handling after condition statements.

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

env.set_expression causes template errors

The below example fails with a runtime exception.

If I switch back to the default '{{' and '}}' it works.

Error message

$ 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)

Code

#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;
}

Versions

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

Strange space handling in ## commands

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

How to test for not last item?

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

Functions to convert string to number

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?

std::string render optimization

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) {

Missign reference when passing map of regexes

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> &regexes) {
	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;
}

Problem with user defined callback

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?

Callback for in-memory includes?

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!

Type of element

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.

Integration Problem

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.

How to Original output

such as the template file contents are <div>{{no_value}}</div>, I want the result is <div>{{no_value}}</div>,What shall I do?

Nested loops

## 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

Informative error messages: Add line numbers

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.

When use the std::string_view don't std::move the std::string. #BUG

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

render_to() takes stringstream

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.

Get property of last item

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?

Optimization: Pass json data to renderer as reference

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 ?

Build problem By vs2015

Building error if using VS2015+string-view-lite.
How to use vs2015 to be successful??
or Whether to use vs2017 can solve the problem??

Loops over not-existing elements

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:

  1. 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.

  2. 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

Proposal - change is_last / is_first variables to functions

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 %}

Callbacks with zero parameters stopped working

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 .

Update vcpkg to latest release v2.1

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.

Performance problem of inja

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.