karsher 0.1.0

karsher is a dumb cache written in rust
#![feature(let_chains)]
use nom::{
    branch::alt,
    bytes::complete::{tag_no_case, take_while, take_while1},
    character::complete::multispace0,
    combinator::{map, rest},
    multi::many0,
    sequence::{delimited, pair, preceded},
    IResult, Parser,
};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::{
    collections::BTreeMap,
    io::{stdout, Write},
};

fn main() {
    let mut cache: BTreeMap<u64, String> = BTreeMap::new();
    let mut cache_aliases: BTreeMap<u64, u64> = BTreeMap::new();
    write_cursor_and_flush();
    let stdin = std::io::stdin();
    loop {
        let mut line = String::new();
        stdin.read_line(&mut line).expect("read error");
        match parse_command(&line) {
            Ok((_, command)) => match command {
                CacheCommand::Add { aliases, value } => {
                    let key = calculate_hash(&value);
                    cache.insert(key, value.to_owned());

                    let aliases: Vec<(u64, &str)> = aliases
                        .iter()
                        .filter_map(|alias| {
                            let k = calculate_hash(alias);
                            if !cache_aliases.contains_key(&k) {
                                Some((k, *alias))
                            } else {
                                None
                            }
                        })
                        .collect();

                    for (hash_alias, _) in &aliases {
                        cache_aliases.insert(*hash_alias, key);
                    }
                    println!("added {value} with hash key {key} and aliases {aliases:?}");
                }
                CacheCommand::Remove(key) => {
                    let key = {
                        if let Some(actual_key) = cache_aliases.remove(&calculate_hash(&key)) {
                            Some(actual_key)
                        } else {
                            key.parse::<u64>().ok()
                        }
                    };

                    if let Some(key) = key && let Some(v) = cache.remove(&key) {
                        cache_aliases = cache_aliases.into_iter().filter(|e| e.1 != key).collect();
                        println!("removed {v} with hash key {key}");
                    }
                }
                CacheCommand::Get(key) => {
                    let parsed_key = {
                        if let Some(actual_key) = cache_aliases.get(&calculate_hash(&key)) {
                            Some(*actual_key)
                        } else {
                            key.parse::<u64>().ok()
                        }
                    };

                    if let Some(key) = parsed_key && let Some(value) = cache.get(&key) {
                        println!("found {value}");
                    } else {
                        println!("{key} not found");
                    }
                }
            },
            Err(e) => eprintln!("error parsing command: {e}"),
        }

        write_cursor_and_flush();
    }
}

fn write_cursor_and_flush() {
    print!("> ");
    let _ = stdout().flush();
}

fn parse_command(command: &str) -> IResult<&str, CacheCommand> {
    preceded(
        multispace0,
        alt((
            map(
                pair(
                    preceded(
                        delimited(multispace0, tag_no_case("ADD"), multispace0),
                        many0(preceded(
                            delimited(multispace0, tag_no_case("-a"), multispace0),
                            preceded(multispace0, take_while(|c: char| c.is_alphanumeric())),
                        )),
                    ),
                    rest.map(|s: &str| s.trim()),
                ),
                |(aliases, value)| CacheCommand::Add { aliases, value },
            ),
            map(
                preceded(
                    alt((tag_no_case("DEL"), tag_no_case("DELETE"))),
                    preceded(
                        multispace0,
                        take_while1(|s: char| s.is_alphanumeric() || s == '-'),
                    ),
                ),
                CacheCommand::Remove,
            ),
            map(
                preceded(
                    tag_no_case("GET"),
                    preceded(
                        multispace0,
                        take_while1(|s: char| s.is_alphanumeric() || s == '-'),
                    ),
                ),
                CacheCommand::Get,
            ),
        )),
    )(command)
}

fn calculate_hash<T: Hash>(t: &T) -> u64 {
    let mut s = DefaultHasher::new();
    t.hash(&mut s);
    s.finish()
}

enum CacheCommand<'a> {
    Add {
        aliases: Vec<&'a str>,
        value: &'a str,
    },
    Remove(&'a str),
    Get(&'a str),
}