karsher 0.7.0

karsher is a dumb cache written in rust
use crate::prelude::*;
use strum::{EnumString, EnumVariantNames, VariantNames};

#[derive(Debug, EnumString, EnumVariantNames)]
pub enum CacheCommand<'a> {
    Add { aliases: Vec<&'a str>, value: &'a str },
    List,
    CurrentCache,
    Backup,
    Restore,
    ListCache,
    RemoveCache(Option<&'a str>),
    Remove(&'a str),
    Concat(&'a str),
    Get(&'a str),
    Exec { key: &'a str, args: Option<&'a str> },
    Cd(&'a str),
    Using(&'a str),
    Dump(Option<&'a str>),
    Clear,
    Help,
}

impl CacheCommand<'_> {
    pub const fn doc() -> &'static [(&'static [&'static str], &'static str)] {
        const VARIANTS: &[&str] = CacheCommand::VARIANTS;
        assert!(16 == VARIANTS.len(), "enum doc no longer valid!");
        &[
            (&["put"], "Put a new value to current cache. can have multiple aliases with option '-a'. e.g `set -a drc -a drcomp docker-compose`"),
            (&["describe","ds"], "List values within the cache."),
            (&["listcache","lsch"], "List available caches."),
            (&["del","delete"], "Remove value from cache. Accept either a hashkey or an alias. e.g `del drc`"),
            (&["get"], "Get value from cache. Accept either a hashkey or an alias. e.g `get drc`"),
            (&["exec","run"], "Run a value from the cache as an OS command. Accept either a hashkey or an alias. e.g `run drc`"),
            (&["use","using"], "Use another cache context default cache is DEFAULT. e.g `use linux`"),
            (&["dump"], "Dump cache(s) as json. Take an optional parameter, the cache name. e.g `dump linux`"),
            (&["clear","cls"], "Clear the terminal."),
            (&["delch","deletecache"], "Delete cache or clear current cache value."),
            (&["currch","currentcache"], "Current cache."),
            (&["backup","bckp"], "Backup the database of caches to the current directory"),
            (&["restore"], "Restore the database from current directory"),
            (&["cd"], "Navigate to a directory"),
            (&["merge/mergech"], "Merge current with a given cache"),
            (&["help"], "Display Help."),
        ]
    }
}

fn add_command(command: &str) -> Res<CacheCommand> {
    map(
        pair(
            preceded(
                preceded(multispace0, tag_no_case("PUT")),
                many0(preceded(
                    preceded(multispace1, tag_no_case("-a")),
                    preceded(
                        multispace1,
                        cut(verify(
                            take_while(|c: char| c.is_alphanumeric()),
                            |s: &str| !s.is_empty(),
                        )),
                    ),
                )),
            ),
            preceded(
                multispace1,
                cut(verify(rest.map(|s: &str| s.trim()), |s: &str| {
                    !s.is_empty()
                })),
            ),
        ),
        |(aliases, value)| CacheCommand::Add { aliases, value },
    )(command)
}

fn del_command(command: &str) -> Res<CacheCommand> {
    map(
        alt((
            extract_key(tag_no_case("del")),
            extract_key(tag_no_case("delete")),
        )),
        CacheCommand::Remove,
    )(command)
}

fn get_command(command: &str) -> Res<CacheCommand> {
    map(extract_key(tag_no_case("GET")), CacheCommand::Get)(command)
}

fn concat_command(command: &str) -> Res<CacheCommand> {
    map(
        alt((
            extract_key(tag_no_case("MERGE")),
            extract_key(tag_no_case("MERGECH")),
        )),
        CacheCommand::Concat,
    )(command)
}

fn backup_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(
        |s| alt((tag_no_case("backup"), tag_no_case("bckp")))(s),
        |_| CacheCommand::Backup,
    )(command)
}

fn restore_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(tag_no_case("restore"), |_| CacheCommand::Restore)(command)
}

fn exec_command(command: &str) -> Res<CacheCommand> {
    map(
        pair(
            alt((
                extract_key(tag_no_case("EXEC")),
                extract_key(tag_no_case("RUN")),
                take_till1(|s: char| s.is_whitespace()),
            )),
            opt(rest.map(|r: &str| r.trim())),
        ),
        |(key, args)| CacheCommand::Exec { key, args },
    )(command)
}

fn cd_command(command: &str) -> Res<CacheCommand> {
    map(
        preceded(
            tag_no_case("CD"),
            preceded(multispace1, rest.map(|r: &str| r.trim())),
        ),
        CacheCommand::Cd,
    )(command)
}

fn list_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(
        |s| alt((tag_no_case("describe"), tag_no_case("ds")))(s),
        |_| CacheCommand::List,
    )(command)
}

fn current_cache_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(
        |s| alt((tag_no_case("CURRCH"), tag_no_case("currentcache")))(s),
        |_| CacheCommand::CurrentCache,
    )(command)
}

fn help_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(tag_no_case("HELP"), |_| CacheCommand::Help)(command)
}

fn clear_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(
        |s| alt((tag_no_case("CLEAR"), tag_no_case("CLS")))(s),
        |_| CacheCommand::Clear,
    )(command)
}

fn list_cache_command(command: &str) -> Res<CacheCommand> {
    extract_no_args(
        |s| alt((tag_no_case("LISTCACHE"), tag_no_case("lsch")))(s),
        |_| CacheCommand::ListCache,
    )(command)
}

fn extract_no_args<'a, F, M>(
    parser: F,
    mapper: M,
) -> impl Fn(&'a str) -> Res<'a, CacheCommand<'a>>
where
    F: Fn(&'a str) -> Res<&'a str>,
    M: Fn(&'a str) -> CacheCommand<'a>,
{
    move |s| {
        map(
            preceded(
                &parser,
                verify(rest, |s: &str| s.trim().is_empty() || s == "\n"),
            ),
            &mapper,
        )(s)
    }
}
fn extract_key<'a, F>(parser: F) -> impl Fn(&'a str) -> Res<&'a str>
where
    F: Fn(&'a str) -> Res<&'a str>,
{
    move |s: &str| {
        preceded(
            &parser,
            preceded(
                multispace1,
                take_while1(|s: char| {
                    s.is_alphanumeric() || s == '-' || s == '_'
                }),
            ),
        )(s)
    }
}

fn using_command(command: &str) -> Res<CacheCommand> {
    map(
        alt((
            extract_key(tag_no_case("USING")),
            extract_key(tag_no_case("USE")),
        )),
        CacheCommand::Using,
    )(command)
}

fn dump_command(command: &str) -> Res<CacheCommand> {
    map(
        preceded(
            tag_no_case("DUMP"),
            cut(verify(rest, |s: &str| {
                s.is_empty() || s.starts_with(' ') || s == "\n"
            }))
            .and_then(opt(preceded(
                multispace1,
                take_while1(|s: char| s.is_alphanumeric() || s == '-'),
            ))),
        ),
        CacheCommand::Dump,
    )(command)
}
fn del_cache_command(command: &str) -> Res<CacheCommand> {
    map(
        preceded(
            alt((tag_no_case("DELCH"), tag_no_case("deletecache"))),
            cut(verify(rest, |s: &str| {
                s.is_empty() || s.starts_with(' ') || s == "\n"
            }))
            .and_then(opt(preceded(multispace1, rest.map(|s: &str| s.trim())))),
        ),
        CacheCommand::RemoveCache,
    )(command)
}

pub fn parse_command(command: &str) -> Res<CacheCommand> {
    preceded(
        multispace0,
        alt((
            add_command,
            cd_command,
            del_command,
            get_command,
            using_command,
            dump_command,
            list_cache_command,
            concat_command,
            current_cache_command,
            del_cache_command,
            list_command,
            help_command,
            clear_command,
            backup_command,
            restore_command,
            exec_command,
        )),
    )(command)
}