Skip to main content

tor_netdoc/parse2/
lines.rs

1//! Version of `std::str::Lines` that tracks line numbers and has `remainder()`
2
3use extend::ext;
4
5/// Version of `std::str::Lines` that tracks line numbers and has `remainder()`
6///
7/// Implements `Iterator`, returning one `str` for each line, with the `'\n'` removed.
8///
9/// Missing final newline is silently tolerated.
10#[derive(Debug, Clone)]
11pub struct Lines<'s> {
12    /// Line number at the start of `rest`
13    lno: usize,
14    /// The remaining part of the document
15    rest: &'s str,
16}
17
18/// Extension trait adding a method to `str`
19#[ext(name = StrExt)]
20pub impl str {
21    /// Remove `count` bytes from the end of `self`
22    ///
23    /// # Panics
24    ///
25    /// Panics if `count > self.len()`.
26    #[allow(clippy::string_slice)] // TODO
27    fn strip_end_counted(&self, count: usize) -> &str {
28        &self[0..self.len().checked_sub(count).expect("stripping too much")]
29    }
30}
31
32/// Information about the next line we have peeked
33///
34/// To get the line as an actual string, pass this to `peeked_line`.
35///
36/// # Correctness
37///
38/// Each `Peeked` is only valid in conjunction with the `Lines` that returned it,
39/// and becomes invalidated if the `Lines` is modified
40/// (ie, it can be invalidated by calls that take `&mut Lines`).
41///
42/// Cloning a `Peeked` is hazrdous since using it twice would be wrong.
43///
44/// None of this is checked at compile- or run-time.
45// We could perhaps use lifetimes somehow to enforce this,
46// but `ItemStream` wants `Peeked` to be `'static` and `Clone`.
47#[derive(Debug, Clone, amplify::Getters)]
48pub struct Peeked {
49    /// The length of the next line
50    //
51    // # Invariant
52    //
53    // `rest[line_len]` is a newline, or `line_len` is `rest.len()`.
54    #[getter(as_copy)]
55    line_len: usize,
56}
57
58impl<'s> Lines<'s> {
59    /// Start reading lines from a document as a string
60    pub fn new(s: &'s str) -> Self {
61        Lines { lno: 1, rest: s }
62    }
63
64    /// Line number of the next line we'll read
65    pub fn peek_lno(&self) -> usize {
66        self.lno
67    }
68
69    /// Peek the next line
70    pub fn peek(&self) -> Option<Peeked> {
71        if self.rest.is_empty() {
72            None
73        } else if let Some(newline) = self.rest.find('\n') {
74            Some(Peeked { line_len: newline })
75        } else {
76            Some(Peeked {
77                line_len: self.rest.len(),
78            })
79        }
80    }
81
82    /// The rest of the file as a `str`
83    pub fn remaining(&self) -> &'s str {
84        self.rest
85    }
86
87    /// After `peek`, advance to the next line, consuming the one that was peeked
88    ///
89    /// # Correctness
90    ///
91    /// See [`Peeked`].
92    #[allow(clippy::needless_pass_by_value)] // Yes, we want to consume Peeked
93    #[allow(clippy::string_slice)] // TODO
94    pub fn consume_peeked(&mut self, peeked: Peeked) -> &'s str {
95        let line = self.peeked_line(&peeked);
96        self.rest = &self.rest[peeked.line_len..];
97        if !self.rest.is_empty() {
98            debug_assert!(self.rest.starts_with('\n'));
99            self.rest = &self.rest[1..];
100        }
101        self.lno += 1;
102        line
103    }
104
105    /// After `peek`, obtain the actual peeked line as a `str`
106    ///
107    /// As with [`<Lines as Iterator>::next`](Lines::next), does not include the newline.
108    // Rustdoc doesn't support linking` fully qualified syntax.
109    // https://github.com/rust-lang/rust/issues/74563
110    ///
111    /// # Correctness
112    ///
113    /// See [`Peeked`].
114    #[allow(clippy::string_slice)] // TODO
115    pub fn peeked_line(&self, peeked: &Peeked) -> &'s str {
116        &self.rest[0..peeked.line_len()]
117    }
118}
119
120impl<'s> Iterator for Lines<'s> {
121    type Item = &'s str;
122
123    fn next(&mut self) -> Option<&'s str> {
124        let peeked = self.peek()?;
125        let line = self.consume_peeked(peeked);
126        Some(line)
127    }
128}