clip_core/describe/
formatter.rs

1//SPDX-FileCopyrightText: 2024 Claire Bts <claxxx.bts@gmail.com>
2//SPDX-License-Identifier: GPL-3.0-or-later
3
4// clip aims to simplify writing cli and/or parser in general
5
6//Copyright (C) 2024 Claire Bts claxxx.bts@gmail.com
7
8//Clipv is free software: you can redistribute it and/or modify it under the terms of the
9//GNU General Public License as published by the Free Software Foundation, either version 3 of the
10//License, or (at your option) any later version.
11
12//Clipv is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
13//even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14//General Public License for more details.
15
16//You should have received a copy of the GNU General Public License along with Clipv. If
17//not, see <https://www.gnu.org/licenses/>.
18
19#[derive(Default)]
20pub struct Formatter<'a> {
21    pub very_start: Option<&'a str>,
22    pub very_end: Option<&'a str>,
23    pub start: Option<&'a str>,
24    pub end: Option<&'a str>,
25    pub middle: Option<&'a str>,
26    pub new_line_chars: Option<&'a str>,
27}
28
29/// Adds characters to each line of a string
30pub fn start_with(string: String, chars: &str) -> String {
31    let mut result = String::new();
32    for line in string.lines() {
33        result.push_str(format!("{chars}{line}\n").as_str());
34    }
35    result
36}
37
38pub fn start_other_lines_with(string: String, chars: &str) -> String {
39    let mut iterator = string.lines();
40    let mut result = String::new();
41    if let Some(first_line) = iterator.next() {
42        result.push_str(format!("{first_line}\n").as_str());
43    }
44    for line in iterator {
45        result.push_str(format!("{chars}{line}\n").as_str());
46    }
47    result
48}
49
50impl<'a> Formatter<'a> {
51    pub fn fmt<'b, Item: 'b, I: Iterator<Item = &'b Item>, F: FnMut(I::Item) -> Option<String>>(
52        &self,
53        args: I,
54        format_function: F,
55    ) -> String {
56        format!(
57            "{very_start}{description}{very_end}",
58            very_start = self.very_start.unwrap_or(""),
59            very_end = self.very_end.unwrap_or(""),
60            description = args.filter_map(format_function).fold(
61                "".to_string(),
62                |string: String, item: String| {
63                    format!(
64                        "{string}{middle}{start}{content}{end}",
65                        start = self.start.unwrap_or(""),
66                        content = if let Some(chars) = self.new_line_chars {
67                            start_other_lines_with(item, chars)
68                        } else { item },
69                        middle = if string.is_empty() {
70                            ""
71                        } else {
72                            self.middle.unwrap_or("")
73                        },
74                        end = self.end.unwrap_or("")
75                    )
76                }
77            )
78        )
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    #[test]
86    fn default_formatter() {
87        assert_eq!(
88            Formatter::default().fmt([1, 2, 3].iter(), |item| Some(item.to_string())),
89            String::from("123")
90        );
91    }
92
93    #[test]
94    fn it_should_fmt_with_start_and_end() {
95        assert_eq!(
96            Formatter {
97                start: Some("<"),
98                end: Some(">"),
99                ..Default::default()
100            }
101            .fmt([1, 2, 3].iter(), |item| Some(item.to_string())),
102            "<1><2><3>"
103        );
104    }
105
106    #[test]
107    fn it_should_fmt_with_middle_char() {
108        assert_eq!(
109            Formatter {
110                middle: Some(" "),
111                ..Default::default()
112            }
113            .fmt([1, 2, 3].iter(), |item| Some(item.to_string())),
114            "1 2 3"
115        );
116    }
117
118    #[test]
119    fn it_should_fmt_with_start_end_middle_char() {
120        assert_eq!(
121            Formatter {
122                start: Some("<"),
123                end: Some(">"),
124                middle: Some(" "),
125                very_start: Some("Result: "),
126                very_end: Some("."),
127                new_line_chars: None,
128            }
129            .fmt([1, 2, 3].iter(), |item| Some(item.to_string())),
130            "Result: <1> <2> <3>."
131        );
132    }
133
134    #[test]
135    fn it_should_fmt_and_filter_none_values() {
136        assert_eq!(
137            Formatter {
138                start: Some("<"),
139                end: Some(">"),
140                middle: Some(","),
141                ..Default::default()
142            }
143            .fmt([1, 2, 3, 4, 5, 4, 3, 5].iter(), |item| {
144                if item % 2 == 0 {
145                    Some(item.to_string())
146                } else {
147                    None
148                }
149            }),
150            "<2>,<4>,<4>"
151        );
152        assert_eq!(
153            Formatter {
154                start: Some("<"),
155                end: Some(">"),
156                middle: Some(","),
157                ..Default::default()
158            }
159            .fmt([1, 2, 3, 4, 5, 4, 3, 5].iter(), |item| {
160                if item % 2 != 0 {
161                    Some(item.to_string())
162                } else {
163                    None
164                }
165            }),
166            "<1>,<3>,<5>,<3>,<5>"
167        );
168    }
169}