ical/
line.rs

1//! Read and unfold a line from a `BufRead`.
2//!
3//! Individual lines within vCard are delimited by the [RFC5322] line
4//! break, which is a CRLF sequence (U+000D followed by U+000A).  Long
5//! logical lines of text can be split into a multiple-physical-line
6//! representation using the following folding technique.  Content lines
7//! SHOULD be folded to a maximum width of 75 octets, excluding the line
8//! break.  Multi-octet characters MUST remain contiguous.  The rationale
9//! for this folding process can be found in [RFC5322], Section 2.1.1.
10//!
11//! A logical line MAY be continued on the next physical line anywhere
12//! between two characters by inserting a CRLF immediately followed by a
13//! single white space character (space (U+0020) or horizontal tab
14//! (U+0009)).  The folded line MUST contain at least one character.  Any
15//! sequence of CRLF followed immediately by a single white space
16//! character is ignored (removed) when processing the content type.
17//!
18//! [RFC5322]: https://tools.ietf.org/html/rfc5322
19//! # Examples
20//!
21//! ```toml
22//! [dependencies.ical]
23//! version = "0.3.*"
24//! default-features = false
25//! features = ["line-reader"]
26//! ```
27//!
28//! ```rust
29//! extern crate ical;
30//!
31//! use std::io::BufReader;
32//! use std::fs::File;
33//!
34//! let buf = BufReader::new(File::open("./tests/ressources/vcard_input.vcf").unwrap());
35//!
36//! let reader = ical::LineReader::new(buf);
37//!
38//! for line in reader {
39//!     println!("{}", line);
40//! }
41//! ```
42
43use std::fmt;
44use std::io::BufRead;
45use std::iter::Iterator;
46
47/// An unfolded raw line.
48///
49/// Its inner is only a raw line from the file. No parsing or checking have
50/// been made yet.
51#[derive(Debug, Clone, Default)]
52pub struct Line {
53    inner: String,
54    number: usize,
55}
56
57impl Line {
58    /// Return a new `Line` object.
59    pub fn new(line: String, line_number: usize) -> Line {
60        Line {
61            inner: line,
62            number: line_number,
63        }
64    }
65
66    /// Return a `&str`
67    pub fn as_str(&self) -> &str {
68        self.inner.as_str()
69    }
70
71    /// Return the line number.
72    pub fn number(&self) -> usize {
73        self.number
74    }
75}
76
77impl fmt::Display for Line {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        write!(f, "Line {}: {}", self.number, self.inner)
80    }
81}
82
83/// A trait generic for implementing line reading use crate::by `PropertyParser`.
84pub trait LineRead {
85    /// Return the next line unwrapped and formated.
86    fn next_line(&mut self) -> Option<Line>;
87}
88
89#[derive(Debug, Clone, Default)]
90/// Take a `BufRead` and return the unfolded `Line`.
91pub struct LineReader<B> {
92    reader: B,
93    saved: Option<String>,
94    number: usize,
95}
96
97impl<B: BufRead> LineReader<B> {
98    /// Return a new `LineReader` from a `Reader`.
99    pub fn new(reader: B) -> LineReader<B> {
100        LineReader {
101            reader,
102            saved: None,
103            number: 0,
104        }
105    }
106}
107
108impl<B: BufRead> LineRead for LineReader<B> {
109    fn next_line(&mut self) -> Option<Line> {
110        let mut next_line = String::new();
111        let mut line_number: usize = 0;
112
113        if let Some(start) = self.saved.take() {
114            // If during the last iteration a new line have been saved, start with.
115            next_line.push_str(start.as_str());
116            self.number += 1;
117            line_number = self.number;
118        } else {
119            // This is the first iteration, next_start isn't been filled yet.
120            for line in self.reader.by_ref().lines() {
121                let line = line.ok()?;
122                self.number += 1;
123
124                if !line.is_empty() {
125                    next_line = line.trim_end().to_string();
126                    line_number = self.number;
127                    break;
128                }
129            }
130        }
131
132        for line in self.reader.by_ref().lines() {
133            let mut line = line.ok()?;
134
135            if line.is_empty() {
136                self.number += 1;
137            } else if line.starts_with(' ') || line.starts_with('\t') {
138                // This is a multi-lines attribute.
139
140                // Remove the whitespace character and join with the current line.
141                line.remove(0);
142                next_line.push_str(line.trim_end());
143                self.number += 1;
144            } else {
145                // This is a new attribute so it need to be saved it for
146                // the next iteration.
147                self.saved = Some(line.trim().to_string());
148                break;
149            }
150        }
151
152        if next_line.is_empty() {
153            None
154        } else {
155            Some(Line::new(next_line, line_number))
156        }
157    }
158}
159
160impl<B: BufRead> Iterator for LineReader<B> {
161    type Item = Line;
162
163    fn next(&mut self) -> Option<Line> {
164        self.next_line()
165    }
166}