Skip to main content

tor_netdoc/parse2/
lines.rs

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