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}