From 94e6660040d5b021e0ee6aac86ef51e25dd2c725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?PedroGon=C3=A7aloCorreia?= Date: Sat, 27 Oct 2018 12:48:32 +0100 Subject: [PATCH] feat(filters): array manipulation filters Implemented some jekyll filters related to arrays: - push - pop - unshift - shift - array_to_sentence_string --- src/filters/array.rs | 219 +++++++++++++++++++++++++++++++++++++++++++ src/filters/mod.rs | 4 + src/parser.rs | 7 ++ 3 files changed, 230 insertions(+) create mode 100644 src/filters/array.rs diff --git a/src/filters/array.rs b/src/filters/array.rs new file mode 100644 index 000000000..543fcc6d5 --- /dev/null +++ b/src/filters/array.rs @@ -0,0 +1,219 @@ +use liquid_value::Value; + +use super::{check_args_len, invalid_input}; +use interpreter::FilterResult; +use std::fmt::Write; + +/// Receives a `Value::Array` as an input. +/// Returns a copy of the input with the given value appended at the end. +pub fn push(input: &Value, args: &[Value]) -> FilterResult { + check_args_len(args, 1, 0)?; + + let value = args[0].clone(); + let mut array = input + .as_array() + .ok_or_else(|| invalid_input("Array expected"))? + .clone(); + array.push(value); + + Ok(Value::array(array)) +} + +/// Receives a `Value::Array` as an input. +/// Returns a copy of the input with the last element removed. +pub fn pop(input: &Value, args: &[Value]) -> FilterResult { + check_args_len(args, 0, 0)?; + + let mut array = input + .as_array() + .ok_or_else(|| invalid_input("Array expected"))? + .clone(); + array.pop(); + + Ok(Value::array(array)) +} + +/// Receives a `Value::Array` as an input. +/// Returns a copy of the input with the given value appended at the beginning. +pub fn unshift(input: &Value, args: &[Value]) -> FilterResult { + check_args_len(args, 1, 0)?; + + let value = args[0].clone(); + let mut array = input + .as_array() + .ok_or_else(|| invalid_input("Array expected"))? + .clone(); + array.insert(0, value); + + Ok(Value::array(array)) +} + +/// Receives a `Value::Array` as an input. +/// Returns a copy of the input with the first element removed. +pub fn shift(input: &Value, args: &[Value]) -> FilterResult { + check_args_len(args, 0, 0)?; + + let mut array = input + .as_array() + .ok_or_else(|| invalid_input("Array expected"))? + .clone(); + + if !array.is_empty() { + array.remove(0); + } + + Ok(Value::array(array)) +} + +/// Convert an array into a sentence. +pub fn array_to_sentence_string(input: &Value, args: &[Value]) -> FilterResult { + check_args_len(args, 0, 1)?; + + let connector = args.get(0).map(|arg| arg.to_str()); + let connector = connector + .as_ref() + .map(|s| s.as_ref()) + .unwrap_or_else(|| "and"); + + let mut array = input + .as_array() + .ok_or_else(|| invalid_input("Array expected"))? + .iter(); + + let mut sentence = array + .next() + .map(|v| v.to_string()) + .unwrap_or_else(|| "".to_string()); + + let last = array.next_back(); + + for value in array { + write!(sentence, ", {}", value).expect("It should be safe to write to a string."); + } + + if let Some(last) = last { + write!(sentence, ", {} {}", connector, last) + .expect("It should be safe to write to a string."); + } + + Ok(Value::scalar(sentence)) +} + +#[cfg(test)] +mod tests { + + use super::*; + + macro_rules! unit { + ($a:ident, $b:expr) => {{ + unit!($a, $b, &[]) + }}; + ($a:ident, $b:expr, $c:expr) => {{ + $a(&$b, $c).unwrap() + }}; + } + + #[test] + fn unit_push() { + let input = Value::Array(vec![Value::scalar("Seattle"), Value::scalar("Tacoma")]); + let args = &[Value::scalar("Spokane")]; + let desired_result = Value::Array(vec![ + Value::scalar("Seattle"), + Value::scalar("Tacoma"), + Value::scalar("Spokane"), + ]); + assert_eq!(unit!(push, input, args), desired_result); + } + + #[test] + fn unit_pop() { + let input = Value::Array(vec![Value::scalar("Seattle"), Value::scalar("Tacoma")]); + let args = &[]; + let desired_result = Value::Array(vec![Value::scalar("Seattle")]); + assert_eq!(unit!(pop, input, args), desired_result); + } + + #[test] + fn unit_pop_empty() { + let input = Value::Array(vec![]); + let args = &[]; + let desired_result = Value::Array(vec![]); + assert_eq!(unit!(pop, input, args), desired_result); + } + + #[test] + fn unit_unshift() { + let input = Value::Array(vec![Value::scalar("Seattle"), Value::scalar("Tacoma")]); + let args = &[Value::scalar("Olympia")]; + let desired_result = Value::Array(vec![ + Value::scalar("Olympia"), + Value::scalar("Seattle"), + Value::scalar("Tacoma"), + ]); + assert_eq!(unit!(unshift, input, args), desired_result); + } + + #[test] + fn unit_shift() { + let input = Value::Array(vec![Value::scalar("Seattle"), Value::scalar("Tacoma")]); + let args = &[]; + let desired_result = Value::Array(vec![Value::scalar("Tacoma")]); + assert_eq!(unit!(shift, input, args), desired_result); + } + + #[test] + fn unit_shift_empty() { + let input = Value::Array(vec![]); + let args = &[]; + let desired_result = Value::Array(vec![]); + assert_eq!(unit!(shift, input, args), desired_result); + } + + #[test] + fn unit_array_to_sentence_string() { + let input = Value::Array(vec![ + Value::scalar("foo"), + Value::scalar("bar"), + Value::scalar("baz"), + ]); + let args = &[]; + let desired_result = Value::scalar("foo, bar, and baz"); + assert_eq!(unit!(array_to_sentence_string, input, args), desired_result); + } + + #[test] + fn unit_array_to_sentence_string_two_elements() { + let input = Value::Array(vec![Value::scalar("foo"), Value::scalar("bar")]); + let args = &[]; + let desired_result = Value::scalar("foo, and bar"); + assert_eq!(unit!(array_to_sentence_string, input, args), desired_result); + } + + #[test] + fn unit_array_to_sentence_string_one_element() { + let input = Value::Array(vec![Value::scalar("foo")]); + let args = &[]; + let desired_result = Value::scalar("foo"); + assert_eq!(unit!(array_to_sentence_string, input, args), desired_result); + } + + #[test] + fn unit_array_to_sentence_string_no_elements() { + let input = Value::Array(vec![]); + let args = &[]; + let desired_result = Value::scalar(""); + assert_eq!(unit!(array_to_sentence_string, input, args), desired_result); + } + + #[test] + fn unit_array_to_sentence_string_custom_connector() { + let input = Value::Array(vec![ + Value::scalar("foo"), + Value::scalar("bar"), + Value::scalar("baz"), + ]); + let args = &[Value::scalar("or")]; + let desired_result = Value::scalar("foo, bar, or baz"); + assert_eq!(unit!(array_to_sentence_string, input, args), desired_result); + } +} diff --git a/src/filters/mod.rs b/src/filters/mod.rs index dc2371038..eff3076d1 100644 --- a/src/filters/mod.rs +++ b/src/filters/mod.rs @@ -1,8 +1,12 @@ +#[cfg(feature = "extra-filters")] +mod array; mod date; mod html; mod math; mod url; +#[cfg(feature = "extra-filters")] +pub use self::array::{array_to_sentence_string, pop, push, shift, unshift}; pub use self::date::date; #[cfg(feature = "extra-filters")] pub use self::date::date_in_tz; diff --git a/src/parser.rs b/src/parser.rs index 1508b11b6..5275dc561 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -149,6 +149,13 @@ impl ParserBuilder { ).filter( "date_in_tz", filters::date_in_tz as interpreter::FnFilterValue, + ).filter("push", filters::push as interpreter::FnFilterValue) + .filter("pop", filters::pop as interpreter::FnFilterValue) + .filter("unshift", filters::unshift as interpreter::FnFilterValue) + .filter("shift", filters::shift as interpreter::FnFilterValue) + .filter( + "array_to_sentence_string", + filters::array_to_sentence_string as interpreter::FnFilterValue, ) }