tyrx 0.1.2

Typed, ergonomic regular expression library
Documentation
use tyrx::{TyRx, RegexPattern, FromMatch, ErasedLifetime, Result};
use tyrx::{builder, util::Spanned};


#[test]
fn complex_nested_types_and_attributes() -> Result<()> {
    #[derive(Clone, Debug, PartialEq, Eq, RegexPattern, FromMatch, ErasedLifetime)]
    struct Foo<'lt> {
        #[tyrx(pattern = r"(?<Foo.bar>[a-zA-Z_]+)\s+")]
        bar: &'lt str,
        #[tyrx(rename = qux, pattern = format_args!(r"(?<Foo.qux>[{}\.]+)", builder::CharRange::<'0', '9'>::pattern_display()))]
        baz: String,
    }

    #[derive(Clone, Debug, PartialEq, RegexPattern, FromMatch, ErasedLifetime)]
    #[tyrx(lifetime = 'x)]
    enum Choices<'x> {
        First,
        Second(Foo<'x>),
        Third {
            #[tyrx(pattern = format_args!("(?<Choices.Third.field>{}+)", builder::CharClass::<builder::AsciiAlpha>::pattern_display()))]
            field: Spanned<String>,
            #[tyrx(pattern = format_args!("(?<Choices.Third.space>{}+)", builder::CharClass::<builder::AsciiBlank>::pattern_display()))]
            space: builder::Ignore<f64>, // the wrapped type shouldn't attempt to parse
            #[tyrx(rename = r#impl)]
            other_field: i64,
        },
    }

    // local variable so we can test a non-'static lifetime
    let s = String::from(r#"
        First
        abdefxqz -876
        __ 137.036
    "#);

    // exercise the `FromMatch` impl of `Spanned`
    let results: Vec<_> = Spanned::<Choices<'_>>::iter_from_str(s.as_str()).collect::<Result<_>>()?;

    assert_eq!(results, [
        Spanned::new(Choices::First, 9..14),
        Spanned::new(Choices::Third {
            field: Spanned::new(String::from("abdefxqz"), 23..31),
            space: Default::default(),
            other_field: -876,
        }, 23..36),
        Spanned::new(Choices::Second(Foo {
            bar: "__",
            baz: String::from("137.036"),
        }), 45..55),
    ]);

    Ok(())
}

#[test]
fn option_from_match() -> Result<()> {
    #[derive(Clone, PartialEq, Debug, RegexPattern, FromMatch, ErasedLifetime)]
    struct Compound {
        begin: builder::LineStart,
        id: builder::Any1<builder::CharClass<builder::AsciiAlnum>, String>,
        sep: builder::Char<':'>,
        value: Option<f32>,
        end: builder::LineEnd,
    }

    let opt_no_match: Option<i16> = TyRx::from_str("nope")?;
    assert_eq!(opt_no_match, None);

    let opt_empty: Option<i16> = TyRx::from_str("")?;
    assert_eq!(opt_empty, None);

    let opt_some: Option<i16> = TyRx::from_str("+3907")?;
    assert_eq!(opt_some, Some(3907));

    let compound_some = Compound::from_str("someident98:496.875")?;
    assert_eq!(compound_some.id.value(), "someident98");
    assert_eq!(compound_some.value, Some(496.875));

    let compound_none = Compound::from_str("123otherident:")?;
    assert_eq!(compound_none.id.value(), "123otherident");
    assert_eq!(compound_none.value, None);

    Ok(())
}

#[test]
fn derive_regex_pattern_flags() {
    #[derive(Clone, Debug, RegexPattern)]
    struct NoFlags;

    #[derive(Clone, Debug, RegexPattern)]
    #[tyrx(flag(case_insensitive))]
    struct CaseInsensitive;

    #[derive(Clone, Debug, RegexPattern)]
    #[tyrx(flag(unicode = false))]
    struct NoUnicode;

    #[derive(Clone, Debug, RegexPattern)]
    #[tyrx(flag(ignore_whitespace = true, dot_matches_new_line = false, unicode, crlf = true, swap_greed = false))]
    struct ManyFlags;

    #[derive(Clone, Copy, PartialEq, Eq, Debug, RegexPattern, FromMatch, ErasedLifetime)]
    #[tyrx(flag(multi_line, case_insensitive = true))]
    enum ManyVariants {
        NoFlags,
        #[tyrx(flag(swap_greed))]
        SwapGreed,
        #[tyrx(flag(ignore_whitespace = false))]
        NoIgnoreWhitespace,
        #[tyrx(flag(ignore_whitespace = false, swap_greed = true, crlf, unicode = false))]
        ManyFlags,
    }

    assert_eq!(NoFlags::pattern_display().to_string(), "");
    assert_eq!(CaseInsensitive::pattern_display().to_string(), "(?i:)");
    assert_eq!(NoUnicode::pattern_display().to_string(), "(?-u:)");
    assert_eq!(ManyFlags::pattern_display().to_string(), "(?Rux-sU:)");

    assert_eq!(
        ManyVariants::pattern_display().to_string(),
        "(?im:(?<ManyVariants.NoFlags>NoFlags)|(?U:(?<ManyVariants.SwapGreed>SwapGreed))|(?-x:(?<ManyVariants.NoIgnoreWhitespace>NoIgnoreWhitespace))|(?RU-ux:(?<ManyVariants.ManyFlags>ManyFlags)))",
    );

    // test case insensitive matching of every variant
    let enums: Vec<_> = ManyVariants::iter_from_str("SwapGreed NoIgnoreWhitespace manyflags noFlAGs")
        .collect::<Result<_>>()
        .unwrap();

    assert_eq!(enums, [
        ManyVariants::SwapGreed,
        ManyVariants::NoIgnoreWhitespace,
        ManyVariants::ManyFlags,
        ManyVariants::NoFlags,
    ]);
}

#[test]
fn top_level_type_renaming() -> Result<()> {
    #[derive(Clone, PartialEq, Eq, Debug, RegexPattern, FromMatch, ErasedLifetime)]
    #[tyrx(rename = ChangedTheName)]
    struct Renamed {
        field: u16,
        #[tyrx(rename = not_that_field)]
        other: String,
    }

    #[derive(Clone, PartialEq, Debug, RegexPattern, FromMatch, ErasedLifetime)]
    #[tyrx(rename = AlternateTy)]
    enum FewVariants {
        Var {
            one: i32,
            #[tyrx(rename = sep)]
            comma: builder::Char<','>,
            two: u64
        },
        #[tyrx(rename = Second)]
        Renamed(bool),
    }

    assert_eq!(
        Renamed::pattern_display().to_string(),
        r"(?<ChangedTheName.field>\+?[0-9]+)(?<ChangedTheName.not_that_field>.*)",
    );
    assert_eq!(
        Renamed::from_str("01683 other stuff")?,
        Renamed {
            field: 1683,
            other: String::from(" other stuff"),
        },
    );

    assert_eq!(
        FewVariants::pattern_display().to_string(),
        r"(?<AlternateTy.Var>(?<AlternateTy.Var.one>[-\+]?[0-9]+)(?<AlternateTy.Var.sep>,)(?<AlternateTy.Var.two>\+?[0-9]+))|(?<AlternateTy.Second>(?<AlternateTy.Second.0>false|true))",
    );
    assert_eq!(
        FewVariants::from_str("-47,+19919911")?,
        FewVariants::Var {
            one: -47,
            comma: Default::default(),
            two: 19919911,
        },
    );
    assert_eq!(
        Spanned::<FewVariants>::iter_from_str("a true statement, a false impression").collect::<Result<Vec<_>>>()?,
        [
            Spanned::new(FewVariants::Renamed(true), 2..6),
            Spanned::new(FewVariants::Renamed(false), 20..25),
        ],
    );

    Ok(())
}