use crate::*;
use std::cmp::{min, Ordering};
use std::collections::HashMap;
use std::fmt::{Formatter, Result as FormatResult, Write};
#[cfg(feature = "json")]
pub(crate) fn to_pascal_case<T: AsRef<str>>(text: T) -> String {
let mut chars = text.as_ref().chars();
if let Some(first) = chars.next() {
first.to_uppercase().collect::<String>() + chars.as_str()
} else {
String::with_capacity(0)
}
}
#[cfg(feature = "cmd")]
pub(crate) fn to_pascal_case_parts<T: AsRef<str>>(text: T, sep: char) -> String {
let parts = text.as_ref().split(sep);
let mut pascal_case = String::with_capacity(text.as_ref().len());
for part in parts {
let mut chars = part.chars();
if let Some(first) = chars.next() {
pascal_case.push_str(&first.to_uppercase().to_string());
pascal_case.push_str(chars.as_str());
}
}
pascal_case
}
pub fn cmp_keys(key: &str, other_key: &str) -> Ordering {
let parts_1 = key
.split(ConfigurationPath::key_delimiter())
.filter(|s| s.is_empty())
.collect::<Vec<_>>();
let parts_2 = other_key
.split(ConfigurationPath::key_delimiter())
.filter(|s| s.is_empty())
.collect::<Vec<_>>();
let max = min(parts_1.len(), parts_2.len());
for i in 0..max {
let x = parts_1[i];
let y = parts_2[i];
if let Ok(value_1) = x.parse::<usize>() {
if let Ok(value_2) = y.parse::<usize>() {
let result = value_1.cmp(&value_2);
if result != Ordering::Equal {
return result;
}
} else {
return Ordering::Less;
}
} else if y.parse::<usize>().is_ok() {
return Ordering::Greater;
} else {
let result = x.to_uppercase().cmp(&y.to_uppercase());
if result != Ordering::Equal {
return result;
}
}
}
parts_1.len().cmp(&parts_2.len())
}
pub fn accumulate_child_keys(
data: &HashMap<String, (String, Value)>,
keys: &mut Vec<String>,
parent_path: Option<&str>,
) {
if let Some(path) = parent_path {
let parent_key = path.to_uppercase();
let parent_key_len = path.len();
let delimiter = ConfigurationPath::key_delimiter().chars().next().unwrap();
for (key, value) in data {
if key.len() > parent_key_len
&& key.starts_with(&parent_key)
&& key.chars().nth(parent_key_len).unwrap() == delimiter
{
keys.push(segment(&value.0, parent_key_len + 1).to_owned());
}
}
} else {
for value in data.values() {
keys.push(segment(&value.0, 0).to_owned());
}
}
keys.sort_by(|k1, k2| cmp_keys(k1, k2));
}
fn segment(key: &str, start: usize) -> &str {
let subkey = &key[start..];
if let Some(index) = subkey.find(ConfigurationPath::key_delimiter()) {
&subkey[..index]
} else {
subkey
}
}
pub fn fmt_debug_view<T>(root: &T, formatter: &mut Formatter<'_>) -> FormatResult
where
T: ConfigurationRoot,
{
recurse_children(root, &root.children(), formatter, "")
}
fn recurse_children<T: ConfigurationRoot>(
root: &T,
children: &[Box<dyn ConfigurationSection>],
formatter: &mut Formatter<'_>,
indent: &str,
) -> FormatResult {
for child in children {
formatter.write_str(indent)?;
formatter.write_str(child.key())?;
let mut found = false;
for provider in root.providers().rev() {
if let Some(value) = provider.get(child.path()) {
formatter.write_char('=')?;
formatter.write_str(&value)?;
formatter.write_str(" (")?;
formatter.write_str(provider.name())?;
formatter.write_char(')')?;
found = true;
break;
}
}
if !found {
formatter.write_char(':')?;
}
formatter.write_char('\n')?;
recurse_children(
root,
&child.children(),
formatter,
&(indent.to_owned() + " "),
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_pascal_case_should_normalize_argument_name() {
let argument = "noBuild";
let pascal_case = to_pascal_case(argument);
assert_eq!(pascal_case, "NoBuild");
}
#[test]
fn to_pascal_case_parts_should_normalize_argument_name() {
let argument = "no-build";
let pascal_case = to_pascal_case_parts(argument, '-');
assert_eq!(pascal_case, "NoBuild");
}
}