Skip to content

Commit

Permalink
Allow default tag to assign nested values
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Bottriell <[email protected]>
  • Loading branch information
rydrman committed Apr 9, 2023
1 parent 529cf7b commit 429ba67
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 15 deletions.
2 changes: 1 addition & 1 deletion crates/spk-schema/crates/liquid/src/error_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn test_error_position_extraction() {
crate::render_template(TPL, &json!({})).expect_err("expected template render to fail");
let expected = r#"
1 | {% default = data | replace ''%}
| ^ unexpected "="; expected Identifier
| ^ unexpected "="; expected Variable
"#;
let message = err.to_string();
assert_eq!(message, expected);
Expand Down
69 changes: 59 additions & 10 deletions crates/spk-schema/crates/liquid/src/tag_default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/imageworks/spk

use liquid::model::map::Entry;
use liquid::{Object, ValueView};
use liquid_core::error::ResultLiquidExt;
use liquid_core::parser::FilterChain;
use liquid_core::{Language, ParseTag, Renderable, Result, Runtime, TagReflection, TagTokenIter};
use liquid_core::runtime::Variable;
use liquid_core::{
Language,
ParseTag,
Renderable,
Result,
Runtime,
TagReflection,
TagTokenIter,
Value,
};

#[cfg(test)]
#[path = "./tag_default_test.rs"]
Expand Down Expand Up @@ -36,11 +48,9 @@ impl ParseTag for DefaultTag {
options: &Language,
) -> Result<Box<dyn Renderable>> {
let dst = arguments
.expect_next("Identifier expected.")?
.expect_identifier()
.into_result()?
.to_string()
.into();
.expect_next("Variable expected.")?
.expect_variable()
.into_result()?;

arguments
.expect_next("Assignment operator \"=\" expected.")?
Expand All @@ -65,7 +75,7 @@ impl ParseTag for DefaultTag {

#[derive(Debug)]
struct Default {
dst: liquid_core::model::KString,
dst: Variable,
src: FilterChain,
}

Expand All @@ -83,10 +93,49 @@ impl Renderable for Default {
.trace_with(|| self.trace().into())?
.into_owned();

let name = self.dst.as_str().into();
if runtime.try_get(&[name]).is_none() {
runtime.set_global(self.dst.clone(), value);
let variable = self.dst.evaluate(runtime)?;
let mut path = variable.into_iter().collect::<Vec<_>>().into_iter();
let root = path.next().expect("at least one entry in path");

let mut current_pos = liquid::model::Path::with_index(root.clone());
let type_err = |pos: &liquid::model::Path| {
liquid::Error::with_msg("Cannot set default")
.trace("Stepping into non-object")
.context("position", pos.to_string())
.context("target", self.dst.to_string())
};

let Some(last) = path.next_back() else {
if runtime.get(&[root.clone()]).is_err() {
runtime.set_global(root.to_kstr().into(), value);
}
return Ok(());
};
let mut data = runtime
.get(&[root.to_owned().into()])
.map(|v| v.into_owned())
.unwrap_or_else(|_| Value::Object(Object::new()));
let mut data_ref = &mut data;
for step in path {
data_ref = data_ref
.as_object_mut()
.ok_or_else(|| type_err(&current_pos))?
.entry(step.to_kstr())
.or_insert_with(|| Value::Object(Object::new()));
current_pos.push(step.to_owned());
}
match data_ref
.as_object_mut()
.ok_or_else(|| type_err(&current_pos))?
.entry(last.to_kstr())
{
Entry::Occupied(_) => {}
Entry::Vacant(v) => {
v.insert(value);
runtime.set_global(root.to_kstr().into(), data);
}
}

Ok(())
}
}
54 changes: 54 additions & 0 deletions crates/spk-schema/crates/liquid/src/tag_default_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,57 @@ sources:
crate::render_template(TPL, &options).expect("template should not fail to render");
assert_eq!(rendered, EXPECTED);
}

#[rstest]
fn test_template_rendering_default_nested() {
// ensure that setting a nested default value
// works as expected

let options = json!({
"nested": {
"existing": "existing"
}
});
static TPL: &str = r#"
{% default nested.existing = "ignored" %}
{% default nested.other = "something" %}
pkg: {{ nested.other }}-{{ nested.existing }}
"#;
static EXPECTED: &str = r#"
pkg: something-existing
"#;
let rendered = match crate::render_template(TPL, &options) {
Ok(r) => r,
Err(err) => {
println!("{err}");
panic!("template should not fail to render");
}
};
assert_eq!(rendered, EXPECTED);
}

#[rstest]
fn test_template_rendering_default_nested_not_object() {
// ensure that setting a nested default value
// works as expected

let options = json!({
"integer": 64,
});
static TPL: &str = r#"
{% default integer.nested = "invalid" %}
"#;
let err = crate::render_template(TPL, &options)
.expect_err("Should fail when setting default under non-object");
let expected = r#"liquid: Cannot set default
from: Stepping into non-object
with:
position=integer
target=integer["nested"]
"#;
let message = err.to_string();
assert_eq!(message, expected);
}
12 changes: 9 additions & 3 deletions docs/use/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,14 @@ In addition to the default tags and filters within the liquid language, spk prov
The `default` tag can be used to more easily declare the default value for a variable. The following two statements are equivalent:

```liquid
{% assign version = version | default: "2.3.4" %}
{% default version = "2.3.4" %}
{% assign var = var | default: "2.3.4" %}
{% default var = "2.3.4" %}
```

Additionally, this tag can be used to set defaults in nested structures. Often, for options that may be provided at the command line.

```liquid
{% default opt.version = "2.3.4" %}
```

##### Filters
Expand Down Expand Up @@ -494,7 +500,7 @@ The `parse_version` filter breaks down an spk version into its components, eithe
The `replace_re` filter works like the built-in `replace` filter, except that it matches using a perl-style regular expression and allows group replacement in the output. These regular expressions do not support look-arounds or back-references. For example:

```liquid
{% default version = "2.3.4" %}
{% assign version = opt.version | default: "2.3.4" %}
{% assign major_minor = version | replace_re: "(\d+)\.(\d+).*", "$1.$2" %}
{{ major_minor }} # 2.3
```
2 changes: 1 addition & 1 deletion packages/gnu/gcc/gcc48.spk.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# {{ assign version = opt.version | default: "4.8.5" }}
# {% assign version = opt.version | default: "4.8.5" %}
pkg: gcc/{{ version }}
api: v0/package

Expand Down

0 comments on commit 429ba67

Please sign in to comment.