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

add take_while, take_until filters #535

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions docs/content/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,72 @@ or by author name:

If `value` is not passed, it will drop any elements where the attribute is `null`.

#### take_while
Slice an array by a filter. Returns a slice of the array values that occur from the start before an
element for which `attribute` is not equal to `value`. Unlike the `filter` filter, this stops at the
first element that does not match.

If `value` is not passed, produces elements while the attribute is not null.

If the first element does not match, an empty array is returned. If all elements match, the whole array is returned.

`attribute` is mandatory.

Example:

Given `sorted_orders` is an array of `Crate`

```rust
struct Crate {
label: String,
destination: String,
// ...
}
```

```jinja2
Next cargo ship to the happy place will be filled with
{{ sorted_orders | take_while(attribute="destination", value="Finland") | len }}
crates.
```

#### take_until
Slice an array by a filter. Returns a slice of the array values that occur from the start before an
element for which `attribute` is equal to `value`.

If `value` is not passed, produces elements while the attribute is null.

If the first element matches, an empty array is returned. If no elements match, the whole array is returned.

`attribute` is mandatory.

Example:

Given `all_posts` is an array of `Post` and `current_page` is a single `Post`

```rust
struct Post {
title: String,
// unique to the nanosecond level
timestamp: String,
// ...
}
```

The `attribute` argument with a `value` can be used to find the older and newer posts compared to a current post:

```jinja2
Number of posts older than this one:
{{ all_posts | sort(attribute="timestamp")
| take_until(attribute="timestamp", value=current_page.timestamp) | len }}
```

```jinja2
You should read the next post titled:
{{ all_posts | sort(attribute="timestamp") | reverse
| take_until(attribute="timestamp", value=current_page.timestamp) | last }}
```

#### map

Retrieves an attribute from each object in an array. The `attribute` argument is mandatory and specifies what to extract.
Expand Down
242 changes: 242 additions & 0 deletions src/builtins/filters/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,68 @@ pub fn filter(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
Ok(to_value(arr).unwrap())
}

/// Slice an array by a filter. Returns a slice of the array values that occur from the start
/// before an element for which `attribute` is not equal to `value`. If `value` is not passed,
/// produces elements while the element attribute is not null.
pub fn take_while(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
let arr = try_get_value!("take_while", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}

let key = match args.get("attribute") {
Some(val) => try_get_value!("take_while", "attribute", String, val),
None => return Err(Error::msg("The `take_while` filter has to have an `attribute` argument")),
};
let boundary_value = args.get("value").unwrap_or(&Value::Null);

let json_pointer = get_json_pointer(&key);
let output = arr
.into_iter()
.take_while(|v| {
let val = v.pointer(&json_pointer).unwrap_or(&Value::Null);
if boundary_value.is_null() {
!val.is_null()
} else {
val == boundary_value
}
})
.collect::<Vec<_>>();

Ok(to_value(output).unwrap())
}

/// Slice an array by a filter. Returns a slice of the array values that occur from the start
/// before an element for which `attribute` is equal to `value`. If `value` is not passed,
/// produces elements while the element attribute is null.
pub fn take_until(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
let arr = try_get_value!("take_until", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}

let key = match args.get("attribute") {
Some(val) => try_get_value!("take_until", "attribute", String, val),
None => return Err(Error::msg("The `take_until` filter has to have an `attribute` argument")),
};
let boundary_value = args.get("value").unwrap_or(&Value::Null);

let json_pointer = get_json_pointer(&key);
let output = arr
.into_iter()
.take_while(|v| {
let val = v.pointer(&json_pointer).unwrap_or(&Value::Null);
if boundary_value.is_null() {
val.is_null()
} else {
val != boundary_value
}
})
.collect::<Vec<_>>();

Ok(to_value(output).unwrap())
}

/// Map retrieves an attribute from a list of objects.
/// The 'attribute' argument specifies what to retrieve.
pub fn map(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
Expand Down Expand Up @@ -760,6 +822,186 @@ mod tests {
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_while_empty() {
let res = take_while(&json!([]), &HashMap::new());
assert!(res.is_ok());
assert_eq!(res.unwrap(), json!([]));
}

#[test]
fn test_take_while_value_match() {
let input = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
{"id": 3, "year": 2016},
{"id": 4, "year": 2015},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());
args.insert("value".to_string(), to_value(2015).unwrap());

let expected = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
]);

let res = take_while(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_while_value_nomatch() {
let input = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
{"id": 3, "year": 2016},
{"id": 4, "year": 2015},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());
args.insert("value".to_string(), to_value(2016).unwrap());

let expected = json!([
]);

let res = take_while(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_while_novalue_match() {
let input = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
{"id": 3},
{"id": 4, "year": 2016},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());

let expected = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
]);

let res = take_while(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_while_novalue_nomatch() {
let input = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());

let expected = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
]);

let res = take_while(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_until_empty() {
let res = take_until(&json!([]), &HashMap::new());
assert!(res.is_ok());
assert_eq!(res.unwrap(), json!([]));
}

#[test]
fn test_take_until_value_match() {
let input = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
{"id": 3, "year": 2016},
{"id": 4, "year": 2015},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());
args.insert("value".to_string(), to_value(2016).unwrap());

let expected = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
]);

let res = take_until(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_until_value_nomatch() {
let input = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
{"id": 3, "year": 2016},
{"id": 4, "year": 2015},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());
args.insert("value".to_string(), to_value(2014).unwrap());

let expected = json!([
{"id": 1, "year": 2015},
{"id": 2, "year": 2015},
{"id": 3, "year": 2016},
{"id": 4, "year": 2015},
]);

let res = take_until(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_until_novalue_match() {
let input = json!([
{"id": 1},
{"id": 2, "year": 2015},
{"id": 3},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());

let expected = json!([
{"id": 1},
]);

let res = take_until(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_take_until_novalue_nomatch() {
let input = json!([
{"id": 1},
{"id": 2},
]);
let mut args = HashMap::new();
args.insert("attribute".to_string(), to_value("year").unwrap());

let expected = json!([
{"id": 1},
{"id": 2},
]);

let res = take_until(&input, &args);
assert!(res.is_ok());
assert_eq!(res.unwrap(), to_value(expected).unwrap());
}

#[test]
fn test_map_empty() {
let res = map(&json!([]), &HashMap::new());
Expand Down
Loading