slash-lang 0.1.0

Parser and AST for the slash-command language
Documentation
use slash_lang::parser::ast::{Op, Priority, Urgency};
use slash_lang::parser::parse;

#[test]
fn parses_pipelines_and_ops() {
    let input = "/A!! | /b && /c || /d";
    let ast = parse(input).expect("parse should succeed");
    assert_eq!(ast.pipelines.len(), 3);
}

#[test]
fn rejects_redirection_mid_pipeline() {
    let input = "/a > out | /b";
    assert!(parse(input).is_err());
}

#[test]
fn pipeline_operators_are_set_correctly() {
    let prog = parse("/a && /b || /c").unwrap();
    assert_eq!(prog.pipelines.len(), 3);
    assert_eq!(prog.pipelines[0].operator, Some(Op::And));
    assert_eq!(prog.pipelines[1].operator, Some(Op::Or));
    assert!(prog.pipelines[2].operator.is_none());
}

#[test]
fn pipe_operator_is_on_sending_command() {
    let prog = parse("/a | /b").unwrap();
    let cmds = &prog.pipelines[0].commands;
    assert_eq!(cmds[0].pipe, Some(Op::Pipe));
    assert!(cmds[1].pipe.is_none());
}

#[test]
fn priority_inferred_from_casing() {
    let prog = parse("/ALL_CAPS").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].priority, Priority::Max);

    let prog = parse("/TitleCase").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].priority, Priority::High);

    let prog = parse("/camelCase").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].priority, Priority::Medium);

    let prog = parse("/kebab-case").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].priority, Priority::Low);

    let prog = parse("/snake_case").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].priority, Priority::Lowest);
}

#[test]
fn urgency_inferred_from_bangs() {
    let prog = parse("/cmd!").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].urgency, Urgency::Low);

    let prog = parse("/cmd!!").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].urgency, Urgency::Medium);

    let prog = parse("/cmd!!!").unwrap();
    assert_eq!(prog.pipelines[0].commands[0].urgency, Urgency::High);
}

#[test]
fn builder_args_parsed_correctly() {
    let prog = parse("/build.target(release).jobs(4).lto").unwrap();
    let cmd = &prog.pipelines[0].commands[0];
    assert_eq!(cmd.name, "build");
    assert_eq!(cmd.args.len(), 3);
    assert_eq!(cmd.args[0].name, "target");
    assert_eq!(cmd.args[0].value.as_deref(), Some("release"));
    assert_eq!(cmd.args[1].name, "jobs");
    assert_eq!(cmd.args[1].value.as_deref(), Some("4"));
    assert_eq!(cmd.args[2].name, "lto");
    assert!(cmd.args[2].value.is_none());
}

#[test]
fn redirection_truncate_and_append() {
    let prog = parse("/build > out.txt").unwrap();
    let cmd = &prog.pipelines[0].commands[0];
    assert!(
        matches!(cmd.redirect, Some(slash_lang::parser::ast::Redirection::Truncate(ref f)) if f == "out.txt")
    );

    let prog = parse("/log >> audit.log").unwrap();
    let cmd = &prog.pipelines[0].commands[0];
    assert!(
        matches!(cmd.redirect, Some(slash_lang::parser::ast::Redirection::Append(ref f)) if f == "audit.log")
    );
}