hyperlink 0.2.1

Very fast link checker for CI.
use std::fmt;
use std::hash::Hash;
use std::mem;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Paragraph {
    hash: [u8; 32],
}

pub struct ParagraphHasher {
    hasher: blake3::Hasher,
}

pub trait ParagraphWalker: Send {
    type Paragraph: Clone + Eq + PartialEq + Hash + Ord + PartialOrd + Send + 'static;

    fn new() -> Self;

    #[inline]
    fn is_noop() -> bool {
        false
    }

    fn update_raw(&mut self, text: &[u8]);
    fn finish_paragraph(&mut self) -> Option<Self::Paragraph>;

    fn update(&mut self, text: &[u8]) {
        let mut start = 0;
        for (i, &c) in text.iter().enumerate() {
            if c.is_ascii_whitespace() {
                if start < i {
                    self.update_raw(&text[start..i]);
                }
                start = i + 1;
            }
        }
        if start < text.len() {
            self.update_raw(&text[start..]);
        }
    }
}

impl ParagraphWalker for ParagraphHasher {
    type Paragraph = Paragraph;

    fn new() -> Self {
        ParagraphHasher {
            hasher: blake3::Hasher::new(),
        }
    }

    fn update_raw(&mut self, text: &[u8]) {
        self.hasher.update(text);
    }

    fn finish_paragraph(&mut self) -> Option<Self::Paragraph> {
        let rv = Paragraph {
            hash: self.hasher.finalize().into(),
        };
        self.hasher.reset();
        Some(rv)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct DebugParagraph<T> {
    inner: T,
    contents: String,
}

impl fmt::Display for DebugParagraph<Paragraph> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.contents)
    }
}

pub struct DebugParagraphWalker<T> {
    inner: T,
    contents: String,
}

impl<T> ParagraphWalker for DebugParagraphWalker<T>
where
    T: ParagraphWalker,
{
    type Paragraph = DebugParagraph<T::Paragraph>;

    fn new() -> Self {
        DebugParagraphWalker {
            inner: T::new(),
            contents: String::new(),
        }
    }

    fn update_raw(&mut self, text: &[u8]) {
        self.inner.update(text);
        self.contents.push_str(&String::from_utf8_lossy(text));
    }

    fn finish_paragraph(&mut self) -> Option<Self::Paragraph> {
        let inner = self.inner.finish_paragraph()?;
        Some(DebugParagraph {
            inner,
            contents: mem::take(&mut self.contents),
        })
    }
}

pub struct NoopParagraphWalker;

#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum VoidParagraph {}

impl ParagraphWalker for NoopParagraphWalker {
    type Paragraph = VoidParagraph;

    #[inline]
    fn new() -> Self {
        NoopParagraphWalker
    }

    #[inline]
    fn is_noop() -> bool {
        true
    }

    #[inline]
    fn update_raw(&mut self, _text: &[u8]) {}

    #[inline]
    fn finish_paragraph(&mut self) -> Option<Self::Paragraph> {
        None
    }
}