squidge/
lib.rs

1/*!
2This crate provides functionality to shorten delimited data based on a given configuration.
3
4Here's a quick example showing its usage:
5
6```
7use squidge::{Config, shorten_line};
8
9let line = "module/submodule/service/lib.rs";
10let result = shorten_line(&Config::default(), &line);
11let expected = vec!["m", "s", "s", "lib.rs"];
12assert_eq!(result, expected);
13```
14*/
15
16use regex::Regex;
17
18/// Represents the config used by squidge.
19///
20/// Example usage:
21/// ```
22/// use squidge::Config;
23/// use regex::Regex;
24///
25/// let re = Regex::new("module").unwrap();
26/// let cfg = Config {
27///     delimiter: "\\",
28///     ignore_first_n: 2,
29///     ignore_last_n: 2,
30///     ignore_regex: Some(re),
31/// };
32/// ```
33#[derive(Debug)]
34pub struct Config<'a> {
35    /// Delimiter to split the line on
36    pub delimiter: &'a str,
37    /// Number of elements to ignore (for shortening) from the start
38    pub ignore_first_n: usize,
39    /// Number of elements to ignore (for shortening) from the end
40    pub ignore_last_n: usize,
41    /// Optional regex to determine which components to ignore while shortening
42    pub ignore_regex: Option<Regex>,
43}
44
45impl<'a> Default for Config<'a> {
46    fn default() -> Self {
47        Config {
48            delimiter: "/",
49            ignore_first_n: 0,
50            ignore_last_n: 1,
51            ignore_regex: None,
52        }
53    }
54}
55
56/// Shortens a line based on the provided configuration and returns the components as a `Vec<String>`.
57///
58/// Example:
59/// ```
60/// use regex::Regex;
61/// use squidge::{Config, shorten_line};
62///
63/// let line = "/path/to/a/module/submodule/service/lib.rs";
64/// let re = Regex::new("module").unwrap();
65/// let cfg = Config {
66///     ignore_first_n: 2,
67///     ignore_last_n: 2,
68///     ignore_regex: Some(re),
69///     ..Config::default()
70/// };
71/// let result = shorten_line(&cfg, line);
72/// let expected = vec![
73///     "",
74///     "path",
75///     "t",
76///     "a",
77///     "module",
78///     "submodule",
79///     "service",
80///     "lib.rs",
81/// ];
82/// assert_eq!(result, expected);
83/// ```
84///
85pub fn shorten_line(cfg: &Config, line: &str) -> Vec<String> {
86    let num_elements = line.matches(cfg.delimiter).count();
87    let line_iter = line.split(cfg.delimiter);
88    let mut shortened_elements: Vec<String> = Vec::new();
89
90    for (i, component) in line_iter.enumerate() {
91        if i < cfg.ignore_first_n
92            || (cfg.ignore_last_n > num_elements || i > num_elements - cfg.ignore_last_n)
93        {
94            shortened_elements.push(component.to_string());
95            continue;
96        }
97
98        let shorten_element = match cfg.ignore_regex {
99            Some(ref r) => match r.is_match(component) {
100                true => false,
101                false => true,
102            },
103            None => true,
104        };
105
106        let shortened_element = match shorten_element {
107            true => match component.chars().next() {
108                Some(c) => c.to_string(),
109                None => String::new(),
110            },
111            false => component.to_string(),
112        };
113
114        shortened_elements.push(shortened_element);
115    }
116    shortened_elements
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn shorten_line_works_with_default_config() {
125        // GIVEN
126        let line = "module/submodule/service/lib.rs";
127
128        // WHEN
129        let result = shorten_line(&Config::default(), line);
130
131        // THEN
132        let expected = vec!["m", "s", "s", "lib.rs"];
133        assert_eq!(result, expected);
134    }
135
136    #[test]
137    fn shorten_line_works_with_a_starting_delimiter() {
138        // GIVEN
139        let line = "/module/submodule/service/lib.rs";
140
141        // WHEN
142        let result = shorten_line(&Config::default(), line);
143
144        // THEN
145        let expected = vec!["", "m", "s", "s", "lib.rs"];
146        assert_eq!(result, expected);
147    }
148
149    #[test]
150    fn shorten_line_respects_delimiter() {
151        // GIVEN
152        let line = "module,submodule,service,lib.rs";
153        let cfg = Config {
154            delimiter: ",",
155            ..Config::default()
156        };
157
158        // WHEN
159        let result = shorten_line(&cfg, line);
160
161        // THEN
162        let expected = vec!["m", "s", "s", "lib.rs"];
163        assert_eq!(result, expected);
164    }
165
166    #[test]
167    fn shorten_line_ignores_components_matching_regex() {
168        // GIVEN
169        let line = "module/submodule/service/lib.rs";
170        let re = Regex::new("module").unwrap();
171        let cfg = Config {
172            ignore_regex: Some(re),
173            ..Config::default()
174        };
175
176        // WHEN
177        let result = shorten_line(&cfg, line);
178
179        // THEN
180        let expected = vec!["module", "submodule", "s", "lib.rs"];
181        assert_eq!(result, expected);
182    }
183
184    #[test]
185    fn shorten_line_ignores_last_n_components() {
186        // GIVEN
187        let line = "module/submodule/service/lib.rs";
188        let cfg = Config {
189            ignore_last_n: 3,
190            ..Config::default()
191        };
192
193        // WHEN
194        let result = shorten_line(&cfg, line);
195
196        // THEN
197        let expected = vec!["m", "submodule", "service", "lib.rs"];
198        assert_eq!(result, expected);
199    }
200
201    #[test]
202    fn shorten_line_works_when_ignore_last_n_is_greater_than_num_components() {
203        // GIVEN
204        let line = "module/submodule/service/lib.rs";
205        let cfg = Config {
206            ignore_last_n: 6,
207            ..Config::default()
208        };
209
210        // WHEN
211        let result = shorten_line(&cfg, line);
212
213        // THEN
214        let expected = vec!["module", "submodule", "service", "lib.rs"];
215        assert_eq!(result, expected);
216    }
217
218    #[test]
219    fn shorten_line_ignores_first_n_components() {
220        // GIVEN
221        let line = "module/submodule/service/lib.rs";
222        let cfg = Config {
223            ignore_first_n: 1,
224            ..Config::default()
225        };
226
227        // WHEN
228        let result = shorten_line(&cfg, line);
229
230        // THEN
231        let expected = vec!["module", "s", "s", "lib.rs"];
232        assert_eq!(result, expected);
233    }
234
235    #[test]
236    fn shorten_line_works_when_ignore_first_n_is_greater_than_num_components() {
237        // GIVEN
238        let line = "module/submodule/service/lib.rs";
239        let cfg = Config {
240            ignore_first_n: 6,
241            ..Config::default()
242        };
243
244        // WHEN
245        let result = shorten_line(&cfg, line);
246
247        // THEN
248        let expected = vec!["module", "submodule", "service", "lib.rs"];
249        assert_eq!(result, expected);
250    }
251
252    #[test]
253    fn shorten_line_ignores_first_n_and_last_m_components() {
254        // GIVEN
255        let line = "module/submodule/service/lib.rs";
256        let cfg = Config {
257            ignore_first_n: 1,
258            ignore_last_n: 2,
259            ..Config::default()
260        };
261
262        // WHEN
263        let result = shorten_line(&cfg, line);
264
265        // THEN
266        let expected = vec!["module", "s", "service", "lib.rs"];
267        assert_eq!(result, expected);
268    }
269
270    #[test]
271    fn shorten_line_works_when_a_component_is_empty() {
272        // GIVEN
273        let line = "module//service/lib.rs";
274        let result = shorten_line(&Config::default(), line);
275
276        // WHEN
277        let expected = vec!["m", "", "s", "lib.rs"];
278        assert_eq!(result, expected);
279    }
280
281    #[test]
282    fn shorten_line_works_with_non_default_config() {
283        // GIVEN
284        let line = "/path/to/a/module/submodule/service/lib.rs";
285        let re = Regex::new("module").unwrap();
286        let cfg = Config {
287            ignore_first_n: 2,
288            ignore_last_n: 2,
289            ignore_regex: Some(re),
290            ..Config::default()
291        };
292
293        // WHEN
294        let result = shorten_line(&cfg, line);
295
296        // THEN
297        let expected = vec![
298            "",
299            "path",
300            "t",
301            "a",
302            "module",
303            "submodule",
304            "service",
305            "lib.rs",
306        ];
307        assert_eq!(result, expected);
308    }
309
310    #[test]
311    fn shorten_line_works_with_line_that_is_not_delimited() {
312        // GIVEN
313        let line = "/path/to/a/module/submodule/service/lib.rs";
314        let cfg = Config {
315            delimiter: ":",
316            ..Config::default()
317        };
318
319        // WHEN
320        let result = shorten_line(&cfg, line);
321
322        // THEN
323        let expected = vec!["/path/to/a/module/submodule/service/lib.rs"];
324        assert_eq!(result, expected);
325    }
326
327    #[test]
328    fn shorten_line_works_with_empty_delimiter() {
329        // GIVEN
330        let line = "/path/to/lib.rs";
331        let cfg = Config {
332            delimiter: "",
333            ..Config::default()
334        };
335
336        // WHEN
337        let result = shorten_line(&cfg, line);
338
339        // THEN
340        let expected = vec![
341            "", "/", "p", "a", "t", "h", "/", "t", "o", "/", "l", "i", "b", ".", "r", "s", "",
342        ];
343        assert_eq!(result, expected);
344    }
345
346    #[test]
347    fn shorten_line_works_line_with_delimiters_only() {
348        // GIVEN
349        let line = "/////";
350
351        // WHEN
352        let result = shorten_line(&Config::default(), line);
353
354        // THEN
355        let expected = vec!["", "", "", "", "", ""];
356        assert_eq!(result, expected);
357    }
358}