tailspin 6.0.0

A log file highlighter
Documentation
use memchr::{memchr, memchr3};
use regex::{Regex, RegexBuilder};

use crate::style::Style;

use super::super::span::{Collector, Finder};

#[derive(Debug)]
pub(crate) struct NumberFinder {
    regex: Regex,
    style: Style,
}

impl NumberFinder {
    pub fn new(style: Style) -> Self {
        let pattern = r"(?x)
            \b          # start of number
            \d+         # integer part
            (?:\.\d+)?  # optional fractional
            \b          # end of number
        ";

        let regex = RegexBuilder::new(pattern)
            .unicode(false)
            .build()
            .expect("hardcoded number regex must compile");

        Self { regex, style }
    }
}

impl Finder for NumberFinder {
    fn find_spans(&self, input: &str, collector: &mut Collector) {
        let bytes = input.as_bytes();
        if memchr3(b'0', b'1', b'2', bytes).is_none()
            && memchr3(b'3', b'4', b'5', bytes).is_none()
            && memchr3(b'6', b'7', b'8', bytes).is_none()
            && memchr(b'9', bytes).is_none()
        {
            return;
        }

        for m in self.regex.find_iter(input) {
            collector.push(m.start(), m.end(), self.style);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::style::Color;

    #[test]
    fn finds_numbers() {
        let finder = NumberFinder::new(Style::new().fg(Color::Cyan));
        let mut collector = Collector::new(0);
        finder.find_spans("hello 42 world 3.14", &mut collector);

        let spans = collector.into_spans();
        assert_eq!(spans.len(), 2);
        assert_eq!(&"hello 42 world 3.14"[spans[0].start..spans[0].end], "42");
        assert_eq!(&"hello 42 world 3.14"[spans[1].start..spans[1].end], "3.14");
    }

    #[test]
    fn no_match_produces_no_spans() {
        let finder = NumberFinder::new(Style::new().fg(Color::Cyan));
        let mut collector = Collector::new(0);
        finder.find_spans("no numbers here", &mut collector);
        assert!(collector.into_spans().is_empty());
    }
}