rt-format 0.3.1

Fully-runtime equivalent of the format! macro
Documentation
use std::collections::HashMap;

use rt_format::argument::{
    ArgumentSource, NamedArguments, NoNamedArguments, NoPositionalArguments, PositionalArguments
};
use rt_format::parser::{parse_specifier};
use rt_format::{Align, ParsedFormat, Format, Pad, Precision, Repr, Sign, Specifier, Width};

mod common;
use common::Variant;

type ParseResult<'a> = Result<ParsedFormat<'a, Variant>, usize> ;

fn parse<'a, P, N>(format: &'a str, positional: &'a P, named: &'a N) -> ParseResult<'a>
where
    P: PositionalArguments<'a, Variant>,
    N: NamedArguments<Variant>,
{
    ParsedFormat::parse(format, positional, named)
}

#[test]
fn unmatched_brace() {
    assert_eq!(Err(4), parse("foo {", &NoPositionalArguments, &NoNamedArguments));
    assert_eq!(Err(4), parse("bar } baz", &NoPositionalArguments, &NoNamedArguments));
}

#[test]
fn escaped_braces() {
    assert_eq!(
        "{}",
        parse("{{}}", &NoPositionalArguments, &NoNamedArguments)
            .unwrap()
            .to_string()
    );
}

#[test]
fn invalid_specifier() {
    assert_eq!(
        Err(4),
        parse("foo {:Z} bar", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn invalid_arg_position() {
    assert_eq!(
        Err(4),
        parse("foo {0bar} baz", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn positional_arg_iter() {
    assert_eq!(
        "42 42.042",
        parse("{} {}", &[Variant::Int(42), Variant::Float(42.042)], &NoNamedArguments)
            .unwrap()
            .to_string()
    );
}

#[test]
fn positional_arg_lookup() {
    assert_eq!(
        "42.042",
        parse("{1}", &[Variant::Int(42), Variant::Float(42.042)], &NoNamedArguments)
            .unwrap()
            .to_string()
    );
}

#[test]
fn named_arg_lookup() {
    let mut map = HashMap::new();
    map.insert("arglebargle".to_string(), Variant::Float(-42.042));
    assert_eq!(
        "-42.042",
        parse("{arglebargle}", &NoPositionalArguments, &map)
            .unwrap()
            .to_string()
    );
}

#[test]
fn missing_next_arg() {
    assert_eq!(
        Err(3),
        parse("{} {}", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn missing_positional_arg() {
    assert_eq!(Err(0), parse("{1}", &[Variant::Int(42)], &NoNamedArguments));
}

#[test]
fn missing_named_arg() {
    assert_eq!(Err(0), parse("{arglebargle}", &NoPositionalArguments, &NoNamedArguments));
}

#[test]
fn missing_positional_width() {
    assert_eq!(
        Err(0),
        parse("{:1$}", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn missing_named_width() {
    assert_eq!(
        Err(0),
        parse("{:arglebargle$}", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn missing_positional_precision() {
    assert_eq!(
        Err(0),
        parse("{:.1$}", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn missing_named_precision() {
    assert_eq!(
        Err(0),
        parse("{:.arglebargle$}", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn missing_asterisk_precision() {
    assert_eq!(
        Err(3),
        parse("{} {0:.*}", &[Variant::Int(42)], &NoNamedArguments)
    );
}

#[test]
fn named_argument_validity() {
    let mut map = HashMap::new();
    map.insert("ascii_identifier".to_string(), Variant::Int(42));
    map.insert("_leading_underscore".to_string(), Variant::Int(4242));
    map.insert("уникод".to_string(), Variant::Float(42.042));
    map.insert("0leading_digit".to_string(), Variant::Int(-42));
    map.insert("invalid/character".to_string(), Variant::Float(-42.042));

    assert!(parse("{ascii_identifier}", &NoPositionalArguments, &map).is_ok());
    assert!(parse("{_leading_underscore}", &NoPositionalArguments, &map).is_ok());
    assert!(parse("{уникод}", &NoPositionalArguments, &map).is_ok());

    assert_eq!(Err(0), parse("{0leading_digit}", &NoPositionalArguments, &map));
    assert_eq!(Err(0), parse("{invalid/character}", &NoPositionalArguments, &map));
}

#[test]
fn parse_specifier_smoke_test() {
    struct NoValues;
    impl ArgumentSource<Variant> for NoValues {
        fn next_argument(&mut self) -> Option<&Variant> { None }
        fn lookup_argument_by_index(&self, _: usize) -> Option<&Variant> { None }
        fn lookup_argument_by_name(&self, _: &str) -> Option<&Variant> { None }
    }

    assert_eq!(
        Ok(Specifier {
            align: Align::Right,
            sign: Sign::Always,
            repr: Repr::Alt,
            pad: Pad::Zero,
            width: Width::AtLeast { width: 42 },
            precision: Precision::Exactly { precision: 17 },
            format: Format::UpperExp,
        }),
        parse_specifier(">+#042.17E", &mut NoValues {})
    );
}