Skip to content

Commit

Permalink
Improve grammar to fix whitespace gobbling
Browse files Browse the repository at this point in the history
  • Loading branch information
hiddewie committed Apr 27, 2024
1 parent 1e14d41 commit 9ba01c3
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 30 deletions.
30 changes: 7 additions & 23 deletions src/evaluate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,21 +115,19 @@ fn parse_expression(value: &Value, expression: &mut Pairs<Rule>) -> Result<Value
}


fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<(String, bool), TemplateRenderError> {
fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<String, TemplateRenderError> {
let mut result = String::new();

let mut inner_rules = record.into_inner();
let expression = inner_rules.next().unwrap();

let mut gobble = false;
match expression.as_rule() {
Rule::if_elif_else_template => {
let mut done = false;
let mut valid = false;
for if_inner in expression.into_inner() {
match if_inner.as_rule() {
Rule::if_template => {
gobble = true;
valid = false;

let mut if_inner_expression = if_inner.into_inner();
Expand Down Expand Up @@ -178,10 +176,7 @@ fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<(String, bool),
}
Rule::template => {
if valid {
let (evaluation, gobble_inner) = evaluate_template(&data, if_inner)?;
if gobble_inner {
result = result.trim_end_matches(&[' ', '\t']).to_string();
}
let evaluation = evaluate_template(&data, if_inner)?;
result.push_str(evaluation.as_str())
}
}
Expand All @@ -199,7 +194,6 @@ fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<(String, bool),
for for_inner in expression.into_inner() {
match for_inner.as_rule() {
Rule::for_template => {
gobble = true;
let mut for_inner_expression = for_inner.into_inner();
iterable_name = for_inner_expression.next().unwrap().as_str();
let for_iterable = parse_expression(&data, &mut for_inner_expression.next().unwrap().into_inner()).unwrap();
Expand Down Expand Up @@ -253,13 +247,9 @@ fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<(String, bool),
}
_ => iterable.clone(),
};
let (template_result, gobble_inner) = evaluate_template(&context_value, for_inner.clone())?;
let template_result = evaluate_template(&context_value, for_inner.clone())?;
iterable_result.replace_with(|current_result|
if gobble_inner {
format!("{}{}", current_result.as_str().trim_end_matches(&[' ', '\t']), template_result)
} else {
format!("{}{}", current_result, template_result)
}
format!("{}{}", current_result, template_result)
);
}
}
Expand Down Expand Up @@ -299,11 +289,8 @@ fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<(String, bool),
}
_ => value.clone(),
};
let (template_result, gobble_inner) = evaluate_template(&context_value, for_inner.clone())?;
let template_result = evaluate_template(&context_value, for_inner.clone())?;
result.push_str(template_result.as_str());
if gobble_inner {
result = result.trim_end_matches(&[' ', '\t']).to_string();
}
}
_ => unreachable!(),
}
Expand All @@ -324,7 +311,7 @@ fn evaluate_template(data: &Value, record: Pair<Rule>) -> Result<(String, bool),
_ => unreachable!(),
}

return Ok((result, gobble));
return Ok(result);
}

pub fn evaluate_file(data: &Value, file: Pair<Rule>) -> Result<String, TemplateRenderError> {
Expand All @@ -333,10 +320,7 @@ pub fn evaluate_file(data: &Value, file: Pair<Rule>) -> Result<String, TemplateR
for record in file.into_inner() {
match record.as_rule() {
Rule::template => {
let (evaluation, gobble_inner) = evaluate_template(&data, record)?;
if gobble_inner {
result = result.trim_end_matches(&[' ', '\t']).to_string();
}
let evaluation = evaluate_template(&data, record)?;
result.push_str(evaluation.as_str())
}
Rule::character => {
Expand Down
14 changes: 7 additions & 7 deletions src/grammar/template.pest
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ literal = { boolean | number | string | null | array | dictionary }
function_call = { function ~ whitespace* ~ ("(" ~ whitespace* ~ (expression ~ whitespace* ~ ("," ~ whitespace* ~ expression)*)? ~ whitespace* ~ ")")? }
expression = { !keywords ~ (literal | properties) ~ whitespace* ~ ("|" ~ whitespace* ~ function_call)* }
expression_template = { start_marker ~ whitespace* ~ expression ~ whitespace* ~ end_marker }
if_template = { start_marker ~ whitespace* ~ (keyword_unless | keyword_if) ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
elif_template = { start_marker ~ whitespace* ~ keyword_elif ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
else_template = { start_marker ~ whitespace* ~ keyword_else ~ whitespace* ~ end_marker ~ vertical_whitespace? }
end_template = { start_marker ~ whitespace* ~ keyword_end ~ whitespace* ~ end_marker ~ vertical_whitespace? }
if_elif_else_template = { if_template ~ sub_template ~ elif_template* ~ sub_template ~ else_template? ~ sub_template ~ end_template }
for_template = { start_marker ~ whitespace* ~ keyword_for ~ whitespace+ ~ property ~ whitespace+ ~ "in" ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
with_template = { start_marker ~ whitespace* ~ keyword_with ~ whitespace+ ~ property ~ whitespace+ ~ "=" ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? ~ sub_template ~ end_template }
if_template = { linear_whitespace* ~ start_marker ~ whitespace* ~ (keyword_unless | keyword_if) ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
elif_template = { linear_whitespace* ~ start_marker ~ whitespace* ~ keyword_elif ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
else_template = { linear_whitespace* ~ start_marker ~ whitespace* ~ keyword_else ~ whitespace* ~ end_marker ~ vertical_whitespace? }
end_template = { linear_whitespace* ~ start_marker ~ whitespace* ~ keyword_end ~ whitespace* ~ end_marker ~ vertical_whitespace? }
if_elif_else_template = { if_template ~ sub_template ~ (elif_template ~ sub_template)* ~ (else_template ~ sub_template)? ~ end_template }
for_template = { linear_whitespace* ~ start_marker ~ whitespace* ~ keyword_for ~ whitespace+ ~ property ~ whitespace+ ~ "in" ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
with_template = { linear_whitespace* ~ start_marker ~ whitespace* ~ keyword_with ~ whitespace+ ~ property ~ whitespace+ ~ "=" ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? ~ sub_template ~ end_template }
for_else_template = { for_template ~ sub_template ~ else_template? ~ sub_template ~ end_template }
comment = { comment_start_marker ~ (!comment_end_marker ~ ANY)+ ~ comment_end_marker ~ vertical_whitespace? }
debug_template = { start_marker ~ whitespace* ~ keyword_debug ~ whitespace+ ~ expression ~ whitespace* ~ end_marker ~ vertical_whitespace? }
Expand Down
4 changes: 4 additions & 0 deletions tests/configuration/vlan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
vlans:
- id: 10
- id: 20
name: printer
23 changes: 23 additions & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,4 +1049,27 @@ fn if_elif_with_subtemplate() {
\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z INFO template\] Using configuration file 'tests/configuration/hello_world.yml'
\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z INFO template\] Parsing configuration using YAML format
$"#).unwrap());
}

#[test]
fn vlan_test() {
// From https://blog.networktocode.com/post/whitespace-control-in-jinja-templates/
let mut cmd = Command::cargo_bin("template").unwrap();
let assert = cmd
.arg("--template")
.arg("tests/template/vlan.template")
.arg("--configuration")
.arg("tests/configuration/vlan.yml")
.assert();

assert
.success()
.stdout(r#"vlan 10
vlan 20
name printer
"#)
.stderr(is_match(r#"^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z INFO template\] Using template file 'tests/template/vlan.template'
\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z INFO template\] Using configuration file 'tests/configuration/vlan.yml'
\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z INFO template\] Parsing configuration using YAML format
$"#).unwrap());
}
6 changes: 6 additions & 0 deletions tests/template/vlan.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% for vlan in vlans %}
vlan {% vlan.id %}
{% if vlan.name %}
name {% vlan.name %}
{% end %}
{% end %}

0 comments on commit 9ba01c3

Please sign in to comment.