pub extern crate pyo3;
use pyo3::PyResult;
use pyo3::prelude::*;
use pyo3::types::{PyAny, PyDict, PyIterator, PyModule};
use std::collections::HashMap;
#[derive(Debug)]
pub struct PyRegex {
compiled: Py<PyAny>,
}
impl PyRegex {
pub fn new(pattern: &str) -> PyResult<Self> {
Python::with_gil(|py| {
Ok(PyRegex {
compiled: PyModule::import(py, "regex")?
.call_method("compile", (pattern,), None)?
.into(),
})
})
}
fn kwargs(py: Python) -> Option<Bound<PyDict>> {
let kwargs = PyDict::new(py);
kwargs.set_item("concurrent", true).ok()?;
Some(kwargs)
}
pub fn search_match(&self, text: &str) -> PyResult<Option<PyRegexMatch>> {
Python::with_gil(|py| {
let result =
self.compiled
.call_method(py, "search", (text,), Self::kwargs(py).as_ref())?;
Ok(if result.is_none(py) {
None
} else {
Some(PyRegexMatch { inner: result })
})
})
}
pub fn find_iter(&self, text: &str) -> PyResult<Vec<PyRegexMatch>> {
Python::with_gil(|py| {
let mut matches = Vec::new();
let binding =
self.compiled
.call_method(py, "finditer", (text,), Self::kwargs(py).as_ref())?;
let iter = binding.downcast_bound::<PyIterator>(py)?;
for item in iter {
let match_obj = item?;
matches.push(PyRegexMatch {
inner: match_obj.into(),
});
}
Ok(matches)
})
}
pub fn is_match(&self, text: &str) -> PyResult<bool> {
Python::with_gil(|py| {
Ok(!self
.compiled
.call_method(py, "search", (text,), Self::kwargs(py).as_ref())?
.is_none(py))
})
}
pub fn find_all(&self, text: &str) -> PyResult<Vec<String>> {
Python::with_gil(|py| {
self.compiled
.call_method(py, "findall", (text,), Self::kwargs(py).as_ref())?
.extract::<Vec<String>>(py)
})
}
pub fn replace(&self, text: &str, replacement: &str) -> PyResult<String> {
Python::with_gil(|py| {
self.compiled
.call_method(py, "sub", (replacement, text), Self::kwargs(py).as_ref())?
.extract::<String>(py)
})
}
pub fn split(&self, text: &str) -> PyResult<Vec<String>> {
Python::with_gil(|py| {
self.compiled
.call_method(py, "split", (text,), Self::kwargs(py).as_ref())?
.extract::<Vec<String>>(py)
})
}
pub fn escape(str: &str, special_only: bool, literal_spaces: bool) -> PyResult<String> {
Python::with_gil(|py| {
let kwargs = PyDict::new(py);
kwargs.set_item("special_only", special_only)?;
kwargs.set_item("literal_spaces", literal_spaces)?;
PyModule::import(py, "regex")?
.call_method("escape", (str,), Some::<Bound<PyDict>>(kwargs).as_ref())?
.extract::<String>()
})
}
}
pub struct PyRegexMatch {
inner: Py<PyAny>,
}
impl PyRegexMatch {
pub fn group(&self, group: u16) -> PyResult<Option<String>> {
Python::with_gil(|py| {
self.inner
.call_method1(py, "group", (group as usize,))?
.extract::<Option<String>>(py)
})
}
pub fn groups(&self) -> PyResult<Vec<Option<String>>> {
Python::with_gil(|py| {
self.inner
.call_method1(py, "groups", ())?
.extract::<Vec<Option<String>>>(py)
})
}
pub fn groupdict(&self) -> PyResult<HashMap<String, Option<String>>> {
Python::with_gil(|py| {
self.inner
.call_method1(py, "groupdict", ())?
.extract::<HashMap<String, Option<String>>>(py)
})
}
pub fn start(&self, group: u16) -> PyResult<isize> {
Python::with_gil(|py| {
self.inner
.call_method1(py, "start", (group as usize,))?
.extract::<isize>(py)
})
}
pub fn end(&self, group: u16) -> PyResult<isize> {
Python::with_gil(|py| {
self.inner
.call_method1(
py,
"end",
(group as usize,),
)?
.extract::<isize>(py)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_escape() -> PyResult<()> {
pyo3::prepare_freethreaded_python();
assert_eq!(PyRegex::escape("[]", false, false)?, "\\[\\]");
Ok(())
}
#[test]
fn test_pyregex_match_methods() -> PyResult<()> {
pyo3::prepare_freethreaded_python();
let pattern = r"(?P<word>\w+)-(\d+)";
let text = "Test-123";
let re = PyRegex::new(pattern)?;
if let Some(m) = re.search_match(text)? {
assert_eq!(m.group(0)?, Some("Test-123".to_string()));
assert_eq!(m.group(1)?, Some("Test".to_string()));
assert_eq!(m.group(2)?, Some("123".to_string()));
let gd = m.groupdict()?;
assert_eq!(gd.get("word").cloned(), Some(Some("Test".to_string())));
let start = m.start(0)?;
let end = m.end(0)?;
println!("Match span for group 0: {}..{}", start, end);
} else {
panic!("No match found");
}
Ok(())
}
}