loose_liquid_lib/jekyll/
array.rs

1use std::fmt::Write;
2
3use liquid_core::Expression;
4use liquid_core::Result;
5use liquid_core::Runtime;
6use liquid_core::{
7    Display_filter, Filter, FilterParameters, FilterReflection, FromFilterParameters, ParseFilter,
8};
9use liquid_core::{Value, ValueView};
10
11use crate::invalid_input;
12
13#[derive(Debug, FilterParameters)]
14struct PushArgs {
15    #[parameter(description = "The element to append to the array.")]
16    element: Expression,
17}
18
19#[derive(Clone, ParseFilter, FilterReflection)]
20#[filter(
21    name = "push",
22    description = "Appends the given element to the end of an array.",
23    parameters(PushArgs),
24    parsed(PushFilter)
25)]
26pub struct Push;
27
28#[derive(Debug, FromFilterParameters, Display_filter)]
29#[name = "push"]
30struct PushFilter {
31    #[parameters]
32    args: PushArgs,
33}
34
35impl Filter for PushFilter {
36    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
37        let args = self.args.evaluate(runtime)?;
38
39        let element = args.element.to_value();
40        let mut array = input
41            .to_value()
42            .into_array()
43            .ok_or_else(|| invalid_input("Array expected"))?;
44        array.push(element);
45
46        Ok(Value::Array(array))
47    }
48}
49
50#[derive(Clone, ParseFilter, FilterReflection)]
51#[filter(
52    name = "pop",
53    description = "Removes the last element of an array.",
54    parsed(PopFilter)
55)]
56pub struct Pop;
57
58#[derive(Debug, Default, Display_filter)]
59#[name = "pop"]
60struct PopFilter;
61
62impl Filter for PopFilter {
63    fn evaluate(&self, input: &dyn ValueView, _runtime: &dyn Runtime) -> Result<Value> {
64        let mut array = input
65            .to_value()
66            .into_array()
67            .ok_or_else(|| invalid_input("Array expected"))?;
68        array.pop();
69
70        Ok(Value::Array(array))
71    }
72}
73
74#[derive(Debug, FilterParameters)]
75struct UnshiftArgs {
76    #[parameter(description = "The element to append to the array.")]
77    element: Expression,
78}
79
80#[derive(Clone, ParseFilter, FilterReflection)]
81#[filter(
82    name = "unshift",
83    description = "Appends the given element to the start of an array.",
84    parameters(UnshiftArgs),
85    parsed(UnshiftFilter)
86)]
87pub struct Unshift;
88
89#[derive(Debug, FromFilterParameters, Display_filter)]
90#[name = "unshift"]
91struct UnshiftFilter {
92    #[parameters]
93    args: UnshiftArgs,
94}
95
96impl Filter for UnshiftFilter {
97    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
98        let args = self.args.evaluate(runtime)?;
99
100        let element = args.element.to_value();
101        let mut array = input
102            .to_value()
103            .into_array()
104            .ok_or_else(|| invalid_input("Array expected"))?;
105        array.insert(0, element);
106
107        Ok(Value::Array(array))
108    }
109}
110
111#[derive(Clone, ParseFilter, FilterReflection)]
112#[filter(
113    name = "shift",
114    description = "Removes the first element of an array.",
115    parsed(ShiftFilter)
116)]
117pub struct Shift;
118
119#[derive(Debug, Default, Display_filter)]
120#[name = "shift"]
121struct ShiftFilter;
122
123impl Filter for ShiftFilter {
124    fn evaluate(&self, input: &dyn ValueView, _runtime: &dyn Runtime) -> Result<Value> {
125        let mut array = input
126            .to_value()
127            .into_array()
128            .ok_or_else(|| invalid_input("Array expected"))?;
129
130        if !array.is_empty() {
131            array.remove(0);
132        }
133
134        Ok(Value::Array(array))
135    }
136}
137
138#[derive(Debug, FilterParameters)]
139struct ArrayToSentenceStringArgs {
140    #[parameter(
141        description = "The connector between the last two elements. Defaults to \"and\".",
142        arg_type = "str"
143    )]
144    connector: Option<Expression>,
145}
146
147#[derive(Clone, ParseFilter, FilterReflection)]
148#[filter(
149    name = "array_to_sentence_string",
150    description = "Converts an array into a sentence. This sentence will be a list of the elements of the array separated by comma, with a connector between the last two elements.",
151    parameters(ArrayToSentenceStringArgs),
152    parsed(ArrayToSentenceStringFilter)
153)]
154pub struct ArrayToSentenceString;
155
156#[derive(Debug, FromFilterParameters, Display_filter)]
157#[name = "array_to_sentence_string"]
158struct ArrayToSentenceStringFilter {
159    #[parameters]
160    args: ArrayToSentenceStringArgs,
161}
162
163impl Filter for ArrayToSentenceStringFilter {
164    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
165        let args = self.args.evaluate(runtime)?;
166
167        let connector = args.connector.unwrap_or_else(|| "and".into());
168
169        let mut array = input
170            .as_array()
171            .ok_or_else(|| invalid_input("Array expected"))?
172            .values();
173
174        let mut sentence = array
175            .next()
176            .map(|v| v.to_kstr().into_string())
177            .unwrap_or_else(|| "".to_string());
178
179        let mut iter = array.peekable();
180        while let Some(value) = iter.next() {
181            if iter.peek().is_some() {
182                write!(sentence, ", {}", value.render())
183                    .expect("It should be safe to write to a string.");
184            } else {
185                write!(sentence, ", {} {}", connector, value.render())
186                    .expect("It should be safe to write to a string.");
187            }
188        }
189
190        Ok(Value::scalar(sentence))
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn unit_push() {
200        let input = liquid_core::value!(["Seattle", "Tacoma"]);
201        let unit_result = liquid_core::call_filter!(Push, input, "Spokane").unwrap();
202        let desired_result = liquid_core::value!(["Seattle", "Tacoma", "Spokane",]);
203        assert_eq!(unit_result, desired_result);
204    }
205
206    #[test]
207    fn unit_pop() {
208        let input = liquid_core::value!(["Seattle", "Tacoma"]);
209        let unit_result = liquid_core::call_filter!(Pop, input).unwrap();
210        let desired_result = liquid_core::value!(["Seattle"]);
211        assert_eq!(unit_result, desired_result);
212    }
213
214    #[test]
215    fn unit_pop_empty() {
216        let input = liquid_core::value!([]);
217        let unit_result = liquid_core::call_filter!(Pop, input).unwrap();
218        let desired_result = liquid_core::value!([]);
219        assert_eq!(unit_result, desired_result);
220    }
221
222    #[test]
223    fn unit_unshift() {
224        let input = liquid_core::value!(["Seattle", "Tacoma"]);
225        let unit_result = liquid_core::call_filter!(Unshift, input, "Olympia").unwrap();
226        let desired_result = liquid_core::value!(["Olympia", "Seattle", "Tacoma"]);
227        assert_eq!(unit_result, desired_result);
228    }
229
230    #[test]
231    fn unit_shift() {
232        let input = liquid_core::value!(["Seattle", "Tacoma"]);
233        let unit_result = liquid_core::call_filter!(Shift, input).unwrap();
234        let desired_result = liquid_core::value!(["Tacoma"]);
235        assert_eq!(unit_result, desired_result);
236    }
237
238    #[test]
239    fn unit_shift_empty() {
240        let input = liquid_core::value!([]);
241        let unit_result = liquid_core::call_filter!(Shift, input).unwrap();
242        let desired_result = liquid_core::value!([]);
243        assert_eq!(unit_result, desired_result);
244    }
245
246    #[test]
247    fn unit_array_to_sentence_string() {
248        let input = liquid_core::value!(["foo", "bar", "baz"]);
249        let unit_result = liquid_core::call_filter!(ArrayToSentenceString, input).unwrap();
250        let desired_result = "foo, bar, and baz";
251        assert_eq!(unit_result, desired_result);
252    }
253
254    #[test]
255    fn unit_array_to_sentence_string_two_elements() {
256        let input = liquid_core::value!(["foo", "bar"]);
257        let unit_result = liquid_core::call_filter!(ArrayToSentenceString, input).unwrap();
258        let desired_result = "foo, and bar";
259        assert_eq!(unit_result, desired_result);
260    }
261
262    #[test]
263    fn unit_array_to_sentence_string_one_element() {
264        let input = liquid_core::value!(["foo"]);
265        let unit_result = liquid_core::call_filter!(ArrayToSentenceString, input).unwrap();
266        let desired_result = "foo";
267        assert_eq!(unit_result, desired_result);
268    }
269
270    #[test]
271    fn unit_array_to_sentence_string_no_elements() {
272        let input = liquid_core::value!([]);
273        let unit_result = liquid_core::call_filter!(ArrayToSentenceString, input).unwrap();
274        let desired_result = "";
275        assert_eq!(unit_result, desired_result);
276    }
277
278    #[test]
279    fn unit_array_to_sentence_string_custom_connector() {
280        let input = liquid_core::value!(["foo", "bar", "baz"]);
281        let unit_result = liquid_core::call_filter!(ArrayToSentenceString, input, "or").unwrap();
282        let desired_result = "foo, bar, or baz";
283        assert_eq!(unit_result, desired_result);
284    }
285}