use crate::{pascal_case, Result, Settings};
use std::{borrow::Cow, collections::HashMap};
#[derive(Debug)]
pub struct Provider {
args: Vec<String>,
switch_mappings: HashMap<String, String>,
}
impl Provider {
pub fn new<I, V, S>(args: I, switch_mappings: &[(S, S)]) -> Self
where
I: Iterator<Item = V>,
V: AsRef<str>,
S: AsRef<str>,
{
Self {
args: args.map(|a| a.as_ref().to_owned()).collect(),
switch_mappings: switch_mappings
.iter()
.filter(|m| m.0.as_ref().starts_with("--") || m.0.as_ref().starts_with('-'))
.map(|(k, v)| (k.as_ref().to_uppercase(), v.as_ref().to_owned()))
.collect(),
}
}
}
impl crate::Provider for Provider {
#[inline]
fn name(&self) -> &str {
"Command Line"
}
fn load(&self, settings: &mut Settings) -> Result {
let mut args = self.args.iter();
while let Some(arg) = args.next() {
let mut current = Cow::Borrowed(arg);
let start: usize = if arg.starts_with("--") {
2
} else if arg.starts_with('-') {
1
} else if arg.starts_with('/') {
let mut temp = arg.clone();
temp.replace_range(0..1, "--");
current = Cow::Owned(temp);
2
} else {
0
};
let key;
let value;
if let Some(separator) = current.find('=') {
let segment: String = current
.chars()
.take(separator)
.map(|c| c.to_ascii_uppercase())
.collect();
key = if let Some(mapping) = self.switch_mappings.get(&segment) {
mapping.clone()
} else if start == 1 {
continue;
} else {
current.chars().skip(start).take(separator - start).collect()
};
value = current.chars().skip(separator + 1).collect();
} else {
if start == 0 {
continue;
}
key = if let Some(mapping) = self.switch_mappings.get(¤t.to_uppercase()) {
mapping.clone()
} else if start == 1 {
continue;
} else {
current.chars().skip(start).collect()
};
let Some(next) = args.next() else {
continue;
};
value = next.clone();
}
settings.insert(pascal_case(&key), value);
}
Ok(())
}
}
impl<I, V> From<I> for Provider
where
I: Iterator<Item = V>,
V: AsRef<str>,
{
#[inline]
fn from(value: I) -> Self {
Self::new(value, &Vec::<(&str, &str)>::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Provider as _;
#[test]
fn load_should_ignore_unknown_arguments() {
let args = ["foo", "/bar=baz"].iter();
let provider = Provider::from(args);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
println!("{settings:?}");
assert_eq!(settings.len(), 1);
assert_eq!(settings.get("bar"), Some("baz"));
}
#[test]
fn load_should_ignore_arguments_in_the_middle() {
let args = [
"Key1=Value1",
"--Key2=Value2",
"/Key3=Value3",
"Bogus1",
"--Key4",
"Value4",
"Bogus2",
"/Key5",
"Value5",
"Bogus3",
]
.iter();
let provider = Provider::from(args);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert_eq!(settings.get("Key1"), Some("Value1"));
assert_eq!(settings.get("Key2"), Some("Value2"));
assert_eq!(settings.get("Key3"), Some("Value3"));
assert_eq!(settings.get("Key4"), Some("Value4"));
assert_eq!(settings.get("Key5"), Some("Value5"));
}
#[test]
fn load_should_process_key_value_pairs_without_mappings() {
let args = [
"Key1=Value1",
"--Key2=Value2",
"/Key3=Value3",
"--Key4",
"Value4",
"/Key5",
"Value5",
"--single=1",
"--two-part=2",
]
.iter();
let provider = Provider::from(args);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert_eq!(settings.get("Key1"), Some("Value1"));
assert_eq!(settings.get("Key2"), Some("Value2"));
assert_eq!(settings.get("Key3"), Some("Value3"));
assert_eq!(settings.get("Key4"), Some("Value4"));
assert_eq!(settings.get("Key5"), Some("Value5"));
assert_eq!(settings.get("Single"), Some("1"));
assert_eq!(settings.get("TwoPart"), Some("2"));
}
#[test]
fn load_should_process_key_value_pairs_with_mappings() {
let args = [
"-K1=Value1",
"--Key2=Value2",
"/Key3=Value3",
"--Key4",
"Value4",
"/Key5",
"Value5",
"/Key6=Value6",
]
.iter();
let switch_mappings = [
("-K1", "LongKey1"),
("--Key2", "SuperLongKey2"),
("--Key6", "SuchALongKey6"),
];
let provider = Provider::new(args, &switch_mappings);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert_eq!(settings.get("LongKey1"), Some("Value1"));
assert_eq!(settings.get("SuperLongKey2"), Some("Value2"));
assert_eq!(settings.get("Key3"), Some("Value3"));
assert_eq!(settings.get("Key4"), Some("Value4"));
assert_eq!(settings.get("Key5"), Some("Value5"));
assert_eq!(settings.get("SuchALongKey6"), Some("Value6"));
}
#[test]
fn load_should_override_value_when_key_is_duplicated() {
let args = ["/Key1=Value1", "--Key1=Value2"].iter();
let provider = Provider::from(args);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert_eq!(settings.get("Key1"), Some("Value2"));
}
#[test]
fn load_should_ignore_key_when_value_is_missing() {
let args = ["--Key1", "Value1", "/Key2"].iter();
let provider = Provider::from(args);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert_eq!(settings.len(), 1);
assert_eq!(settings.get("Key1"), Some("Value1"));
}
#[test]
fn load_should_ignore_unrecognizable_argument() {
let args = ["ArgWithoutPrefixAndEqualSign"].iter();
let provider = Provider::from(args);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert!(settings.is_empty());
}
#[test]
fn load_should_ignore_argument_when_short_switch_is_undefined() {
let args = ["-Key1", "Value1"].iter();
let switch_mappings = [("-Key2", "LongKey2")];
let provider = Provider::new(args, &switch_mappings);
let mut settings = Settings::default();
provider.load(&mut settings).unwrap();
assert!(settings.is_empty());
}
}