Skip to main content

config/
section.rs

1use crate::{context, path, Configuration, Settings};
2use std::fmt::{self, Debug, Display, Formatter, Write};
3use tokens::{ChangeToken, NeverChangeToken};
4
5macro_rules! section {
6    ($self:ident) => {
7        /// Gets the key of the section.
8        #[inline]
9        pub fn key(&$self) -> &str {
10            path::last(&$self.path)
11        }
12
13        /// Gets the path of the section.
14        #[inline]
15        pub fn path(&$self) -> &str {
16            &$self.path
17        }
18
19        /// Gets the value of the section, if any.
20        #[inline]
21        pub fn value(&$self) -> &str {
22            $self.config.settings.get(&$self.path).unwrap_or_default()
23        }
24
25        /// Determines if the section exists.
26        ///
27        /// # Remarks
28        ///
29        /// A section exists if either its value or its subsections are not empty.
30        pub fn exists(&$self) -> bool {
31            !$self.value().is_empty() || !$self.sections().is_empty()
32        }
33
34        /// Gets a configuration value in this section.
35        ///
36        /// # Arguments
37        ///
38        /// * `key` - The case-insensitive key of the configuration value to get
39        #[inline]
40        pub fn get(&$self, key: &str) -> Option<&str> {
41            $self.config.settings.get_subkey(&$self.path, key)
42        }
43    };
44}
45
46macro_rules! diagnostic {
47    ($self:ty, $name:literal) => {
48        impl Debug for $self {
49            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50                f.debug_struct($name)
51                    .field("key", &self.key())
52                    .field("path", &self.path())
53                    .field("value", &self.value())
54                    .field("exists", &self.exists())
55                    .finish()
56            }
57        }
58
59        impl Display for $self {
60            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
61                let (value, providers) = self.config.settings.get_with_id(&self.path).unwrap_or_default();
62
63                f.write_str(self.key())?;
64
65                if value.is_empty() {
66                    f.write_char(':')?;
67                } else {
68                    f.write_str(" = ")?;
69                    f.write_str(value)?;
70                }
71
72                if providers > 0 && !self.config.providers.is_empty() && f.alternate() {
73                    f.write_str(" (")?;
74                    context::expand(providers, &self.config.providers, f)?;
75                    f.write_char(')')?;
76                }
77
78                Ok(())
79            }
80        }
81    };
82}
83
84fn collect_section_keys(config: &Configuration, parent: &str) -> Vec<String> {
85    let mut keys = Vec::new();
86
87    for (path, _) in config {
88        let Some(key) = path::next(path, Some(parent)) else {
89            continue;
90        };
91
92        if !keys.iter().any(|k: &String| k.eq_ignore_ascii_case(key)) {
93            keys.push(key.to_owned());
94        }
95    }
96
97    keys
98}
99
100/// Represents a [configuration](Configuration) section.
101#[derive(Clone)]
102pub struct Section<'a> {
103    config: &'a Configuration,
104    path: String,
105}
106
107impl<'a> Section<'a> {
108    #[inline]
109    pub(crate) fn new(config: &'a Configuration, path: String) -> Self {
110        Self { config, path }
111    }
112
113    /// Gets a configuration [subsection](Section) in this section.
114    ///
115    /// # Arguments
116    ///
117    /// * `key` - The case-insensitive key of the configuration subsection to get
118    #[inline]
119    pub fn section(&self, key: &str) -> Section<'a> {
120        self.config.section(path::combine(&[&self.path, key]))
121    }
122
123    /// Gets all of the [subsections](Section) in this section.
124    pub fn sections(&self) -> Vec<Section<'a>> {
125        collect_section_keys(self.config, &self.path)
126            .into_iter()
127            .map(|key| self.config.section(key))
128            .collect()
129    }
130
131    /// Gets an owned copy of the section.
132    ///
133    /// # Remarks
134    ///
135    /// This function is useful for taking ownership of a section in order to decouple it from the entire
136    /// [configuration](Configuration) that created it. The owned section holds a reference to the subset of key/value
137    /// pairs at this point in the [configuration](Configuration).
138    pub fn to_owned(&self) -> OwnedSection {
139        let len = self.path.len();
140        let token: Box<dyn ChangeToken> = Box::new(NeverChangeToken);
141        let mut settings = Settings::new();
142
143        for (key, value) in self.config {
144            if key.len() > len && path::starts_with(key, &self.path) {
145                settings.insert(key, value);
146            }
147        }
148
149        OwnedSection {
150            config: Configuration::new(settings, [token], self.config.providers.clone()),
151            path: self.path.clone(),
152        }
153    }
154
155    section!(self);
156}
157
158impl<'a> From<Section<'a>> for Vec<Section<'a>> {
159    #[inline]
160    fn from(section: Section<'a>) -> Self {
161        section.sections()
162    }
163}
164
165impl<'a> From<&'a Section<'a>> for Vec<Section<'a>> {
166    #[inline]
167    fn from(section: &'a Section<'a>) -> Self {
168        section.sections()
169    }
170}
171
172/// Represents an owned [configuration section](Section).
173#[derive(Clone)]
174pub struct OwnedSection {
175    config: Configuration,
176    path: String,
177}
178
179impl OwnedSection {
180    /// Gets a configuration [subsection](Section) in this section.
181    ///
182    /// # Arguments
183    ///
184    /// * `key` - The case-insensitive key of the configuration subsection to get
185    #[inline]
186    pub fn section(&self, key: &str) -> Section<'_> {
187        self.config.section(path::combine(&[&self.path, key]))
188    }
189
190    /// Gets all of the [subsections](Section) in this section.
191    pub fn sections(&self) -> Vec<Section<'_>> {
192        collect_section_keys(&self.config, &self.path)
193            .into_iter()
194            .map(|key| self.config.section(key))
195            .collect()
196    }
197
198    section!(self);
199}
200
201diagnostic!(Section<'_>, "Section");
202diagnostic!(OwnedSection, "OwnedSection");