gistit 0.2.3

Quick and easy code snippet sharing
use lazy_static::lazy_static;
use ngrammatic::{Corpus, CorpusBuilder, Pad};

pub const SUPPORTED_COLORSCHEMES: [&str; 24] = [
    "1337",
    "Coldark-Cold",
    "Coldark-Dark",
    "DarkNeon",
    "Dracula",
    "GitHub",
    "Monokai Extended",
    "Monokai Extended Bright",
    "Monokai Extended Light",
    "Monokai Extended Origin",
    "Nord",
    "OneHalfDark",
    "OneHalfLight",
    "Solarized (dark)",
    "Solarized (light)",
    "Sublime Snazzy",
    "TwoDark",
    "Visual Studio Dark+",
    "ansi",
    "base16",
    "base16-256",
    "gruvbox-dark",
    "gruvbox-light",
    "zenburn",
];

lazy_static! {
    static ref FUZZY_MATCH: Corpus = SUPPORTED_COLORSCHEMES.iter().fold(
        CorpusBuilder::new().arity(2).pad_full(Pad::Auto).finish(),
        |mut corpus, &t| {
            corpus.add_text(t);
            corpus
        },
    );
}

pub mod check {
    use super::{FUZZY_MATCH, SUPPORTED_COLORSCHEMES};

    use std::ffi::OsStr;
    use std::fs;
    use std::net::Ipv4Addr;
    use std::ops::RangeInclusive;

    use crate::file::EXTENSION_TO_LANG_MAPPING;
    use crate::{Error, Result};

    const ALLOWED_FILE_SIZE_RANGE: RangeInclusive<u64> = 20..=50_000;

    const ALLOWED_DESCRIPTION_CHAR_LENGHT_RANGE: RangeInclusive<usize> = 10..=100;

    const ALLOWED_AUTHOR_CHAR_LENGTH_RANGE: RangeInclusive<usize> = 3..=30;

    const GISTIT_HASH_CHAR_LENGTH: usize = 64;

    pub fn description(description: &str) -> Result<&str> {
        if ALLOWED_DESCRIPTION_CHAR_LENGHT_RANGE.contains(&description.len()) {
            Ok(description)
        } else {
            Err(Error::Argument(
                "invalid description character length.",
                "--description",
            ))
        }
    }

    pub fn author(author: &str) -> Result<&str> {
        if ALLOWED_AUTHOR_CHAR_LENGTH_RANGE.contains(&author.len()) {
            Ok(author)
        } else {
            Err(Error::Argument(
                "invalid author character length.",
                "--author",
            ))
        }
    }

    pub fn metadata(attr: &fs::Metadata) -> Result<()> {
        let size_allowed = ALLOWED_FILE_SIZE_RANGE.contains(&attr.len());

        if size_allowed {
            Ok(())
        } else {
            Err(Error::Argument("file size not allowed", "[FILE]"))
        }
    }

    pub fn extension(ext: Option<&OsStr>) -> Result<()> {
        let ext = ext
            .and_then(OsStr::to_str)
            .ok_or(Error::Argument("file doesn't have an extension", "[FILE]"))?;

        if EXTENSION_TO_LANG_MAPPING.contains_key(ext) {
            Ok(())
        } else {
            Err(Error::Argument("file extension not supported", "[FILE]"))
        }
    }

    pub fn colorscheme(colorscheme: &str) -> Result<&str> {
        if SUPPORTED_COLORSCHEMES.contains(&colorscheme) {
            Ok(colorscheme)
        } else {
            let fuzzy_matches = FUZZY_MATCH.search(colorscheme, 0.25);
            let maybe_match = fuzzy_matches.first();

            maybe_match.map_or_else(
                || Err(Error::Argument("invalid colorscheme", "--colorscheme")),
                |top_match| Err(Error::Colorscheme(top_match.text.clone())),
            )
        }
    }

    pub const fn hash(hash: &str) -> Result<&str> {
        if hash.len() == GISTIT_HASH_CHAR_LENGTH {
            Ok(hash)
        } else {
            Err(Error::Argument("invalid gistit hash format.", "--hash"))
        }
    }

    pub fn host_port<'a, 'b>(host: &'a str, port: &'b str) -> Result<(&'a str, &'b str)> {
        let _host: Ipv4Addr = host
            .parse()
            .map_err(|_| Error::Argument("invalid host", "--host"))?;
        let _port: u16 = port
            .parse()
            .map_err(|_| Error::Argument("invalid port", "--port"))?;
        Ok((host, port))
    }
}