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}