config/
util.rs

1use crate::*;
2use std::cmp::{min, Ordering};
3use std::collections::HashMap;
4use std::fmt::{Formatter, Result as FormatResult, Write};
5
6#[cfg(feature = "json")]
7pub(crate) fn to_pascal_case<T: AsRef<str>>(text: T) -> String {
8    let mut chars = text.as_ref().chars();
9
10    if let Some(first) = chars.next() {
11        first.to_uppercase().collect::<String>() + chars.as_str()
12    } else {
13        String::with_capacity(0)
14    }
15}
16
17#[cfg(feature = "cmd")]
18pub(crate) fn to_pascal_case_parts<T: AsRef<str>>(text: T, sep: char) -> String {
19    let parts = text.as_ref().split(sep);
20    let mut pascal_case = String::with_capacity(text.as_ref().len());
21
22    for part in parts {
23        let mut chars = part.chars();
24
25        if let Some(first) = chars.next() {
26            pascal_case.push_str(&first.to_uppercase().to_string());
27            pascal_case.push_str(chars.as_str());
28        }
29    }
30
31    pascal_case
32}
33
34/// Compares two configuration keys.
35///
36/// # Arguments
37///
38/// * `key` - The key to compare
39/// * `other_key` - The key to compare against
40pub fn cmp_keys(key: &str, other_key: &str) -> Ordering {
41    let parts_1 = key
42        .split(ConfigurationPath::key_delimiter())
43        .filter(|s| s.is_empty())
44        .collect::<Vec<_>>();
45    let parts_2 = other_key
46        .split(ConfigurationPath::key_delimiter())
47        .filter(|s| s.is_empty())
48        .collect::<Vec<_>>();
49    let max = min(parts_1.len(), parts_2.len());
50
51    for i in 0..max {
52        let x = parts_1[i];
53        let y = parts_2[i];
54
55        if let Ok(value_1) = x.parse::<usize>() {
56            if let Ok(value_2) = y.parse::<usize>() {
57                // int : int
58                let result = value_1.cmp(&value_2);
59
60                if result != Ordering::Equal {
61                    return result;
62                }
63            } else {
64                // int : string
65                return Ordering::Less;
66            }
67        } else if y.parse::<usize>().is_ok() {
68            // string : int
69            return Ordering::Greater;
70        } else {
71            // string : string
72            let result = x.to_uppercase().cmp(&y.to_uppercase());
73
74            if result != Ordering::Equal {
75                return result;
76            }
77        }
78    }
79    parts_1.len().cmp(&parts_2.len())
80}
81
82/// Accumulates child keys based on the specified hash map.
83///
84/// # Arguments
85///
86/// * `data` - The source hash map to accumulate keys from where the key is normalized to uppercase
87///            and the value is a tuple containing the originally cased key and value
88/// * `keys` - The accumulated keys
89/// * `parent_path` - The parent path
90pub fn accumulate_child_keys(
91    data: &HashMap<String, (String, Value)>,
92    keys: &mut Vec<String>,
93    parent_path: Option<&str>,
94) {
95    if let Some(path) = parent_path {
96        let parent_key = path.to_uppercase();
97        let parent_key_len = path.len();
98        let delimiter = ConfigurationPath::key_delimiter().chars().next().unwrap();
99
100        for (key, value) in data {
101            if key.len() > parent_key_len
102                && key.starts_with(&parent_key)
103                && key.chars().nth(parent_key_len).unwrap() == delimiter
104            {
105                keys.push(segment(&value.0, parent_key_len + 1).to_owned());
106            }
107        }
108    } else {
109        for value in data.values() {
110            keys.push(segment(&value.0, 0).to_owned());
111        }
112    }
113
114    keys.sort_by(|k1, k2| cmp_keys(k1, k2));
115}
116
117fn segment(key: &str, start: usize) -> &str {
118    let subkey = &key[start..];
119
120    if let Some(index) = subkey.find(ConfigurationPath::key_delimiter()) {
121        &subkey[..index]
122    } else {
123        subkey
124    }
125}
126
127/// Formats a debug view of an entire configuration hierarchy.
128///
129/// # Arguments
130///
131/// * `root` - The [`ConfigurationRoot`] to format
132/// * `formatter` - The formatter used to output the configuration
133pub fn fmt_debug_view<T>(root: &T, formatter: &mut Formatter<'_>) -> FormatResult
134where
135    T: ConfigurationRoot,
136{
137    recurse_children(root, &root.children(), formatter, "")
138}
139
140fn recurse_children<T: ConfigurationRoot>(
141    root: &T,
142    children: &[Box<dyn ConfigurationSection>],
143    formatter: &mut Formatter<'_>,
144    indent: &str,
145) -> FormatResult {
146    for child in children {
147        formatter.write_str(indent)?;
148        formatter.write_str(child.key())?;
149
150        let mut found = false;
151
152        for provider in root.providers().rev() {
153            if let Some(value) = provider.get(child.path()) {
154                formatter.write_char('=')?;
155                formatter.write_str(&value)?;
156                formatter.write_str(" (")?;
157                formatter.write_str(provider.name())?;
158                formatter.write_char(')')?;
159                found = true;
160                break;
161            }
162        }
163
164        if !found {
165            formatter.write_char(':')?;
166        }
167
168        formatter.write_char('\n')?;
169
170        recurse_children(
171            root,
172            &child.children(),
173            formatter,
174            &(indent.to_owned() + "  "),
175        )?;
176    }
177
178    Ok(())
179}
180
181#[cfg(test)]
182mod tests {
183
184    use super::*;
185
186    #[test]
187    fn to_pascal_case_should_normalize_argument_name() {
188        // arrange
189        let argument = "noBuild";
190
191        // act
192        let pascal_case = to_pascal_case(argument);
193
194        // assert
195        assert_eq!(pascal_case, "NoBuild");
196    }
197
198    #[test]
199    fn to_pascal_case_parts_should_normalize_argument_name() {
200        // arrange
201        let argument = "no-build";
202
203        // act
204        let pascal_case = to_pascal_case_parts(argument, '-');
205
206        // assert
207        assert_eq!(pascal_case, "NoBuild");
208    }
209}