1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#![no_std]

use core::slice;
use core::str::{from_utf8_unchecked, Lines};

/// Trait extending [`str`] with the [`paragraphs`] method.
///
/// See its documentation for more.
///
/// [`paragraphs`]: ParagraphsExt::paragraphs
pub trait ParagraphsExt {
    /// An iterator over the paragraphs of a string, as string slices.
    ///
    /// Paragraphs consist of one or more lines of text containing non-whitespace
    /// characters surrounded by lines that only contain whitespace characters.
    ///
    /// [`paragraphs`] mirrors the behavior of the standard library's [`lines`].
    /// See its documentation for more details.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// # use split_paragraphs::ParagraphsExt;
    /// let text = "foo\r\nbar\n\nbaz\r";
    /// let mut paragraphs = text.paragraphs();
    ///
    /// assert_eq!(Some("foo\r\nbar"), paragraphs.next());
    /// // Trailing carriage return is included in the last paragraph
    /// assert_eq!(Some("baz\r"), paragraphs.next());
    ///
    /// assert_eq!(None, paragraphs.next());
    /// ```
    ///
    /// The final paragraph does not require any ending:
    ///
    /// ```
    /// # use split_paragraphs::ParagraphsExt;
    /// let text = "\n\n\nfoo\nbar\n\r\nbaz";
    /// let mut paragraphs = text.paragraphs();
    ///
    /// assert_eq!(Some("foo\nbar"), paragraphs.next());
    /// assert_eq!(Some("baz"), paragraphs.next());
    ///
    /// assert_eq!(None, paragraphs.next());
    /// ```
    fn paragraphs(&self) -> Paragraphs;
}

impl ParagraphsExt for str {
    fn paragraphs(&self) -> Paragraphs {
        Paragraphs {
            lines: self.lines(),
        }
    }
}

/// An iterator over the paragraphs of a string, as string slices.
///
/// This struct is created with the [`paragraphs`] method on [`str`] via
/// the [`ParagraphsExt`] trait.
/// See its documentation for more.
///
/// [`paragraphs`]: ParagraphsExt::paragraphs
pub struct Paragraphs<'a> {
    lines: Lines<'a>,
}

impl<'a> Iterator for Paragraphs<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        let first_line = self.lines.next()?;

        let first_non_empty_line = if first_line.trim().is_empty() {
            loop {
                let line = self.lines.next()?;
                if !line.trim().is_empty() {
                    break line;
                }
            }
        } else {
            first_line
        };

        let mut last_non_empty_line = first_non_empty_line;
        loop {
            let line = self.lines.next();
            if line.is_none() {
                break;
            }
            let line = line.unwrap();
            if line.trim().is_empty() {
                break;
            }
            last_non_empty_line = line;
        }

        let result: &str = unsafe {
            from_utf8_unchecked(slice::from_raw_parts(
                first_non_empty_line.as_ptr(),
                last_non_empty_line
                    .as_ptr()
                    .offset_from(first_non_empty_line.as_ptr()) as usize
                    + last_non_empty_line.len(),
            ))
        };

        Some(result)
    }
}

impl<'a> DoubleEndedIterator for Paragraphs<'a> {
    fn next_back(&mut self) -> Option<Self::Item> {
        let last_line = self.lines.next_back()?;

        let last_non_empty_line = if last_line.trim().is_empty() {
            loop {
                let line = self.lines.next_back()?;
                if !line.trim().is_empty() {
                    break line;
                }
            }
        } else {
            last_line
        };

        let mut first_non_empty_line = last_non_empty_line;
        loop {
            let line = self.lines.next_back();
            if line.is_none() {
                break;
            }
            let line = line.unwrap();
            if line.trim().is_empty() {
                break;
            }
            first_non_empty_line = line;
        }

        let result: &str = unsafe {
            from_utf8_unchecked(slice::from_raw_parts(
                first_non_empty_line.as_ptr(),
                last_non_empty_line
                    .as_ptr()
                    .offset_from(first_non_empty_line.as_ptr()) as usize
                    + last_non_empty_line.len(),
            ))
        };

        Some(result)
    }
}