#![deny(
clippy::unwrap_used,
clippy::panic,
clippy::expect_used,
unused_must_use
)]
#![warn(clippy::pedantic)]
#![allow(clippy::must_use_candidate)]
#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use]
extern crate alloc;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt::Display;
mod case;
use case::MatchCase;
mod compiler;
use compiler::RegexCompiler;
mod error;
mod matcher;
pub use error::RegexError;
type Result<T> = core::result::Result<T, RegexError>;
#[doc(inline)]
pub use matcher::{RegexMatch, RegexMatcher};
#[derive(Debug)]
pub struct Regex {
matches: Box<[MatchCase]>,
}
impl Display for Regex {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut first = true;
for c in &self.matches {
if !first {
write!(f, " => ")?;
}
first = false;
write!(f, "{c:#?}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct RegexConf {
pub case_sensitive: bool,
pub ignore_captures_in_result: bool,
}
const DEFAULT_REGEX_CONF: RegexConf = RegexConf {
case_sensitive: true,
ignore_captures_in_result: false,
};
impl Default for RegexConf {
fn default() -> Self {
DEFAULT_REGEX_CONF
}
}
impl Regex {
pub fn compile(src: &str) -> Result<Self> {
RegexCompiler::new(src).process()
}
#[must_use]
#[inline]
pub fn find_matches<'a>(&'a self, src: &'a str) -> RegexMatcher<'a> {
self.find_matches_with_conf(src, DEFAULT_REGEX_CONF)
}
#[must_use]
#[inline]
pub fn find_matches_with_conf<'a>(&'a self, src: &'a str, conf: RegexConf) -> RegexMatcher<'a> {
RegexMatcher::new(src, &self.matches, conf)
}
#[must_use]
#[inline]
pub fn test(&self, src: &str) -> bool {
self.find_matches(src).next().is_some()
}
#[must_use]
#[inline]
pub fn test_with_conf(&self, src: &str, conf: RegexConf) -> bool {
self.find_matches_with_conf(src, conf).next().is_some()
}
pub fn replace<'a>(&self, src: &'a str, replacement: &str) -> Cow<'a, str> {
let matches = self.find_matches(src);
if matches.clone().next().is_none() {
return Cow::Borrowed(src);
}
let mut result = String::new();
let mut curr = 0;
for m in matches {
let (start, end) = m.span();
result.push_str(&src[curr..start]);
result.push_str(replacement);
curr = end;
}
if let Some(remainder) = src.get(curr..) {
result.push_str(remainder);
}
Cow::Owned(result)
}
}
impl TryFrom<&str> for Regex {
type Error = RegexError;
fn try_from(value: &str) -> Result<Self> {
Regex::compile(value)
}
}
pub trait RegexTestable {
fn matches_regex(&self, regex: &str) -> bool;
}
impl RegexTestable for &str {
fn matches_regex(&self, regex: &str) -> bool {
Regex::compile(regex)
.map(|regex| regex.test(self))
.unwrap_or(false)
}
}
pub trait ReplaceRegex {
fn replace_regex<'a>(&'a self, regex: &str, replacement: &str) -> Result<Cow<'a, str>>;
}
impl ReplaceRegex for &str {
fn replace_regex<'a>(&'a self, regex: &str, replacement: &str) -> Result<Cow<'a, str>> {
Regex::compile(regex).map(|regex| regex.replace(self, replacement))
}
}
#[cfg(test)]
mod test;
#[cfg(feature = "bindings")]
pub mod ffi;