-
Notifications
You must be signed in to change notification settings - Fork 79
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
Implement contains
operator
#157
Conversation
For reference, #155 |
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.
Thanks for getting this started!
I'm grateful for any contributions. You can keep owning this or hand off the rest of the work to me at any stage. I'd rather not burn you out :)
src/tags/if_block.rs
Outdated
None => return Error::renderer("Right-hand side of contains operator must be a string"), | ||
}; | ||
|
||
Ok(a.contains(b)) |
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.
contains
supports two use cases
- substring matches (
page.title contains "Hello"
) - checking a hashmap if it contains a key (
page contains "title"
)
This implements the first but for cobalt-org/cobalt.rs#363, it sounds like you need the second.
If you want, I can merge this now. Hopefully soon I can add support for checking for the object containing a key. Otherwise, feel free to add support for that in this PR or another one. Just let me know what you want.
src/tags/if_block.rs
Outdated
fn contains_check(a: Value, b: Value) -> Result<bool> { | ||
let a = match a.as_str() { | ||
Some(s) => s, | ||
None => return Error::renderer("Left-hand side of contains operator must be a string"), |
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.
(this is non-essential feedback, feel free to ignore)
Personally, I prefer
let a = a.as_str().ok_or("Left-hand side of contains operator must be a string")?;
or more like yours
let a = a.as_str().ok_or_else(|| Error::renderer("Left-hand side of contains operator must be a string"))?;
src/tags/if_block.rs
Outdated
.unwrap(); | ||
|
||
let mut context = Context::new(); | ||
let _output = template.render(&mut context).unwrap(); |
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.
Instead of checking for panic, you could
let output = template.render(&mut context);
assert!(output.is_err());
This makes maintenance a little easier because you don't have to worry about string matching the panic message (the error message can change without breaking the tests).
(also applies to the next test in case you decide to implement it)
src/tags/if_block.rs
Outdated
@@ -336,4 +350,75 @@ mod test { | |||
let output = template.render(&mut context).unwrap(); | |||
assert_eq!(output, Some("fourth".to_owned())); | |||
} | |||
|
|||
#[test] | |||
fn contains_comparison() { |
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.
Personally, I prefer the tests to be broken up by their purpose.
This is helpful for the person modifying tests and the person analyzing a test failure.
So for example,
#[test]
fn contains_string_literals() {
...
}
#[test]
fn contains_else() {
...
}
#[test]
fn test contains_variable() {
...
}
(I don't recommend literally copying my function names above when splitting this function because I'm making guesses as to your intent. You know what you intended with each test and this serves as a way for you to document it)
Thanks for quickly reviewing this. This was just a first shot at the minimal implementation. |
|
match *a { | ||
Value::Str(ref val) => Ok(val.contains(b)), | ||
Value::Object(ref obj) => Ok(obj.contains_key(b)), | ||
Value::Array(ref arr) => { |
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.
I looked up contains
but I thought it said it works for substrings and key checks.
Looking it up, instead its documented to work for substrings and array membership.
https://shopify.github.io/liquid/basics/operators/
But the implementation does work for objects's
- https://github.com/Shopify/liquid/blob/master/lib/liquid/condition.rb#L18
- https://ruby-doc.org/core-2.4.2/Hash.html#method-i-include-3F
So the only thing we're missing, compared to official liquid, is converting the right side to a string if the left side is a string.
That I'm perfectly happy postponing. I've started a major revamp for Value
that will make it easier to implement all of these random liquid conversions / coercions that take place.
This looks great! The work I see remaining to do
Again, if there is any of that you aren't comfortable with or want to hand off, feel free to do so. (Sorry we're using an odd version of rustfmt. I tried to mitigate that with writing out its config) |
Ah no problem. With these detailed instructions it will be easy to do. |
Huh, I should actually give the "why" to things. I use clog-cli to create changelists. By having
Squashing is to make the history cleaner which makes it easier to browse or |
Yup, I do something similar in other projects. |
9703131
to
0172b32
Compare
Oops, managed to hit the wrong button. |
The contains operator works for strings (substring search), arrays (string in array) or objects (string key exists). The renderer will error if the right-hand side operator does not evaluate to a string, if the left-hand side is neither a string, array nor object or if the array contains non-string items. Fixes cobalt-org#155
The only remaining failure looks like a network error on traivs, restarting that test case might resolve it. |
Awesome! I kicked off the travis job. I feel this will justify a liquid and cobalt release to get this into yours (and others) hands. EDIT: Wrong CI |
btw the crate ecosystem seems to be trying to standardize the license. Please look at #7 and leave a comment if you accept the change. |
|
This will panic if either side of the operator cannot be evaluated as a string.
It works for both variables and string literals as expected.
cargo test
succeedsrustup run nightly cargo clippy
succeedscargo fmt -- --write-mode=diff
succeeds <-- Tons of unrelated changes.