1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
use anyhow::Result;
use regex::{Captures, Regex};

use crate::Rule;

/// Template macro for replacing a Rust struct with a placeholder struct that can be compiled.
///
/// ```rust
/// // Private
/// replacer::rust_struct!(replace_with_struct; Point2D { x: i32, y: i32 };);
/// // Public
/// replacer::rust_struct!(pub replace_with_other_struct; Point3D { x: i32, y: i32, z: i32 };);
/// // With a lifetime
/// replacer::rust_struct!(replace_with_struct; Point4D<'a> { x: i32, y: &'a i32, z: i32, w: i32 };);
/// ```
#[macro_export]
macro_rules! rust_struct {
    // No lifetime, private
    ($_name:ident; $placeholder:ident {$($element: ident: $ty: ty),*};) => {
        struct $placeholder { $($element: $ty),* }
    };
    // No lifetime, public
    (pub $_name:ident; $placeholder:ident {$($element: ident: $ty: ty),*};) => {
        pub struct $placeholder { $($element: $ty),* }
    };
    // Lifetime, private
    ($_name:ident; $placeholder:ident<$lifetime:lifetime>{$($element: ident: $ty: ty),*};) => {
        struct $placeholder<$lifetime> { $($element: $ty),* }
    };
    // Lifetime, public
    (pub $_name:ident; $placeholder:ident<$lifetime:lifetime>{$($element: ident: $ty: ty),*};) => {
        pub struct $placeholder<$lifetime> { $($element: $ty),* }
    };
}

/// Replace a Rust struct.
/// ```rust
/// # use replacer::rule::{Rule, StructRule};
/// # fn main() -> anyhow::Result<()> {
/// let rule = StructRule::new("point", "Point3D { x: i32, y: i32, z: i32 }")?;
/// assert_eq!(rule.convert("replacer::rust_struct!(point; Point2D{ x: i32, y: i32};}")?,
///     "struct Point3D { x: i32, y: i32, z: i32 }");
/// # Ok(())
/// # }
/// ```
pub struct StructRule {
    /// What the keyword will be replaced with.
    replace_with: String,
    /// Regex used to find the macro.
    regex: Regex,
}

impl Rule for StructRule {
    fn convert(&self, template: &str) -> Result<String> {
        let replace_with: &str = &self.replace_with;
        let replace = self.regex.replace_all(template, |caps: &Captures| {
            format!(
                "{}struct {}",
                &caps.name("pub").map_or("", |cap| cap.as_str()),
                replace_with,
            )
        });

        Ok(replace.into_owned())
    }
}

impl StructRule {
    /// Setup a new rule.
    pub fn new(matches: &str, replace_with: &str) -> Result<Self> {
        let regex = Regex::new(&format!(
            r"{}[\({{]{}[\)}}]",
            r"replacer::rust_struct!\s*",
            format!(r"(?P<pub>pub )?{};[^{{]+\{{[^;]+}};", matches)
        ))?;

        Ok(Self {
            replace_with: replace_with.to_string(),
            regex,
        })
    }
}

#[cfg(test)]
mod tests {
    use anyhow::Result;

    use super::*;

    #[test]
    fn struct_rule() -> Result<()> {
        assert_eq!(
            StructRule::new("replace", "Point2D { x: i32, y: i32 }")?
                .convert("replacer::rust_struct! {replace; Point { x: i32, y: i32};}")?,
            "struct Point2D { x: i32, y: i32 }"
        );
        assert_eq!(
            StructRule::new("replace", "Point2D { x: i32, y: i32 }")?
                .convert("replacer::rust_struct! {pub replace; Point{ x: i32, y: i32};}")?,
            "pub struct Point2D { x: i32, y: i32 }"
        );
        assert_eq!(
            StructRule::new("replace", "i32")?.convert("Hello world!")?,
            "Hello world!"
        );

        Ok(())
    }
}