cmail_rpgp/
normalize_lines.rs

1//! # Line ending normalization module
2//!
3//! This crate provides a `normalize` method that takes an u8 iterator and returns
4//! a new one with newlines normalized to a single style.
5//!
6//! Based on <https://github.com/derekdreery/normalize-line-endings>.
7
8use std::iter::Peekable;
9
10use crate::line_writer::LineBreak;
11
12/// This struct wraps an u8 iterator to normalize line endings.
13pub struct Normalized<I>
14where
15    I: Iterator<Item = u8>,
16{
17    line_break: LineBreak,
18    iter: Peekable<I>,
19    prev_was_cr: bool,
20}
21
22impl<I: Iterator<Item = u8>> Normalized<I> {
23    /// Take a u8 iterator and return similar iterator with normalized line endings
24    ///
25    /// # Example
26    /// ```
27    /// use std::iter::FromIterator;
28    ///
29    /// use pgp::line_writer::LineBreak;
30    /// use pgp::normalize_lines::Normalized;
31    ///
32    /// let input = "This is a string \n with \r some \n\r\n random newlines\r\r\n\n";
33    /// assert_eq!(
34    ///     &String::from_utf8(Normalized::new(input.bytes(), LineBreak::Lf).collect()).unwrap(),
35    ///     "This is a string \n with \n some \n\n random newlines\n\n\n"
36    /// );
37    /// ```
38    pub fn new(iter: I, line_break: LineBreak) -> Normalized<I> {
39        Normalized {
40            iter: iter.peekable(),
41            prev_was_cr: false,
42            line_break,
43        }
44    }
45}
46
47impl<I: Iterator<Item = u8>> Iterator for Normalized<I> {
48    type Item = u8;
49
50    fn next(&mut self) -> Option<u8> {
51        match self.iter.peek() {
52            Some(b'\n') => {
53                match self.line_break {
54                    LineBreak::Lf => {
55                        if self.prev_was_cr {
56                            // we already inserted a \n
57                            let _ = self.iter.next();
58                        }
59
60                        self.iter.next()
61                    }
62                    LineBreak::Cr => {
63                        // skip \n
64                        let _ = self.iter.next();
65
66                        if self.prev_was_cr {
67                            self.prev_was_cr = false;
68                            self.next()
69                        } else {
70                            Some(b'\r')
71                        }
72                    }
73                    LineBreak::Crlf => {
74                        if self.prev_was_cr {
75                            self.prev_was_cr = false;
76                            self.iter.next()
77                        } else {
78                            self.prev_was_cr = true;
79                            Some(b'\r')
80                        }
81                    }
82                }
83            }
84            Some(b'\r') => match self.line_break {
85                LineBreak::Lf => {
86                    self.prev_was_cr = true;
87                    let _ = self.iter.next();
88                    Some(b'\n')
89                }
90                LineBreak::Cr => {
91                    self.prev_was_cr = true;
92                    self.iter.next()
93                }
94                LineBreak::Crlf => {
95                    if self.prev_was_cr {
96                        self.prev_was_cr = false;
97                        Some(b'\n')
98                    } else {
99                        self.prev_was_cr = true;
100                        self.iter.next()
101                    }
102                }
103            },
104            _ => match self.line_break {
105                LineBreak::Lf | LineBreak::Cr => {
106                    self.prev_was_cr = false;
107                    self.iter.next()
108                }
109                LineBreak::Crlf => {
110                    let res = if self.prev_was_cr {
111                        Some(b'\n')
112                    } else {
113                        self.iter.next()
114                    };
115                    self.prev_was_cr = false;
116                    res
117                }
118            },
119        }
120    }
121}
122
123// tests
124#[cfg(test)]
125mod tests {
126    #![allow(clippy::unwrap_used)]
127    use super::*;
128
129    #[test]
130    fn normalized_lf() {
131        let input = "This is a string \n with \r some \n\r\n random newlines\r\r\n\n";
132        assert_eq!(
133            &String::from_utf8(Normalized::new(input.bytes(), LineBreak::Lf).collect()).unwrap(),
134            "This is a string \n with \n some \n\n random newlines\n\n\n"
135        );
136    }
137
138    #[test]
139    fn normalized_cr() {
140        let input = "This is a string \n with \r some \n\r\n random newlines\r\r\n\n";
141        assert_eq!(
142            &String::from_utf8(Normalized::new(input.bytes(), LineBreak::Cr).collect()).unwrap(),
143            "This is a string \r with \r some \r\r random newlines\r\r\r"
144        );
145    }
146
147    #[test]
148    fn normalized_crlf() {
149        let input = "This is a string \n with \r some \n\r\n random newlines\r\r\n\n";
150        assert_eq!(
151            &String::from_utf8(Normalized::new(input.bytes(), LineBreak::Crlf).collect()).unwrap(),
152            "This is a string \r\n with \r\n some \r\n\r\n random newlines\r\n\r\n\r\n"
153        );
154    }
155}