normalizer-tr 0.4.0

Bounded Turkish text-to-speech normalization with original-source diagnostics
Documentation
use crate::{LimitKind, NormalizeError, WorkControl};

pub(super) struct Output<'a> {
    text: String,
    maximum: usize,
    control: &'a WorkControl,
}

impl<'a> Output<'a> {
    pub(super) fn new(maximum: usize, control: &'a WorkControl) -> Self {
        Self {
            text: String::new(),
            maximum,
            control,
        }
    }

    pub(super) fn append(&mut self, text: &str) -> Result<(), NormalizeError> {
        self.control.check()?;
        self.text
            .len()
            .checked_add(text.len())
            .filter(|length| *length <= self.maximum)
            .ok_or(NormalizeError::LimitExceeded(LimitKind::Result))?;
        self.text.push_str(text);
        Ok(())
    }

    pub(super) fn word(&mut self, word: &str) -> Result<(), NormalizeError> {
        if !self.text.is_empty() && !self.text.ends_with(char::is_whitespace) {
            self.append(" ")?;
        }
        self.append(word)
    }

    pub(super) fn finish(self) -> String {
        self.text
    }
}

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

    #[test]
    fn emission_accepts_exact_bytes_and_rejects_amplification_before_growth() {
        let control = WorkControl::default();
        let mut output = Output::new("üç dört".len(), &control);
        assert_eq!(output.word("üç"), Ok(()));
        assert_eq!(output.word("dört"), Ok(()));
        assert_eq!(
            output.append("a"),
            Err(NormalizeError::LimitExceeded(LimitKind::Result))
        );
        assert_eq!(output.finish(), "üç dört");
    }

    #[test]
    fn emission_observes_cancellation_after_work_has_begun() {
        let control = WorkControl::default();
        let mut output = Output::new(10000, &control);
        for _ in 0..100 {
            output.word("bir").unwrap();
        }
        control.cancel();
        assert_eq!(output.word("iki"), Err(NormalizeError::Cancelled));
    }
}