From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- third_party/rust/textwrap/src/line_ending.rs | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 third_party/rust/textwrap/src/line_ending.rs (limited to 'third_party/rust/textwrap/src/line_ending.rs') diff --git a/third_party/rust/textwrap/src/line_ending.rs b/third_party/rust/textwrap/src/line_ending.rs new file mode 100644 index 0000000000..0514fe5fc0 --- /dev/null +++ b/third_party/rust/textwrap/src/line_ending.rs @@ -0,0 +1,88 @@ +//! Line ending detection and conversion. + +use std::fmt::Debug; + +/// Supported line endings. Like in the Rust standard library, two line +/// endings are supported: `\r\n` and `\n` +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LineEnding { + /// _Carriage return and line feed_ – a line ending sequence + /// historically used in Windows. Corresponds to the sequence + /// of ASCII control characters `0x0D 0x0A` or `\r\n` + CRLF, + /// _Line feed_ – a line ending historically used in Unix. + /// Corresponds to the ASCII control character `0x0A` or `\n` + LF, +} + +impl LineEnding { + /// Turns this [`LineEnding`] value into its ASCII representation. + #[inline] + pub const fn as_str(&self) -> &'static str { + match self { + Self::CRLF => "\r\n", + Self::LF => "\n", + } + } +} + +/// An iterator over the lines of a string, as tuples of string slice +/// and [`LineEnding`] value; it only emits non-empty lines (i.e. having +/// some content before the terminating `\r\n` or `\n`). +/// +/// This struct is used internally by the library. +#[derive(Debug, Clone, Copy)] +pub(crate) struct NonEmptyLines<'a>(pub &'a str); + +impl<'a> Iterator for NonEmptyLines<'a> { + type Item = (&'a str, Option); + + fn next(&mut self) -> Option { + while let Some(lf) = self.0.find('\n') { + if lf == 0 || (lf == 1 && self.0.as_bytes()[lf - 1] == b'\r') { + self.0 = &self.0[(lf + 1)..]; + continue; + } + let trimmed = match self.0.as_bytes()[lf - 1] { + b'\r' => (&self.0[..(lf - 1)], Some(LineEnding::CRLF)), + _ => (&self.0[..lf], Some(LineEnding::LF)), + }; + self.0 = &self.0[(lf + 1)..]; + return Some(trimmed); + } + if self.0.is_empty() { + None + } else { + let line = std::mem::take(&mut self.0); + Some((line, None)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn non_empty_lines_full_case() { + assert_eq!( + NonEmptyLines("LF\nCRLF\r\n\r\n\nunterminated") + .collect::)>>(), + vec![ + ("LF", Some(LineEnding::LF)), + ("CRLF", Some(LineEnding::CRLF)), + ("unterminated", None), + ] + ); + } + + #[test] + fn non_empty_lines_new_lines_only() { + assert_eq!(NonEmptyLines("\r\n\n\n\r\n").next(), None); + } + + #[test] + fn non_empty_lines_no_input() { + assert_eq!(NonEmptyLines("").next(), None); + } +} -- cgit v1.2.3