mirror of
https://github.com/pantor/inja.git
synced 2026-02-17 09:03:58 +00:00
add test and readme
This commit is contained in:
12
README.md
12
README.md
@@ -228,6 +228,18 @@ render("{{ isArray(guests) }}", data); // "true"
|
||||
// Implemented type checks: isArray, isBoolean, isFloat, isInteger, isNumber, isObject, isString,
|
||||
```
|
||||
|
||||
### Whitespace Control
|
||||
|
||||
In the default configuration, no whitespace is removed while rendering the file. To support a more readable template style, you can configure the environment to control whitespaces before and after a statement automatically. While enabling `set_trim` removes the first newline after a statement, `set_lstrip` strips tabs and spaces from the beginning of a line to the start of a block.
|
||||
|
||||
```c++
|
||||
Environment env;
|
||||
env.set_trim(true);
|
||||
env.set_lstrip(true);
|
||||
```
|
||||
|
||||
With both `trim` and `lstrip` enabled, you can put statements on their own lines.
|
||||
|
||||
### Callbacks
|
||||
|
||||
You can create your own and more complex functions with callbacks.
|
||||
|
||||
@@ -1370,6 +1370,9 @@ struct LexerConfig {
|
||||
std::string comment_close {"#}"};
|
||||
std::string open_chars {"#{"};
|
||||
|
||||
bool trim_blocks {false};
|
||||
bool lstrip_blocks {false};
|
||||
|
||||
void update_open_chars() {
|
||||
open_chars = "";
|
||||
if (open_chars.find(line_statement[0]) == std::string::npos) {
|
||||
@@ -1793,12 +1796,15 @@ class Lexer {
|
||||
|
||||
// try to match one of the opening sequences, and get the close
|
||||
nonstd::string_view open_str = m_in.substr(m_pos);
|
||||
bool must_lstrip = false;
|
||||
if (inja::string_view::starts_with(open_str, m_config.expression_open)) {
|
||||
m_state = State::ExpressionStart;
|
||||
} else if (inja::string_view::starts_with(open_str, m_config.statement_open)) {
|
||||
m_state = State::StatementStart;
|
||||
must_lstrip = m_config.lstrip_blocks;
|
||||
} else if (inja::string_view::starts_with(open_str, m_config.comment_open)) {
|
||||
m_state = State::CommentStart;
|
||||
must_lstrip = m_config.lstrip_blocks;
|
||||
} else if ((m_pos == 0 || m_in[m_pos - 1] == '\n') &&
|
||||
inja::string_view::starts_with(open_str, m_config.line_statement)) {
|
||||
m_state = State::LineStart;
|
||||
@@ -1806,8 +1812,13 @@ class Lexer {
|
||||
m_pos += 1; // wasn't actually an opening sequence
|
||||
goto again;
|
||||
}
|
||||
if (m_pos == m_tok_start) goto again; // don't generate empty token
|
||||
return make_token(Token::Kind::Text);
|
||||
|
||||
nonstd::string_view text = string_view::slice(m_in, m_tok_start, m_pos);
|
||||
if (must_lstrip)
|
||||
text = clear_final_line_if_whitespace(text);
|
||||
|
||||
if (text.empty()) goto again; // don't generate empty token
|
||||
return Token(Token::Kind::Text, text);
|
||||
}
|
||||
case State::ExpressionStart: {
|
||||
m_state = State::ExpressionBody;
|
||||
@@ -1834,7 +1845,7 @@ class Lexer {
|
||||
case State::LineBody:
|
||||
return scan_body("\n", Token::Kind::LineStatementClose);
|
||||
case State::StatementBody:
|
||||
return scan_body(m_config.statement_close, Token::Kind::StatementClose);
|
||||
return scan_body(m_config.statement_close, Token::Kind::StatementClose, m_config.trim_blocks);
|
||||
case State::CommentBody: {
|
||||
// fast-scan to comment close
|
||||
size_t end = m_in.substr(m_pos).find(m_config.comment_close);
|
||||
@@ -1845,7 +1856,10 @@ class Lexer {
|
||||
// return the entire comment in the close token
|
||||
m_state = State::Text;
|
||||
m_pos += end + m_config.comment_close.size();
|
||||
return make_token(Token::Kind::CommentClose);
|
||||
Token tok = make_token(Token::Kind::CommentClose);
|
||||
if (m_config.trim_blocks)
|
||||
skip_newline();
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1853,7 +1867,7 @@ class Lexer {
|
||||
const LexerConfig& get_config() const { return m_config; }
|
||||
|
||||
private:
|
||||
Token scan_body(nonstd::string_view close, Token::Kind closeKind) {
|
||||
Token scan_body(nonstd::string_view close, Token::Kind closeKind, bool trim = false) {
|
||||
again:
|
||||
// skip whitespace (except for \n as it might be a close)
|
||||
if (m_tok_start >= m_in.size()) return make_token(Token::Kind::Eof);
|
||||
@@ -1867,7 +1881,10 @@ class Lexer {
|
||||
if (inja::string_view::starts_with(m_in.substr(m_tok_start), close)) {
|
||||
m_state = State::Text;
|
||||
m_pos = m_tok_start + close.size();
|
||||
return make_token(closeKind);
|
||||
Token tok = make_token(closeKind);
|
||||
if (trim)
|
||||
skip_newline();
|
||||
return tok;
|
||||
}
|
||||
|
||||
// skip \n
|
||||
@@ -1988,6 +2005,34 @@ class Lexer {
|
||||
Token make_token(Token::Kind kind) const {
|
||||
return Token(kind, string_view::slice(m_in, m_tok_start, m_pos));
|
||||
}
|
||||
|
||||
void skip_newline() {
|
||||
if (m_pos < m_in.size()) {
|
||||
char ch = m_in[m_pos];
|
||||
if (ch == '\n')
|
||||
m_pos += 1;
|
||||
else if (ch == '\r') {
|
||||
m_pos += 1;
|
||||
if (m_pos < m_in.size() && m_in[m_pos] == '\n')
|
||||
m_pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text)
|
||||
{
|
||||
nonstd::string_view result = text;
|
||||
while (!result.empty()) {
|
||||
char ch = result.back();
|
||||
if (ch == ' ' || ch == '\t')
|
||||
result.remove_suffix(1);
|
||||
else if (ch == '\n' || ch == '\r')
|
||||
break;
|
||||
else
|
||||
return text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -3261,6 +3306,16 @@ class Environment {
|
||||
m_impl->lexer_config.update_open_chars();
|
||||
}
|
||||
|
||||
/// Sets whether to remove the first newline after a block
|
||||
void set_trim_blocks(bool trim_blocks) {
|
||||
m_impl->lexer_config.trim_blocks = trim_blocks;
|
||||
}
|
||||
|
||||
/// Sets whether to strip the spaces and tabs from the start of a line to a block
|
||||
void set_lstrip_blocks(bool lstrip_blocks) {
|
||||
m_impl->lexer_config.lstrip_blocks = lstrip_blocks;
|
||||
}
|
||||
|
||||
/// Sets the element notation syntax
|
||||
void set_element_notation(ElementNotation notation) {
|
||||
m_impl->parser_config.notation = notation;
|
||||
|
||||
4
test/data/nested-whitespace/data.json
Executable file
4
test/data/nested-whitespace/data.json
Executable file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"xarray": [0, 1],
|
||||
"yarray": [0, 1, 2]
|
||||
}
|
||||
6
test/data/nested-whitespace/result.txt
Executable file
6
test/data/nested-whitespace/result.txt
Executable file
@@ -0,0 +1,6 @@
|
||||
0-0
|
||||
0-1
|
||||
0-2
|
||||
1-0
|
||||
1-1
|
||||
1-2
|
||||
3
test/data/nested-whitespace/template.txt
Executable file
3
test/data/nested-whitespace/template.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
{% for x in xarray %}{% for y in yarray %}
|
||||
{{x}}-{{y}}
|
||||
{% endfor %}{% endfor %}
|
||||
@@ -41,6 +41,18 @@ TEST_CASE("complete-files") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("complete-files-whitespace-control") {
|
||||
inja::Environment env {test_file_directory};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
for (std::string test_name : {"nested-whitespace"}) {
|
||||
SECTION(test_name) {
|
||||
CHECK( env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt") );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("global-path") {
|
||||
inja::Environment env {test_file_directory, "./"};
|
||||
inja::Environment env_result {"./"};
|
||||
|
||||
@@ -7,8 +7,9 @@ using json = nlohmann::json;
|
||||
|
||||
TEST_CASE("dot-to-pointer") {
|
||||
std::string buffer;
|
||||
CHECK( inja::convert_dot_to_json_pointer("person.names.surname", buffer) == "/person/names/surname" );
|
||||
CHECK( inja::convert_dot_to_json_pointer("test", buffer) == "/test" );
|
||||
CHECK( inja::convert_dot_to_json_pointer("guests.2", buffer) == "/guests/2" );
|
||||
CHECK( inja::convert_dot_to_json_pointer("person.names.surname", buffer) == "/person/names/surname" );
|
||||
}
|
||||
|
||||
TEST_CASE("types") {
|
||||
@@ -172,7 +173,6 @@ TEST_CASE("functions") {
|
||||
SECTION("length") {
|
||||
CHECK( env.render("{{ length(names) }}", data) == "4" ); // Length of array
|
||||
CHECK( env.render("{{ length(name) }}", data) == "5" ); // Length of string
|
||||
|
||||
// CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is number" );
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user