use std::collections::HashMap;
use std::str::FromStr;
#[derive(Clone, Debug, Default)]
pub struct CliBuilder<'a> {
options: &'a [&'a str],
paramopts: &'a [&'a str],
}
impl<'a> CliBuilder<'a> {
pub fn new() -> Self {
Self {
options: &[],
paramopts: &[],
}
}
pub fn options(mut self, options: &'a [&str]) -> Self {
self.options = options;
self
}
pub fn paramopts(mut self, paramopts: &'a [&str]) -> Self {
self.paramopts = paramopts;
self
}
pub fn build(self, raw: &'a [String]) -> Command<'a> {
let command = if raw.is_empty() { "" } else { &raw[0] };
let mut parameters: HashMap<&str, &str> = HashMap::new();
let mut options: Vec<&str> = Vec::new();
let mut arguments: Vec<&str> = Vec::new();
let mut parameter = "";
let mut is_parameter = false;
for (index, argument) in raw.iter().enumerate() {
if index == 0 {
continue;
}
if is_parameter {
parameters.insert(parameter, argument);
parameter = "";
is_parameter = false;
} else {
let process_split = |parameters: &mut HashMap<&'a str, &'a str>,
parameter: &mut &'a str,
is_parameter: &mut bool,
argument: &'a str| {
let splits = argument.splitn(2, '=');
for split in splits {
if *is_parameter {
parameters.insert(parameter, split);
*is_parameter = false;
} else {
*parameter = split;
*is_parameter = true;
}
}
};
if let Some(cut) = argument.strip_prefix("--") {
if self.options.contains(&cut)
|| self.paramopts.contains(&cut)
&& raw
.get(index + 1)
.unwrap_or(&String::new())
.starts_with('-')
{
options.push(cut);
continue;
}
process_split(&mut parameters, &mut parameter, &mut is_parameter, cut);
} else if let Some(cut) = argument.strip_prefix('-') {
if self.options.contains(&cut)
|| self.paramopts.contains(&cut)
&& raw
.get(index + 1)
.unwrap_or(&String::new())
.starts_with('-')
{
options.push(cut);
continue;
} else if cut.len() >= 2 && !cut.contains('=') {
for i in 0..cut.len() {
options.push(match cut.get(i..=i) {
Some(option) => option,
None => continue,
});
}
continue;
} else if cut == "-" {
arguments.push(cut);
continue;
}
process_split(&mut parameters, &mut parameter, &mut is_parameter, cut);
} else {
arguments.push(argument);
}
}
}
if is_parameter {
options.push(parameter);
}
Command::create(command, parameters, options, arguments)
}
}
#[derive(Clone, Debug)]
pub struct Command<'a> {
command: &'a str,
parameters: HashMap<&'a str, &'a str>,
options: Vec<&'a str>,
arguments: Vec<&'a str>,
}
impl<'a> Command<'a> {
pub fn command(&self) -> &'a str {
self.command
}
pub fn parameters(&self) -> &HashMap<&'a str, &'a str> {
&self.parameters
}
pub fn arguments(&self) -> &Vec<&'a str> {
&self.arguments
}
pub fn parameter<T: FromStr>(&self, name: &str, default: T) -> T {
match self.parameters.get(name) {
Some(parameter) => parameter.parse().unwrap_or(default),
None => default,
}
}
pub fn param(&self, name: &str, default: &'a str) -> &str {
match self.parameters.get(name) {
Some(parameter) => parameter,
None => default,
}
}
pub fn options(&self) -> &Vec<&str> {
&self.options
}
pub fn option(&self, name: &str) -> bool {
self.options.contains(&name)
}
pub fn argument<T: FromStr>(&self, index: usize, default: T) -> T {
match self.arguments.get(index) {
Some(argument) => argument.parse().unwrap_or(default),
None => default,
}
}
pub fn arg(&self, index: usize, default: &'a str) -> &str {
match self.arguments.get(index) {
Some(argument) => argument,
None => default,
}
}
#[deprecated]
pub fn without_options(raw: &'a [String]) -> Self {
CliBuilder::new().build(raw)
}
#[deprecated]
pub fn from(raw: &'a [String], options: &'a [&str]) -> Self {
CliBuilder::new().options(options).build(raw)
}
fn create(
command: &'a str,
parameters: HashMap<&'a str, &'a str>,
options: Vec<&'a str>,
arguments: Vec<&'a str>,
) -> Self {
Self {
command,
parameters,
options,
arguments,
}
}
}