fpr-cli 0.4.7

A library that allows one to write cli tools quickly.
Documentation
use crate::com::*;

pub const PFX: &'static str = "--";
#[derive(Debug)]
pub struct Key<'a> {
    pub k: Option<&'a str>,
    pub v: Vec<&'a str>,
    pub used: bool,
}
#[derive(Debug)]
pub struct ParsedArgs<'c> {
    pub k: Vec<Key<'c>>,
}

impl<'c> ParsedArgs<'c> {
    pub fn consume(&mut self, name: &str) -> Option<&Vec<&'c str>> {
        let k = self
            .k
            .iter_mut()
            .find(|k| k.k.map(|k| k == name).unwrap_or(false))?;
        assert!(
            !k.used,
            "A token should not ever be consumed twice. Probably a duplicate argument: {name}"
        );
        k.used = true;
        Some(&k.v)
    }
}

impl<'c> ParsedArgs<'c> {
    pub fn new(args: &[&'c str]) -> Self {
        let mut last: Option<&str> = None;
        let r = ParsedArgs {
            k: args
                .iter()
                .map(|a| {
                    if a.starts_with(PFX) {
                        last = Some(a);
                    }
                    (last, *a)
                })
                .into_group_map()
                .into_iter()
                .map(|(k, v)| Key {
                    k,
                    v: if k.is_some() { v[1..].into() } else { v },
                    used: false,
                })
                .collect(),
        };

        r
    }
}

pub enum Init<C, T: Display> {
    None,
    Const(T),
    Dyn(fn(&C) -> T),
}

impl<C, T: Display> Init<C, T> {
    pub fn get(self, c: &C) -> Option<T> {
        match self {
            Init::None => None,
            Init::Const(v) => Some(v),
            Init::Dyn(f) => Some(f(&c)),
        }
    }
    fn to_string(self, c: &C) -> String {
        match self.get(c) {
            Some(s) => format!(" (default: {s})"),
            None => format!(""),
        }
    }
}

#[derive(Error, Debug)]
pub enum ErrArg {
    #[error("Failed while parsing values for '{arg}': {e}")]
    Val { arg: String, e: ErrVal },
    #[error("'{arg}' must be one value.")]
    OnlyOne { arg: String },
    #[error("'{arg}' is required.")]
    Required { arg: String },
    #[error("'{arg}' requires at least one value.")]
    AtLeastOne { arg: String },
}

pub trait Parse2<'a, 'b, C>
where
    Self: Sized,
    Self::I: Display,
{
    type I;
    fn parse2(
        i: Init<C, Self::I>,
        k: &'static str,
        c: &C,
        p: &mut ParsedArgs<'b>,
    ) -> Result<Self, ErrArg>;
    fn desc2(i: Init<C, Self::I>, d: &'static str, k: &'static str, c: &C) -> [String; 4];
    fn default2(c: &C, i: Init<C, Self::I>) -> Self;
}

impl<'a, 'b, C, T: Parse<'a> + Default> Parse2<'b, 'a, C> for T {
    type I = T;
    fn parse2(
        i: Init<C, Self::I>,
        k: &'static str,
        c: &C,
        p: &mut ParsedArgs<'a>,
    ) -> Result<Self, ErrArg> {
        match p.consume(k) {
            Some(args) => {
                if args.len() != 1 {
                    Err(ErrArg::OnlyOne { arg: k.into() })
                } else {
                    Ok(T::parse(&args[0]).map_err(|e| ErrArg::Val { arg: k.into(), e })?)
                }
            }
            None => Ok(i.get(c).ok_or(ErrArg::Required { arg: k.into() })?),
        }
    }

    fn desc2(i: Init<C, Self>, d: &'static str, k: &'static str, c: &C) -> [String; 4] {
        [
            k.into(),
            format!("Req<{}>", T::desc()),
            d.into(),
            i.to_string(c),
        ]
    }
    fn default2(c: &C, i: Init<C, Self::I>) -> Self {
        i.get(c).unwrap_or(Self::default())
    }
}

impl<'a, 'b, Ctx, T: Parse<'a>> Parse2<'b, 'a, Ctx> for Option<T> {
    type I = T;
    fn parse2(
        i: Init<Ctx, T>,
        k: &'static str,
        c: &Ctx,
        p: &mut ParsedArgs<'a>,
    ) -> Result<Self, ErrArg> {
        match p.consume(k) {
            Some(args) => {
                if args.len() != 1 {
                    Err(ErrArg::OnlyOne { arg: k.into() })
                } else {
                    Ok(Some(
                        T::parse(&args[0]).map_err(|e| ErrArg::Val { arg: k.into(), e })?,
                    ))
                }
            }
            None => Ok(match i.get(c) {
                Some(e) => Some(e),
                None => None,
            }),
        }
    }
    fn desc2(i: Init<Ctx, T>, d: &'static str, k: &'static str, c: &Ctx) -> [String; 4] {
        [
            k.into(),
            format!("Opt<{}>", T::desc()),
            d.into(),
            i.to_string(c),
        ]
    }
    fn default2(c: &Ctx, i: Init<Ctx, Self::I>) -> Self {
        match i.get(c) {
            Some(i) => Some(i),
            None => None,
        }
    }
}

impl<'a, 'b, Ctx, T: Parse<'a> + Display> Parse2<'b, 'a, Ctx> for Vec<T> {
    type I = DisplayVec<T>;
    fn parse2(
        i: Init<Ctx, Self::I>,
        k: &'static str,
        c: &Ctx,
        p: &mut ParsedArgs<'a>,
    ) -> Result<Self, ErrArg> {
        match p.consume(k) {
            Some(args) => {
                let args = args
                    .iter()
                    .map(|a| T::parse(a).map_err(|e| ErrArg::Val { arg: k.into(), e }))
                    .process_results(|i| i.collect_vec())?;
                if args.is_empty() {
                    Err(ErrArg::OnlyOne { arg: k.into() })?;
                }
                Ok(args)
            }
            None => Ok(match i.get(c) {
                Some(e) => e.into(),
                None => vec![],
            }),
        }
    }

    fn desc2(i: Init<Ctx, Self::I>, d: &'static str, k: &'static str, c: &Ctx) -> [String; 4] {
        [
            k.into(),
            format!("Vec<{}>", T::desc()),
            d.into(),
            i.to_string(c),
        ]
    }
    fn default2(c: &Ctx, i: Init<Ctx, Self::I>) -> Self {
        match i.get(c) {
            Some(v) => v.0,
            None => Self::default(),
        }
    }
}

#[derive(Debug)]
pub struct OptVec<T: Display>(pub Vec<T>);
impl<T: Display> From<Vec<T>> for OptVec<T> {
    fn from(v: Vec<T>) -> Self {
        Self(v)
    }
}
impl<T: Display> From<DisplayVec<T>> for OptVec<T> {
    fn from(v: DisplayVec<T>) -> Self {
        Self(v.0)
    }
}
impl<'a, 'b, Ctx, T: Parse<'a>> Parse2<'b, 'a, Ctx> for OptVec<T> {
    type I = DisplayVec<T>;
    fn parse2(
        i: Init<Ctx, Self::I>,
        k: &'static str,
        c: &Ctx,
        p: &mut ParsedArgs<'a>,
    ) -> Result<Self, ErrArg> {
        match p.consume(k) {
            Some(args) => {
                let args = args
                    .iter()
                    .map(|a| T::parse(a).map_err(|e| ErrArg::Val { arg: k.into(), e }))
                    .process_results(|i| i.collect_vec())?;
                Ok(args.into())
            }
            None => Ok(match i.get(c) {
                Some(e) => e.into(),
                None => vec![].into(),
            }),
        }
    }

    fn desc2(i: Init<Ctx, Self::I>, d: &'static str, k: &'static str, c: &Ctx) -> [String; 4] {
        [
            k.into(),
            format!("Vec<{}>", T::desc()),
            d.into(),
            i.to_string(c),
        ]
    }
    fn default2(c: &Ctx, i: Init<Ctx, Self::I>) -> Self {
        Self::from(match i.get(c) {
            Some(v) => v.0,
            None => <Vec<T>>::default(),
        })
    }
}
impl Display for DirExist {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.s)
    }
}
impl Display for FileExist {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.s)
    }
}