liquid_lib/stdlib/filters/
slice.rs1use std::cmp;
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_argument;
12
13fn canonicalize_slice(
14 slice_offset: isize,
15 slice_length: isize,
16 vec_length: usize,
17) -> (usize, usize) {
18 let vec_length = vec_length as isize;
19
20 let slice_offset = cmp::min(slice_offset, vec_length);
22 let slice_offset = if slice_offset < 0 {
24 slice_offset + vec_length
25 } else {
26 slice_offset
27 };
28
29 let slice_length = if slice_offset + slice_length > vec_length {
31 vec_length - slice_offset
32 } else {
33 slice_length
34 };
35
36 (slice_offset as usize, slice_length as usize)
37}
38
39#[derive(Debug, FilterParameters)]
40struct SliceArgs {
41 #[parameter(description = "The offset of the slice.", arg_type = "integer")]
42 offset: Expression,
43
44 #[parameter(description = "The length of the slice.", arg_type = "integer")]
45 length: Option<Expression>,
46}
47
48#[derive(Clone, ParseFilter, FilterReflection)]
49#[filter(
50 name = "slice",
51 description = "Takes a slice of a given string or array.",
52 parameters(SliceArgs),
53 parsed(SliceFilter)
54)]
55pub struct Slice;
56
57#[derive(Debug, FromFilterParameters, Display_filter)]
58#[name = "slice"]
59struct SliceFilter {
60 #[parameters]
61 args: SliceArgs,
62}
63
64impl Filter for SliceFilter {
65 fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
66 let args = self.args.evaluate(runtime)?;
67
68 let offset = args.offset as isize;
69 let length = args.length.unwrap_or(1) as isize;
70
71 if length < 1 {
72 return invalid_argument("length", "Positive number expected").into_err();
73 }
74
75 if let Some(input) = input.as_array() {
76 let (offset, length) = canonicalize_slice(offset, length, input.size() as usize);
77 Ok(Value::array(
78 input
79 .values()
80 .skip(offset)
81 .take(length)
82 .map(|s| s.to_value()),
83 ))
84 } else {
85 let input = input.to_kstr();
86 let (offset, length) = canonicalize_slice(offset, length, input.len());
87 Ok(Value::scalar(
88 input.chars().skip(offset).take(length).collect::<String>(),
89 ))
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn unit_slice() {
100 assert_eq!(
101 liquid_core::call_filter!(
102 Slice,
103 "I often quote myself. It adds spice to my conversation.",
104 2,
105 3
106 )
107 .unwrap(),
108 liquid_core::value!("oft")
109 );
110 }
111
112 #[test]
113 fn unit_slice_no_length_specified() {
114 assert_eq!(
115 liquid_core::call_filter!(
116 Slice,
117 "I often quote myself. It adds spice to my conversation.",
118 4
119 )
120 .unwrap(),
121 liquid_core::value!("t")
122 );
123 }
124
125 #[test]
126 fn unit_slice_negative_offset() {
127 assert_eq!(
128 liquid_core::call_filter!(
129 Slice,
130 "I often quote myself. It adds spice to my conversation.",
131 -10,
132 3
133 )
134 .unwrap(),
135 liquid_core::value!("ver")
136 );
137 }
138
139 #[test]
140 fn unit_slice_non_positive_length() {
141 liquid_core::call_filter!(
142 Slice,
143 "I often quote myself. It adds spice to my conversation.",
144 -10,
145 0
146 )
147 .unwrap_err();
148 liquid_core::call_filter!(
149 Slice,
150 "I often quote myself. It adds spice to my conversation.",
151 -10,
152 -1
153 )
154 .unwrap_err();
155 }
156}