-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add indexed access to context values #141
Changes from 2 commits
43fddab
8c51d00
58e99fe
79debf7
fa86130
3aa5feb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
use Renderable; | ||
use value::Value; | ||
use context::Context; | ||
use token::Token; | ||
use token::Token::*; | ||
use error::{Error, Result}; | ||
|
||
#[derive(Debug)] | ||
pub struct IdentifierPath { | ||
value: String, | ||
indexes: Vec<Value>, | ||
} | ||
|
||
impl Renderable for IdentifierPath { | ||
fn render(&self, context: &mut Context) -> Result<Option<String>> { | ||
println!("{:?}", self); | ||
let value = context | ||
.get_val(&self.value) | ||
.ok_or_else(|| Error::Render(format!("{} not found in context", self.value)))? | ||
.clone(); | ||
|
||
let mut counter = self.indexes.len(); | ||
let result = self.indexes.iter().fold(Ok(&value), |value, index| { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry, but I can't understand what you mean here. Could you explain a bit more please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So for
The current implementation requires copying all of What'd be amazing is if we could do this enum PathPart {
ObjectIndex(&str),
ArrayIndex(usize),
}
impl Context {
fn get_val(&self, path: &[PathPart]) -> Option<Value> {
...
}
} This would allow only the leaf to be copied. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another use case for when copying will involve a lot of data: cobalt's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I got it. This is a complicated thing actualy. I think we could implement this in current model - where we are not using subexpressions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel I'm missing something. I'm not seeing why My rough run through of how this would work: For now, this just involves turning the Long term, when/if we add more complex lookups, we should parse the inner expression first, render it, and make a This effectively transforms
into
(forgive my rough approximation of liquid syntax). Note: these variables shouldn't actually need to be created in the Context, they should be able to live only in Rust. and I don't see any problems with doing variable based lookups. When constructing a |
||
// go through error | ||
let value = if let Ok(value) = value { | ||
value | ||
} else { | ||
return value; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this just
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, it's an early return from fold when value is error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropped, sorry I missed that. |
||
counter -= 1; | ||
match (value, index) { | ||
(&Value::Array(ref value), &Value::Num(ref x)) => { | ||
// at the first condition only is_normal is not enough | ||
// because zero is not counted normal | ||
if (*x != 0f32 && !x.is_normal()) || *x < 0f32 || | ||
x.round() > (::std::usize::MAX as f32) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does liquid support negative indexing like Python? Does it support slicing? (and this code is one of the reasons why I'm considering native integer support) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neither of docs I see say this explicitly, but a simple test in jekyll shows that negative indexing is allowed, ranges(I've tried There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for looking into this! |
||
return Error::renderer("bad array index"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you make the error include the index? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More thanks |
||
} | ||
let idx = x.round() as usize; | ||
let value = | ||
value | ||
.get(idx) | ||
.ok_or_else(|| Error::Render("index out of range".to_string()))?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you include the index and value's length? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yet again, thanks! |
||
Ok(value) | ||
} | ||
(&Value::Array(_), _) => Error::renderer("bad array index type"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the error include a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, thanks! |
||
(&Value::Object(ref value), &Value::Str(ref x)) => { | ||
let value = | ||
value | ||
.get(x) | ||
.ok_or_else(|| Error::Render("object element not found".to_string()))?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you include There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for fixing this! |
||
Ok(value) | ||
} | ||
(&Value::Object(_), _) => Error::renderer("bad object index"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for fixing this! |
||
(value, _) if counter == 0 => Ok(value), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this case handling? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly other - leaf variants of Value I suppose. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for explaining. I think I missed that this counts down. So you are doing special processing on the last element. |
||
_ => Error::renderer("non-indexable element found while indexable expected"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, thanks! That'll make life much easier for people (lack of good error reporting is something we're slowly fixing). |
||
} | ||
}); | ||
|
||
match result { | ||
Ok(result) => result.render(context), | ||
Err(e) => Error::renderer(&format!("rendering error: {}", e)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use result.chain_err(|| "Failed to render expression") There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropped, |
||
} | ||
} | ||
} | ||
|
||
impl IdentifierPath { | ||
pub fn new(value: String) -> Self { | ||
Self { | ||
value: value, | ||
indexes: Vec::new(), | ||
} | ||
} | ||
pub fn append_indexes(&mut self, tokens: &[Token]) -> Result<()> { | ||
let rest = match tokens[0] { | ||
Dot if tokens.len() > 1 => { | ||
match tokens[1] { | ||
Identifier(ref x) => self.indexes.push(Value::Str(x.clone())), | ||
_ => { | ||
return Error::parser("identifier", Some(&tokens[0])); | ||
} | ||
}; | ||
2 | ||
} | ||
OpenSquare if tokens.len() > 2 => { | ||
let index = match tokens[1] { | ||
StringLiteral(ref x) => Value::Str(x.clone()), | ||
NumberLiteral(ref x) => Value::Num(*x), | ||
_ => { | ||
return Error::parser("number | string", Some(&tokens[0])); | ||
} | ||
}; | ||
self.indexes.push(index); | ||
|
||
if tokens[2] != CloseSquare { | ||
return Error::parser("]", Some(&tokens[1])); | ||
} | ||
3 | ||
} | ||
_ => return Ok(()), | ||
}; | ||
|
||
if tokens.len() > rest { | ||
self.append_indexes(&tokens[rest..]) | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
} | ||
#[cfg(test)] | ||
mod test { | ||
use value::Value; | ||
use parse; | ||
use Renderable; | ||
use Context; | ||
use LiquidOptions; | ||
use std::collections::HashMap; | ||
|
||
#[test] | ||
fn identifier_path() { | ||
let options = LiquidOptions::with_known_blocks(); | ||
let template = concat!("array: {{ test_a[0] }}\n", | ||
"complex_dot: {{ test_a[0].test_h }}\n", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whats the status of this? |
||
"complex_string: {{ test_a[0][\"test_h\"] }}\n"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you make these separate tests? Am I reading this right that the cases can be summarized as:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
|
||
let mut context = Context::new(); | ||
let mut internal = HashMap::new(); | ||
internal.insert("test_h".to_string(), Value::Num(5f32)); | ||
|
||
let test = Value::Array(vec![Value::Object(internal)]); | ||
context.set_val("test_a", test); | ||
|
||
let template = parse(template, options).unwrap(); | ||
assert_eq!(template.render(&mut context).unwrap(), | ||
Some(concat!("array: test_h: 5\n", | ||
"complex_dot: 5\n", | ||
"complex_string: 5\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (random place) Should we make it so all The reason I'm considering this is we then have a central place of handling identifiers to ensure we are always consistent about how they are handled. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dup of the first |
||
.to_owned())); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clone, Eq, etc?