simon 0.4.0

Library for declaratively specifying and parsing command line arguments
Documentation
use crate::{SwitchCommon, SwitchShape, Switches};
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Display};

#[derive(PartialEq, Eq, Debug, Default)]
pub struct Invalid {
    pub duplicate_shorts: Vec<String>,
    pub duplicate_longs: Vec<String>,
    pub one_char_longs: Vec<String>,
    pub multi_char_shorts: Vec<String>,
    pub has_empty_switch: bool,
}

impl Invalid {
    fn new(
        duplicate_shorts: Vec<String>,
        duplicate_longs: Vec<String>,
        one_char_longs: Vec<String>,
        multi_char_shorts: Vec<String>,
        has_empty_switch: bool,
    ) -> Option<Self> {
        if duplicate_shorts.is_empty()
            && duplicate_longs.is_empty()
            && one_char_longs.is_empty()
            && multi_char_shorts.is_empty()
            && !has_empty_switch
        {
            None
        } else {
            Some(Self {
                duplicate_shorts,
                duplicate_longs,
                one_char_longs,
                multi_char_shorts,
                has_empty_switch,
            })
        }
    }
}

impl Display for Invalid {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        assert!(
            !(self.duplicate_shorts.is_empty()
                && self.duplicate_longs.is_empty()
                && self.one_char_longs.is_empty()
                && self.multi_char_shorts.is_empty()
                && !self.has_empty_switch)
        );

        write!(f, "Invalid argument spec!\n\n")?;
        if !self.duplicate_shorts.is_empty() {
            write!(f, "Duplicate shorts: \n{:#?}", self.duplicate_shorts)?;
        }
        if !self.duplicate_longs.is_empty() {
            write!(f, "Duplicate longs: \n{:#?}", self.duplicate_longs)?;
        }
        if !self.one_char_longs.is_empty() {
            write!(f, "Single-character longs: \n{:#?}", self.one_char_longs)?;
        }
        if !self.multi_char_shorts.is_empty() {
            write!(f, "Multi-character shorts: \n{:#?}", self.multi_char_shorts)?;
        }
        if self.has_empty_switch {
            write!(f, "Argument specified with neither short nor long switch")?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct SwitchInfo {
    common: SwitchCommon,
    arity: SwitchShape,
}

#[derive(Default, Debug)]
struct SwitchTable {
    table: HashMap<String, HashSet<SwitchInfo>>,
}

impl SwitchTable {
    fn insert(&mut self, key: &str, info: SwitchInfo) {
        self.table
            .entry(key.to_string())
            .or_insert_with(HashSet::default)
            .insert(info);
    }
    fn keys_with_multiple_values(&self) -> impl Iterator<Item = String> + '_ {
        self.table.iter().filter_map(
            |(k, v)| {
                if v.len() > 1 {
                    Some(k.clone())
                } else {
                    None
                }
            },
        )
    }
}

#[derive(Default, Debug)]
struct InvalidNames {
    one_char_longs: Vec<String>,
    multi_char_shorts: Vec<String>,
    has_empty_switch: bool,
}

impl InvalidNames {
    fn insert(&mut self, info: &SwitchInfo) {
        let short = info.common.short.as_str();
        let long = info.common.long.as_str();
        if short.len() > 1 {
            self.multi_char_shorts.push(short.to_string());
        }
        if long.len() == 1 {
            self.one_char_longs.push(long.to_string());
        }
        if short.len() == 0 && long.len() == 0 {
            self.has_empty_switch = true;
        }
    }
}

#[derive(Default, Debug)]
struct Duplicates {
    by_short: SwitchTable,
    by_long: SwitchTable,
}

impl Duplicates {
    fn insert(&mut self, info: SwitchInfo) {
        let short = info.common.short.clone();
        let long = info.common.long.clone();
        if short.len() > 0 {
            self.by_short.insert(&short, info.clone());
        }
        if long.len() > 0 {
            self.by_long.insert(&long, info);
        }
    }
}

#[derive(Default, Debug)]
pub struct Checker {
    duplicates: Duplicates,
    invalid_names: InvalidNames,
}

impl Checker {
    fn insert(&mut self, info: SwitchInfo) {
        self.invalid_names.insert(&info);
        self.duplicates.insert(info);
    }
    pub fn invalid(self) -> Option<Invalid> {
        let Checker {
            duplicates: Duplicates { by_short, by_long },
            invalid_names:
                InvalidNames {
                    one_char_longs,
                    multi_char_shorts,
                    has_empty_switch,
                },
        } = self;
        let duplicate_shorts = by_short.keys_with_multiple_values().collect::<Vec<_>>();
        let duplicate_longs = by_long.keys_with_multiple_values().collect::<Vec<_>>();
        Invalid::new(
            duplicate_shorts,
            duplicate_longs,
            one_char_longs,
            multi_char_shorts,
            has_empty_switch,
        )
    }
}

impl Switches for Checker {
    fn add(&mut self, common: SwitchCommon, arity: SwitchShape) {
        self.insert(SwitchInfo { common, arity });
    }
}