Skip to content
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

Merged
merged 1 commit into from
Jan 9, 2018
Merged

Conversation

badboy
Copy link
Contributor

@badboy badboy commented Jan 9, 2018

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.

  • Tests created for any new feature or regression tests for bugfixes.
  • cargo test succeeds
  • rustup run nightly cargo clippy succeeds
  • cargo fmt -- --write-mode=diff succeeds <-- Tons of unrelated changes.

@epage
Copy link
Member

epage commented Jan 9, 2018

For reference, #155

Copy link
Member

@epage epage left a 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 :)

None => return Error::renderer("Right-hand side of contains operator must be a string"),
};

Ok(a.contains(b))
Copy link
Member

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.

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"),
Copy link
Member

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"))?;

.unwrap();

let mut context = Context::new();
let _output = template.render(&mut context).unwrap();
Copy link
Member

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)

@@ -336,4 +350,75 @@ mod test {
let output = template.render(&mut context).unwrap();
assert_eq!(output, Some("fourth".to_owned()));
}

#[test]
fn contains_comparison() {
Copy link
Member

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)

@badboy
Copy link
Contributor Author

badboy commented Jan 9, 2018

Thanks for quickly reviewing this. This was just a first shot at the minimal implementation.
Will follow your advice and split up tests and also implement the HashMap contains

@badboy
Copy link
Contributor Author

badboy commented Jan 9, 2018

  1. Split up tests
  2. Implemented for objects and arrays
  3. Cleaned up extensive matches

match *a {
Value::Str(ref val) => Ok(val.contains(b)),
Value::Object(ref obj) => Ok(obj.contains_key(b)),
Value::Array(ref arr) => {
Copy link
Member

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

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.

@epage
Copy link
Member

epage commented Jan 9, 2018

This looks great! The work I see remaining to do

  • Resolve any CI failures
  • Squash commits
  • Please title the commit something like feat(if): Support contains operator
  • Please reference that this Fixes #155

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)

@badboy
Copy link
Contributor Author

badboy commented Jan 9, 2018

Ah no problem. With these detailed instructions it will be easy to do.

@epage
Copy link
Member

epage commented Jan 9, 2018

Huh, I should actually give the "why" to things.

I use clog-cli to create changelists. By having feat: stuf in the title of the commit, I won't miss announcing your new feature in the changelog.

Fixes #155 will auto-close the issue and link it to the commit.

Squashing is to make the history cleaner which makes it easier to browse or git bisect.

@badboy
Copy link
Contributor Author

badboy commented Jan 9, 2018

I use clog-cli to create changelists. By having feat: stuf in the title of the commit, I won't miss announcing your new feature in the changelog.

Yup, I do something similar in other projects.
The rest is known as well. Thanks!

@badboy badboy force-pushed the contains-ops branch 4 times, most recently from 9703131 to 0172b32 Compare January 9, 2018 22:21
@badboy badboy closed this Jan 9, 2018
@badboy badboy reopened this Jan 9, 2018
@badboy
Copy link
Contributor Author

badboy commented Jan 9, 2018

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
@badboy
Copy link
Contributor Author

badboy commented Jan 9, 2018

The only remaining failure looks like a network error on traivs, restarting that test case might resolve it.

@epage
Copy link
Member

epage commented Jan 9, 2018

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

@epage
Copy link
Member

epage commented Jan 9, 2018

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.

@epage epage merged commit 14dea6a into cobalt-org:master Jan 9, 2018
@epage
Copy link
Member

epage commented Jan 10, 2018

cargo publish on cobalt v0.11.1 is happening right now

@epage epage mentioned this pull request Apr 10, 2018
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants