gesp/format/
mod.rs

1use std::collections::HashMap;
2use std::hash::Hash;
3use std::str::FromStr;
4
5/// Define an ESP for the ESP format syntax
6mod esp;
7mod parser;
8
9#[cfg(feature = "yaml")]
10pub use esp::formats;
11
12#[cfg_attr(feature = "yaml", derive(serde::Deserialize))]
13#[cfg_attr(feature = "yaml", serde(try_from = "Option<String>"))]
14#[derive(Debug, Clone)]
15pub struct PossibleFormat<P: Placeholder> {
16    pub format: Option<Format<P>>,
17}
18
19impl<P: Placeholder> std::convert::TryFrom<Option<String>> for PossibleFormat<P> {
20    type Error = nom::Err<(String, nom::error::ErrorKind)>;
21
22    fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
23        match value {
24            Some(x) => std::convert::TryFrom::try_from(x).map(|format| Self {
25                format: Some(format),
26            }),
27            None => Ok(Self { format: None }),
28        }
29    }
30}
31
32impl<P: Placeholder + Hash + Eq + Clone> PossibleFormat<P> {
33    pub fn filled_in_string(
34        &self,
35        feature_name: &str,
36        placeholders: &HashMap<P, Vec<String>>,
37    ) -> Result<String, FillInError<P>> {
38        if let Some(format) = self.format.as_ref() {
39            format.filled_in_string(placeholders)
40        } else {
41            Err(FillInError::FeatureNotSetUp(feature_name.to_string()))
42        }
43    }
44}
45
46impl<P: Placeholder + Hash + Eq + Clone> PossibleFormat<P> {
47    pub fn fill_in<W: std::fmt::Write>(
48        &self,
49        f: &mut W,
50        feature_name: &str,
51        placeholders: &HashMap<P, Vec<String>>,
52    ) -> Result<(), FillInError<P>> {
53        if let Some(format) = self.format.as_ref() {
54            format.fill_in(f, placeholders)
55        } else {
56            Err(FillInError::FeatureNotSetUp(feature_name.to_string()))
57        }
58    }
59}
60
61impl<P: Placeholder + Clone> PossibleFormat<P> {
62    pub fn combine(self, other: &Self) -> Self {
63        if let Some(format) = self.format {
64            Self {
65                format: Some(format),
66            }
67        } else {
68            other.clone()
69        }
70    }
71}
72
73impl<P: Placeholder> PossibleFormat<P> {
74    pub fn empty() -> Self {
75        Self { format: None }
76    }
77}
78
79#[derive(PartialEq, Debug, Clone)]
80#[cfg_attr(feature = "yaml", derive(serde::Deserialize))]
81#[cfg_attr(feature = "yaml", serde(try_from = "String"))]
82pub struct Format<P: Placeholder> {
83    pub items: Vec<FormatItem<P>>,
84}
85
86impl<P: Placeholder> FromStr for Format<P> {
87    type Err = nom::Err<(String, nom::error::ErrorKind)>;
88
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        parser::format::<P>(s)
91            .map(|x| x.1)
92            .map_err(|x| x.map(|y| (y.input.to_string(), y.code)))
93    }
94}
95
96impl<P: Placeholder> std::convert::TryFrom<String> for Format<P> {
97    type Error = nom::Err<(String, nom::error::ErrorKind)>;
98    fn try_from(value: String) -> Result<Self, Self::Error> {
99        Self::from_str(&value)
100    }
101}
102
103impl<P: Placeholder> std::convert::From<Format<P>> for PossibleFormat<P> {
104    fn from(value: Format<P>) -> Self {
105        Self {
106            format: Some(value),
107        }
108    }
109}
110
111impl<P: Placeholder + Hash + Eq + Clone> Format<P> {
112    pub fn filled_in_string(
113        &self,
114        placeholders: &HashMap<P, Vec<String>>,
115    ) -> Result<String, FillInError<P>> {
116        let mut s = String::new();
117        self.fill_in(&mut s, placeholders)?;
118        Ok(s)
119    }
120}
121
122impl<P: Placeholder + Hash + Eq + Clone> Format<P> {
123    pub fn fill_in<W: std::fmt::Write>(
124        &self,
125        f: &mut W,
126        placeholders: &HashMap<P, Vec<String>>,
127    ) -> Result<(), FillInError<P>> {
128        for item in self.items.iter() {
129            item.fill_in(f, placeholders)?;
130        }
131        Ok(())
132    }
133}
134
135#[derive(PartialEq, Debug, Clone)]
136pub enum FormatItem<P: Placeholder> {
137    // Cannot contain newlines and spaces
138    Word(String),
139    Placeholder(P),
140    PlaceholderList {
141        placeholder: P,
142        item_format: Format<P>,
143        join_format: Format<P>,
144    },
145    EscapedChar(FormatEscapeChar),
146    Indented(Format<P>),
147}
148
149#[cfg(test)]
150impl<P: Placeholder> FormatItem<P> {
151    pub fn from_sentence(s: &str) -> Vec<Self> {
152        let items = s.split(" ");
153        let mut result = Vec::new();
154        for (idx, item) in items.enumerate() {
155            if idx > 0 {
156                result.push(Self::EscapedChar(FormatEscapeChar::Space));
157            }
158            result.push(Self::Word(item.to_string()))
159        }
160        result
161    }
162}
163
164fn indent(s: String, nb_spaces: usize) -> String {
165    s.split("\n")
166        .map(|line| {
167            let spaces = " ".repeat(nb_spaces);
168            format!("{}{}", spaces, line)
169        })
170        .collect::<Vec<_>>()
171        .join("\n")
172}
173
174#[derive(Debug)]
175pub enum FillInError<Placeholder> {
176    Fmt(std::fmt::Error),
177    MissingPlaceholder(Placeholder),
178    PlaceholderCouldNotBeSubstituted(Placeholder),
179    PlaceholderGotMultipleItems(Placeholder),
180    FeatureNotSetUp(String),
181}
182
183impl<P: Placeholder + Hash + Eq + Clone> FormatItem<P> {
184    pub fn fill_in<W: std::fmt::Write>(
185        &self,
186        f: &mut W,
187        placeholders: &HashMap<P, Vec<String>>,
188    ) -> Result<(), FillInError<P>> {
189        match self {
190            FormatItem::Word(w) => write!(f, "{}", w).map_err(FillInError::Fmt),
191            FormatItem::Placeholder(p) => {
192                if let Some(v) = placeholders.get(p) {
193                    if let Some(item) = v.get(0) {
194                        write!(f, "{}", item).map_err(FillInError::Fmt)
195                    } else if v.is_empty() {
196                        Err(FillInError::PlaceholderCouldNotBeSubstituted(p.clone()))
197                    } else {
198                        Err(FillInError::PlaceholderGotMultipleItems(p.clone()))
199                    }
200                } else {
201                    Err(FillInError::MissingPlaceholder(p.clone()))
202                }
203            }
204            FormatItem::PlaceholderList {
205                placeholder: p,
206                item_format,
207                join_format,
208            } => {
209                if let Some(v) = placeholders.get(p) {
210                    if v.is_empty() {
211                        Err(FillInError::PlaceholderCouldNotBeSubstituted(p.clone()))
212                    } else {
213                        for (idx, item) in v.iter().enumerate() {
214                            // Write intermediate formatting
215                            if idx > 0 {
216                                join_format.fill_in(f, &Default::default())?;
217                            }
218                            item_format.fill_in(
219                                f,
220                                &vec![(p.clone(), vec![item.clone()])].into_iter().collect(),
221                            )?;
222                        }
223                        Ok(())
224                    }
225                } else {
226                    Err(FillInError::MissingPlaceholder(p.clone()))
227                }
228            }
229            FormatItem::EscapedChar(format_escape_char) => {
230                format_escape_char.fill_in(f).map_err(FillInError::Fmt)
231            }
232            FormatItem::Indented(format) => {
233                let mut s = String::new();
234                format.fill_in(&mut s, placeholders)?;
235                write!(f, "{}", indent(s, 4)).map_err(FillInError::Fmt)
236            }
237        }
238        .map(|_| ())
239    }
240}
241
242#[derive(PartialEq, Debug, Clone, Copy)]
243pub enum FormatEscapeChar {
244    /// Represents '\n'
245    Newline,
246    /// Represents '\ '
247    Space,
248    /// Represent '\\'
249    Slash,
250    /// Represents '\&'
251    Ampersand,
252    /// Represents '\$'
253    Dollar,
254    /// Represents '\>>>>'
255    Indent,
256    /// Represents '\<<<<'
257    Dedent,
258}
259
260impl FormatEscapeChar {
261    pub fn fill_in<W: std::fmt::Write>(&self, f: &mut W) -> Result<(), std::fmt::Error> {
262        match self {
263            FormatEscapeChar::Newline => f.write_char('\n'),
264            FormatEscapeChar::Space => f.write_char(' '),
265            FormatEscapeChar::Slash => f.write_char('\\'),
266            FormatEscapeChar::Ampersand => f.write_char('&'),
267            FormatEscapeChar::Dollar => f.write_char('$'),
268            FormatEscapeChar::Indent => f.write_str(">>>>"),
269            FormatEscapeChar::Dedent => f.write_str("<<<<"),
270        }
271        .map(|_| ())
272    }
273}
274
275#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
276#[cfg_attr(feature = "yaml", derive(serde::Deserialize))]
277pub struct EmptyPlaceholder;
278
279impl std::fmt::Display for EmptyPlaceholder {
280    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        Ok(())
282    }
283}
284
285impl<K> crate::config::Formattable<K> for EmptyPlaceholder {
286    fn format(&self, _formatting: &K) -> Result<String, crate::config::PrettyPrintError> {
287        Ok(String::new())
288    }
289}
290
291impl Placeholder for EmptyPlaceholder {
292    fn parser<'a>(input: &'a str) -> nom::IResult<&'a str, Self> {
293        nom::Parser::parse(
294            &mut nom::combinator::fail::<_, EmptyPlaceholder, _>(),
295            input,
296        )
297    }
298
299    fn name(&self) -> &'static str {
300        // should be unreachable
301        "no placeholders allowed here"
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn test_escape_chars() {
311        let mut s = String::new();
312        FormatEscapeChar::Ampersand.fill_in(&mut s).unwrap();
313        assert_eq!(s, "&");
314        FormatEscapeChar::Dollar.fill_in(&mut s).unwrap();
315        assert_eq!(s, "&$");
316        FormatEscapeChar::Newline.fill_in(&mut s).unwrap();
317        assert_eq!(s, "&$\n");
318        FormatEscapeChar::Slash.fill_in(&mut s).unwrap();
319        assert_eq!(s, "&$\n\\");
320        FormatEscapeChar::Space.fill_in(&mut s).unwrap();
321        assert_eq!(s, "&$\n\\ ");
322        FormatEscapeChar::Indent.fill_in(&mut s).unwrap();
323        assert_eq!(s, "&$\n\\ >>>>");
324        FormatEscapeChar::Dedent.fill_in(&mut s).unwrap();
325        assert_eq!(s, "&$\n\\ >>>><<<<");
326    }
327}
328
329pub trait Placeholder: Sized {
330    fn parser<'a>(input: &'a str) -> nom::IResult<&'a str, Self>;
331
332    fn name(&self) -> &'static str;
333}