use velociredactor::detect::{Detection, Detector, LeafContext};
use velociredactor::format::{Format, FormatError, Leaf, LeafVisitor, Splicer};
use velociredactor::{Allow, FormatHint, Redactor};
struct TicketDetector;
impl Detector for TicketDetector {
fn name(&self) -> &str {
"ticket"
}
fn detect(&self, value: &str, _ctx: &LeafContext<'_>, out: &mut Vec<Detection>) {
for (start, _) in value.match_indices("TICKET-") {
let digits = value[start + 7..]
.bytes()
.take_while(u8::is_ascii_digit)
.count();
if digits > 0 {
out.push(Detection::new(start..start + 7 + digits, self.name()));
}
}
}
}
struct Arrow;
impl Format for Arrow {
fn name(&self) -> &str {
"arrow"
}
fn extensions(&self) -> &[&str] {
&["arrow"]
}
fn rewrite(&self, input: &[u8], visitor: &mut dyn LeafVisitor) -> Result<Vec<u8>, FormatError> {
let text = std::str::from_utf8(input).map_err(|e| FormatError::new("arrow", e))?;
let mut splicer = Splicer::new(input);
let mut offset = 0;
for line in text.split_inclusive('\n') {
if let Some((key, value)) = line.trim_end().split_once(" => ") {
let start = offset + key.len() + 4;
let leaf = Leaf::new(value)
.with_key(Some(key.trim()))
.with_offset(start);
if let Some(replacement) = visitor.leaf(&leaf) {
let range = start..start + value.len();
splicer.apply(range.clone(), range, value, &replacement, str::to_owned);
}
}
offset += line.len();
}
Ok(splicer.finish())
}
}
fn main() {
let redactor = Redactor::builder()
.detector(TicketDetector)
.format(Arrow)
.build();
let input = "summary => fixes TICKET-4821\nTICKET-9 => not a value\n";
let redaction = redactor
.redact(input.as_bytes(), FormatHint::Path("notes.arrow".as_ref()))
.expect("valid input");
for finding in redaction.findings() {
println!("{} found by {}", finding.token(), finding.detector);
}
let output = redaction.render(&Allow::none()).expect("renders");
print!("{}", String::from_utf8_lossy(&output));
}