macro-rules-rt 0.1.1

Transforms text or TokenStream using Rust's macro_rules syntax.
Documentation
use macro_rules_rt::{match_all, Rule};
use pretty_assertions::assert_eq;
use syn::Result;

#[derive(Clone, Debug, PartialEq)]
enum Part {
    Unchanged(String),
    Changed(Changed),
}
impl Part {
    fn from(value: match_all::MatchAllPart) -> Self {
        match value {
            match_all::MatchAllPart::Unchanged(u) => Self::Unchanged(u.to_string()),
            match_all::MatchAllPart::Changed(c) => Self::Changed(Changed::from(c)),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
struct Changed {
    source_str: String,
    replacement_str: String,
    replacements: Vec<Replacement>,
}
impl Changed {
    fn from(value: match_all::Changed) -> Self {
        let source_str = value.input().to_string();
        let replacement_str = value.output();
        let replacements = value.iter().map(Replacement::from).collect();
        Self {
            source_str,
            replacement_str,
            replacements,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
enum Replacement {
    Match(String),
    Transform(String),
}
impl Replacement {
    fn from(value: match_all::ChangedPart) -> Self {
        match value {
            match_all::ChangedPart::Match(m) => Self::Match(m.to_string()),
            match_all::ChangedPart::Transform(t) => Self::Transform(t.to_string()),
        }
    }
}

fn unchanged(source: &str) -> Part {
    Part::Unchanged(source.to_string())
}
fn changed(source: &str, replacement: &str, replacements: &[Replacement]) -> Part {
    Part::Changed(Changed {
        source_str: source.to_string(),
        replacement_str: replacement.to_string(),
        replacements: replacements.to_vec(),
    })
}
fn m(s: &str) -> Replacement {
    Replacement::Match(s.to_string())
}
fn t(s: &str) -> Replacement {
    Replacement::Transform(s.to_string())
}

#[track_caller]
fn check_raw(from: &str, to: &str, input: &str, expect: &[Part]) -> Result<()> {
    let rule = Rule::new(from.parse()?, to.parse()?)?;
    let m = rule.match_all(input)?;
    let actual: Vec<_> = m.iter().map(Part::from).collect();
    assert_eq!(actual, expect);
    Ok(())
}
#[track_caller]
fn check(from: &str, to: &str, input: &str, expect: &[Part]) {
    check_raw(from, to, input, expect).unwrap()
}

#[test]
fn test_changed() {
    let e = [changed("1+2", "@", &[m("@")])];
    check("$e:expr", "@", "1+2", &e)
}

#[test]
fn test_unchanged_and_changed() {
    let e = [
        unchanged("@@"),
        changed("1+2", "x", &[m("x")]),
        unchanged("@@"),
    ];
    check("$e:expr", "x", "@@1+2@@", &e)
}

#[test]
fn test_transform() {
    let e = [changed(
        "/// xxx",
        "# [ docx = \" xxx\" ]",
        &[t("# ["), m("docx = "), t("\" xxx\" ]")],
    )];
    check("doc = ", "docx = ", "/// xxx", &e)
}

#[test]
fn test_unchanged_and_transform() {
    let e = [
        unchanged("@@\n"),
        changed(
            "/// xxx",
            "# [ docx = \" xxx\" ]",
            &[t("# ["), m("docx = "), t("\" xxx\" ]")],
        ),
    ];
    check("doc = ", "docx = ", "@@\n/// xxx", &e)
}