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
110
111
112
use rlua::prelude::*;
use regex;

pub struct LuaRegex(regex::Regex);
pub struct LuaCapture<'a>(regex::Captures<'a>);

impl LuaUserData for LuaRegex {
    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_method("match", |_, this: &LuaRegex, val: String| {
            Ok(this.0.is_match(&val))
        });
        methods.add_method("replace_all", |_, this: &LuaRegex, (val, patt): (String, String)| {
            Ok(this.0.replace_all(&val, patt.as_str()).into_owned())
        });
        methods.add_method("capture", |_, this: &LuaRegex, val: String| {
            Ok(this.0.captures(Box::leak(val.into_boxed_str())).map(LuaCapture))
        });
    } 
}

impl<'a> LuaUserData for LuaCapture<'a> {
    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_method("get", |_, this: &LuaCapture, index: usize| {
            Ok(this.0.get(index).map(|s| s.as_str().to_string()))
        });
        methods.add_method("name", |_, this: &LuaCapture, name: String| {
            Ok(this.0.name(&name).map(|s| s.as_str().to_string()))
        });
        methods.add_method("to_table", |_, this: &LuaCapture, _: ()| {
            Ok(this.0.iter().filter_map(|s| s).map(|s| s.as_str().to_string()).collect::<Vec<String>>())
        })
    }
}

pub fn init(lua: &Lua) -> crate::Result<()> {
    let module = lua.create_table()?;

    module.set("new", lua.create_function(|_, expr: String| {
        regex::Regex::new(&expr).map(LuaRegex).map_err(LuaError::external)
    })?)?;

    //TODO: Move into method
    //TODO: Further investigation locally
//    module.set("capture_all", lua.create_function(|lua: &Lua, (expr, val): (String, String) | {
//        let regex = regex::Regex::new(&expr).map_err(LuaError::external)?;
//        let mut re = regex.captures_iter(Box::leak(val.into_boxed_str()));
//        let mut arc_iter = Arc::new(Some(re));
//        let mut f = move |_, _: ()| {
//            let result = match Arc::get_mut(&mut arc_iter).expect("entries iterator is mutably borrowed") {
//                Some(iter) => iter.next().map(LuaCapture),
//                None => None
//            };
//            if result.is_none() { *Arc::get_mut(&mut arc_iter).unwrap() = None; }
//            Ok(result)
//        };
//        Ok(lua.create_function_mut(f)?)
//    })?)?;

    lua.globals().set("regex", module)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn lua_regex_replace_all () {
        let lua = Lua::new();
        init(&lua).unwrap();

        lua.exec::<_, ()>(r#"
            local regex = regex.new([[(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})]])
            local before = "2012-03-14, 2013-01-01 and 2014-07-05"
            local result = regex:replace_all(before, "$m/$d/$y")

            assert(result == "03/14/2012, 01/01/2013 and 07/05/2014")
        "#, None).unwrap();
    }

    #[test]
    fn lua_regex_match () {
        let lua = Lua::new();
        init(&lua).unwrap();

        lua.exec::<_, ()>(r#"
            local regex = regex.new([[^\d{4}-\d{2}-\d{2}$]])
            local date = "2014-01-01"
            local result = regex:match(date)

            assert(result == true)
        "#, None).unwrap();
    }

    #[test]
    fn lua_regex_captures () {
        let lua = Lua::new();
        init(&lua).unwrap();

        lua.exec::<_, ()>(r#"
            local regex = regex.new([['([^']+)'\s+\((\d{4})\)]])
            local val = "Not my favorite movie: 'Citizen Kane' (1941)"
            local result = regex:capture(val):to_table()

            assert(result ~= nil)
            assert(result[1] == "'Citizen Kane' (1941)")
            assert(result[2] == "Citizen Kane")
            assert(result[3] == "1941")
        "#, None).unwrap();
    }
}