axe-cli 0.3.0

axe - Argument execute is xargs alternative that focus on arguments processing and ordering.
Documentation
use std::num::ParseIntError;

use logos::Logos;

#[derive(Debug, Logos, PartialEq)]
#[logos(error = LexingError)]
pub enum ArgTemplateToken {
    #[regex(r"\{[^{}]*\}", priority = 2)]
    ArgPlaceholder,
    #[regex(r"[^{}\\]+", priority = 0)]
    FreeText,
    #[regex(r"\\\{*[^{}]*}*", priority = 1)]
    EscapedText,
}

#[derive(Debug, Logos, PartialEq)]
#[logos(error = LexingError)]
pub enum ArgPlaceholderToken<'a> {
    #[token("{")]
    BraceOpen,
    #[token("}")]
    BraceClose,
    #[regex(r"[0-9]+", |lex| lex.slice().parse())]
    Index(usize),
    #[regex(r"[^0-9\{}]+", |lex| lex.slice())]
    Separator(&'a str),
}

#[derive(Default, Debug, Clone, PartialEq)]
pub enum LexingError {
    InvalidInteger(String),
    #[default]
    InvalidDefinition,
}

impl From<ParseIntError> for LexingError {
    fn from(err: ParseIntError) -> Self {
        use std::num::IntErrorKind::*;
        match err.kind() {
            PosOverflow | NegOverflow => LexingError::InvalidInteger("overflow error".to_owned()),
            _ => LexingError::InvalidInteger("other error".to_owned()),
        }
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn lexer_should_parse_arg_placeholder_with_index_only() {
        let mut lex = ArgPlaceholderToken::lexer("{0}");

        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceOpen)));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Index(0))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceClose)));
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_arg_placeholder_with_index_and_separator() {
        let mut lex = ArgPlaceholderToken::lexer("{0.}");

        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceOpen)));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Index(0))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Separator("."))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceClose)));
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_arg_placeholder_with_index_separator_and_index() {
        let mut lex = ArgPlaceholderToken::lexer("{0.1}");

        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceOpen)));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Index(0))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Separator("."))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Index(1))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceClose)));
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_empty_arg_placeholder() {
        let mut lex = ArgPlaceholderToken::lexer("{}");

        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceOpen)));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceClose)));
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_empty_arg_placeholder_with_separator() {
        let mut lex = ArgPlaceholderToken::lexer("{.}");

        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceOpen)));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Separator("."))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceClose)));
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_arg_placeholder_with_separator_and_index() {
        let mut lex = ArgPlaceholderToken::lexer("{.0}");

        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceOpen)));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Separator("."))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::Index(0))));
        assert_eq!(lex.next(), Some(Ok(ArgPlaceholderToken::BraceClose)));
        assert_eq!(lex.next(), None);
    }

    use super::ArgTemplateToken;

    #[test]
    fn lexer_should_parse_arg_template_with_placeholders() {
        let mut lex = ArgTemplateToken::lexer("{}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer("{0}{1#}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{1#}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer("{0.0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{0.0}");
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_arg_template_with_free_text() {
        let mut lex = ArgTemplateToken::lexer("free.0{0}1_text");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "free.0");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "1_text");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer("{0}__{1} {2}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "__");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{1}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), " ");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{2}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer("free text without placeholders");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "free text without placeholders");
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_fail_to_parse_arg_template_with_invalid_placeholders() {
        let mut lex = ArgTemplateToken::lexer("free{");

        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "free");
        assert_eq!(lex.next(), Some(Err(LexingError::InvalidDefinition)));
        assert_eq!(lex.slice(), "{");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer("free}");

        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "free");
        assert_eq!(lex.next(), Some(Err(LexingError::InvalidDefinition)));
        assert_eq!(lex.slice(), "}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer("{{0}}");
        assert_eq!(lex.next(), Some(Err(LexingError::InvalidDefinition)));
        assert_eq!(lex.slice(), "{");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.next(), Some(Err(LexingError::InvalidDefinition)));
        assert_eq!(lex.slice(), "}");
    }

    #[test]
    fn lexer_should_parse_arg_tmplate_placeholder_even_when_value_is_invalid() {
        let mut lex = ArgTemplateToken::lexer("{abcd.0}{1.1.1}{0.0#}{_0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{abcd.0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{1.1.1}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{0.0#}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{_0}");
        assert_eq!(lex.next(), None);
    }

    #[test]
    fn lexer_should_parse_escaped_characters_in_arg_templates() {
        let mut lex = ArgTemplateToken::lexer(r"free\{");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), r"free");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\{");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer(r"free\}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), "free");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer(r"\{0}\{1\}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\{1\}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer(r"\\{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\\");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{0}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer(r"\{0abc{0}\}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\{0abc");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), r"{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer(r"\{{0}}{1}\d{2}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\{{0}}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{1}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\d");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), "{2}");
        assert_eq!(lex.next(), None);

        let mut lex = ArgTemplateToken::lexer(r"\{ {0} \}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\{ ");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::ArgPlaceholder)));
        assert_eq!(lex.slice(), r"{0}");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::FreeText)));
        assert_eq!(lex.slice(), " ");
        assert_eq!(lex.next(), Some(Ok(ArgTemplateToken::EscapedText)));
        assert_eq!(lex.slice(), r"\}");
        assert_eq!(lex.next(), None);
    }
}