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
34pub 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 let result = value_1.cmp(&value_2);
59
60 if result != Ordering::Equal {
61 return result;
62 }
63 } else {
64 return Ordering::Less;
66 }
67 } else if y.parse::<usize>().is_ok() {
68 return Ordering::Greater;
70 } else {
71 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
82pub 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
127pub 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 let argument = "noBuild";
190
191 let pascal_case = to_pascal_case(argument);
193
194 assert_eq!(pascal_case, "NoBuild");
196 }
197
198 #[test]
199 fn to_pascal_case_parts_should_normalize_argument_name() {
200 let argument = "no-build";
202
203 let pascal_case = to_pascal_case_parts(argument, '-');
205
206 assert_eq!(pascal_case, "NoBuild");
208 }
209}