curlyconf/
cfg.rs

1use std::collections::{HashMap, HashSet};
2use std::io::{self, Error as IoError, ErrorKind as Kind};
3
4use serde::de;
5use serde::Deserialize;
6
7use crate::de::{Deserializer, SECTION_CTX};
8use crate::error::Result;
9use crate::parser::Watcher;
10use crate::tokenizer::Mode;
11
12/// Read configuration from a string.
13///
14/// This uses the defaults (e.g. [`Mode::Semicolon`]). If you want to
15/// configure the configuration parser, use a [`Builder`] struct.
16pub fn from_str<T>(s: &str) -> Result<T>
17where
18    T: for<'de> Deserialize<'de>,
19{
20    Builder::new().from_str(s)
21}
22
23/// Read configuration from a file.
24///
25/// This uses the defaults (e.g. [`Mode::Semicolon`]). If you want to
26/// configure the configuration parser, use a [`Builder`] struct.
27pub fn from_file<T>(name: impl Into<String>) -> io::Result<T>
28where
29    T: for<'de> Deserialize<'de>,
30{
31    Builder::new().from_file(name)
32}
33
34/// Configuration builder.
35pub struct Builder {
36    mode: Mode,
37    aliases: HashMap<String, String>,
38    ignored: HashSet<String>,
39    watcher: Option<Watcher>,
40}
41
42impl Builder {
43    /// Return a new configuration builder.
44    pub fn new() -> Builder {
45        Builder {
46            mode: Mode::Semicolon,
47            aliases: HashMap::new(),
48            ignored: HashSet::new(),
49            watcher: None,
50        }
51    }
52
53    /// Set the mode of this configuration file parser:
54    /// - [`Mode::Semicolon`]: values must end in `;`.
55    /// - [`Mode::Newline`]: values end with a newline.
56    pub fn mode(mut self, mode: Mode) -> Builder {
57        self.mode = mode;
58        self
59    }
60
61    /// Add an alias for a section name or value name.
62    ///
63    /// This is much like the `#[serde(alias = "foo")]` attribute, but since we
64    /// need to know what aliases exist, and serde does not provide that
65    /// information, we need to implement the aliasing ourself.
66    pub fn alias<T>(mut self, alias: &str, section_name: &str) -> Builder {
67        let type_name = std::any::type_name::<T>().split(":").last().unwrap();
68        let type_dot_alias = format!("{}.{}", type_name, alias);
69        self.aliases
70            .insert(type_dot_alias, section_name.to_string());
71        self
72    }
73
74    /// Ignore a value (by name).
75    pub fn ignore<T>(mut self, key: &str) -> Builder {
76        let type_name = std::any::type_name::<T>().split(":").last().unwrap();
77        let type_dot_alias = format!("{}.{}", type_name, key);
78        self.ignored.insert(type_dot_alias);
79        self
80    }
81
82    /// Insert a [`Watcher`]
83    pub fn watcher(mut self, watcher: &Watcher) -> Builder {
84        self.watcher = Some(watcher.clone());
85        self
86    }
87
88    /// This concludes the building phase and reads the configuration from a string.
89    pub fn from_str<T>(self, text: &str) -> Result<T>
90    where
91        T: for<'de> Deserialize<'de>,
92    {
93        let mut deserializer =
94            Deserializer::from_str(text, self.mode, self.aliases, self.ignored, self.watcher);
95        T::deserialize(&mut deserializer)
96    }
97
98    /// This concludes the building phase and reads the configuration from a file.
99    pub fn from_file<T>(self, name: impl Into<String>) -> io::Result<T>
100    where
101        T: for<'de> Deserialize<'de>,
102    {
103        let mut deserializer =
104            Deserializer::from_file(name, self.mode, self.aliases, self.ignored, self.watcher)?;
105        T::deserialize(&mut deserializer)
106            .map_err(|e| IoError::new(Kind::Other, e))
107    }
108}
109
110/// Make it possible for `Deserialize` impls to access the state of the parser.
111///
112/// Automatically implemented on every `Deserializer` implementation.
113pub trait ParserAccess {
114    /// Get the name of the value that's currently being parsed.
115    ///
116    /// Useful in `Deserialize` implementations. You can have one field
117    /// with several aliases (see [`Builder::alias`]) and differentiate
118    /// the action in `Deserialize::deserialize` based on which alias it is.
119    ///
120    /// For example, a `groups: Vec<String>` value, with `addgroup` and
121    /// `delgroup` aliases.
122    fn value_name(&self) -> String {
123        SECTION_CTX.with(|ctx| ctx.borrow().subsection_name().to_string())
124    }
125
126    /// Get a direct static reference to the parser instead of via the Deserializer
127    /// handle so that it can be stored somewhere (usually in a visitor struct).
128    fn parser(&self) -> Parser {
129        Parser
130    }
131}
132
133impl<'de, T> ParserAccess for T where T: de::Deserializer<'de> {}
134
135/// Reference to the parser for `Deserialize` impls.
136///
137/// This struct, like `Deserialize` impls, implements the [`ParserAccess`]
138/// trait. It holds a global, static reference to the current parser.
139/// See [`ParserAccess::parser`].
140pub struct Parser;
141impl ParserAccess for Parser {}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use serde::Deserialize;
147
148    fn init() {
149        let _ = env_logger::builder().is_test(true).try_init();
150    }
151
152    #[test]
153    fn test_struct() {
154        init();
155
156        #[derive(Deserialize, PartialEq, Debug)]
157        struct Main {
158            test: Test,
159        }
160
161        #[derive(Deserialize, PartialEq, Debug)]
162        #[serde(rename = "test")]
163        struct Test {
164            __label__: String,
165            int: u32,
166            seq: Vec<String>,
167        }
168
169        let j = r#"test foo {
170            int 1;
171            seq a,"b";
172        }"#;
173        let expected = Main {
174            test: Test {
175                __label__: "foo".to_owned(),
176                int: 1,
177                seq: vec!["a".to_owned(), "b".to_owned()],
178            },
179        };
180        assert_eq!(expected, from_str(j).unwrap());
181    }
182}