105 lines
2.3 KiB
Rust
105 lines
2.3 KiB
Rust
use std::sync::Arc;
|
|
|
|
use pile_config::Label;
|
|
use regex::Regex;
|
|
use smartstring::{LazyCompact, SmartString};
|
|
|
|
use crate::{
|
|
extract::traits::{ExtractState, ListExtractor, ObjectExtractor},
|
|
value::PileValue,
|
|
};
|
|
|
|
struct RegexData {
|
|
regex: Arc<Regex>,
|
|
/// Captured substrings indexed by group index (0 = whole match).
|
|
captures: Vec<Option<Arc<SmartString<LazyCompact>>>>,
|
|
}
|
|
|
|
impl RegexData {
|
|
fn new(regex: Arc<Regex>, input: &str) -> Option<Self> {
|
|
let caps = regex.captures(input)?;
|
|
let captures = caps
|
|
.iter()
|
|
.map(|m| m.map(|m| Arc::new(m.as_str().into())))
|
|
.collect();
|
|
Some(Self { regex, captures })
|
|
}
|
|
}
|
|
|
|
/// Exposes named capture groups as object fields.
|
|
pub struct RegexExtractor(Arc<RegexData>);
|
|
|
|
impl RegexExtractor {
|
|
/// Run `regex` against `input`. Returns `None` if there is no match.
|
|
pub fn new(regex: Arc<Regex>, input: &str) -> Option<Self> {
|
|
Some(Self(Arc::new(RegexData::new(regex, input)?)))
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl ObjectExtractor for RegexExtractor {
|
|
async fn field(
|
|
&self,
|
|
_state: &ExtractState,
|
|
name: &Label,
|
|
args: Option<&str>,
|
|
) -> Result<Option<PileValue>, std::io::Error> {
|
|
if args.is_some() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let Some(idx) = self
|
|
.0
|
|
.regex
|
|
.capture_names()
|
|
.position(|n| n == Some(name.as_str()))
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
|
|
Ok(Some(
|
|
match self.0.captures.get(idx).and_then(|v| v.as_ref()) {
|
|
Some(s) => PileValue::String(s.clone()),
|
|
None => PileValue::Null,
|
|
},
|
|
))
|
|
}
|
|
|
|
async fn fields(&self) -> Result<Vec<Label>, std::io::Error> {
|
|
#[expect(clippy::unwrap_used)]
|
|
Ok(self
|
|
.0
|
|
.regex
|
|
.capture_names()
|
|
.flatten()
|
|
.map(|n| Label::new(n).unwrap())
|
|
.collect())
|
|
}
|
|
|
|
fn as_list(&self) -> Option<Arc<dyn ListExtractor>> {
|
|
Some(Arc::new(RegexExtractor(self.0.clone())))
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl ListExtractor for RegexExtractor {
|
|
async fn get(
|
|
&self,
|
|
_state: &ExtractState,
|
|
idx: usize,
|
|
) -> Result<Option<PileValue>, std::io::Error> {
|
|
let raw_idx = idx + 1;
|
|
let Some(slot) = self.0.captures.get(raw_idx) else {
|
|
return Ok(None);
|
|
};
|
|
Ok(Some(match slot {
|
|
Some(s) => PileValue::String(s.clone()),
|
|
None => PileValue::Null,
|
|
}))
|
|
}
|
|
|
|
async fn len(&self, _state: &ExtractState) -> Result<usize, std::io::Error> {
|
|
Ok(self.0.captures.len().saturating_sub(1))
|
|
}
|
|
}
|