use std::{io, ops::Range};
pub trait RegexEngine: Sized {
type CompileError: std::error::Error + Send + Sync + 'static;
fn compile(re: &str) -> Result<Self, Self::CompileError>;
}
pub trait Haystack<R>: Sliceable
where
R: RegexEngine,
{
fn is_match_between(&self, re: &R, from: usize, to: usize) -> bool;
fn captures_between(&self, re: &R, from: usize, to: usize) -> Option<RawCaptures>;
}
pub trait Sliceable: Writable {
type Slice<'h>: Writable
where
Self: 'h;
fn char_at(&self, byte_offset: usize) -> Option<char>;
fn slice<'h>(&'h self, range: Range<usize>) -> Self::Slice<'h>;
fn max_len(&self) -> usize;
}
impl<T> Sliceable for T
where
T: AsRef<str> + ?Sized,
{
type Slice<'h>
= &'h str
where
Self: 'h;
fn char_at(&self, byte_offset: usize) -> Option<char> {
if byte_offset >= self.as_ref().len() {
return None;
}
self.as_ref()[byte_offset..].chars().next()
}
fn slice(&self, range: Range<usize>) -> &str {
&self.as_ref()[range]
}
fn max_len(&self) -> usize {
self.as_ref().len()
}
}
pub trait Writable {
fn write_to<W>(&self, w: &mut W) -> io::Result<usize>
where
W: io::Write;
}
impl<T> Writable for T
where
T: AsRef<str> + ?Sized,
{
fn write_to<W>(&self, w: &mut W) -> io::Result<usize>
where
W: io::Write,
{
w.write_all(self.as_ref().as_bytes())
.map(|_| self.as_ref().len())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct RawCaptures {
pub(crate) caps: Vec<Option<(usize, usize)>>,
}
impl RawCaptures {
pub fn new(caps: impl Iterator<Item = Option<(usize, usize)>>) -> Self {
let caps: Vec<_> = caps.collect();
assert!(!caps.is_empty(), "empty captures");
assert!(caps[0].is_some(), "full match is None");
Self { caps }
}
pub(crate) fn get_match(&self) -> (usize, usize) {
self.caps[0].unwrap()
}
pub(crate) fn from(&self) -> usize {
self.get_match().0
}
pub(crate) fn to(&self) -> usize {
self.get_match().1
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Captures<'h, H>
where
H: Sliceable + ?Sized,
{
haystack: &'h H,
caps: Vec<Option<(usize, usize)>>,
}
impl<'h, H> Captures<'h, H>
where
H: Sliceable + ?Sized,
{
pub(crate) fn new(haystack: &'h H, caps: Vec<Option<(usize, usize)>>) -> Self {
Self { haystack, caps }
}
pub fn from(&self) -> usize {
self.get_match().0
}
pub fn to(&self) -> usize {
self.get_match().1
}
pub fn get_match(&self) -> (usize, usize) {
self.caps[0].unwrap()
}
pub fn get(&self, n: usize) -> Option<(usize, usize)> {
self.caps.get(n).copied()?
}
pub fn len(&self) -> usize {
let (from, to) = self.get_match();
to - from
}
pub fn is_empty(&self) -> bool {
let (from, to) = self.get_match();
from == to
}
pub fn match_text(&self) -> H::Slice<'_> {
let (from, to) = self.get_match();
self.haystack.slice(from..to)
}
pub fn submatch_text(&self, n: usize) -> Option<H::Slice<'_>> {
let (from, to) = self.get(n)?;
Some(self.haystack.slice(from..to))
}
pub fn iter_submatches(&self) -> impl Iterator<Item = Option<H::Slice<'_>>> {
self.caps
.iter()
.map(|cap| cap.map(|(from, to)| self.haystack.slice(from..to)))
}
}
#[cfg(feature = "regex")]
mod impl_for_regex {
use super::*;
use regex::{Error, Regex, RegexBuilder};
impl RegexEngine for Regex {
type CompileError = Error;
fn compile(re: &str) -> Result<Self, Self::CompileError> {
RegexBuilder::new(re).multi_line(true).build()
}
}
impl Haystack<Regex> for str {
fn is_match_between(&self, re: &Regex, from: usize, to: usize) -> bool {
re.is_match(&self[from..to])
}
fn captures_between(&self, re: &Regex, from: usize, to: usize) -> Option<RawCaptures> {
let caps = re.captures(&self[from..to])?;
Some(RawCaptures::new(caps.iter().map(|cap| {
cap.map(|cap| (cap.start() + from, cap.end() + from))
})))
}
}
}