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//! # Examples
19//!
20//! ```toml
21//! [dependencies.ical]
22//! version = "0.3.*"
23//! default-features = false
24//! features = ["line-reader"]
25//! ```
26//!
27//! ```rust
28//! extern crate ical;
29//!
30//! use std::io::BufReader;
31//! use std::fs::File;
32//!
33//! let buf = BufReader::new(File::open("./tests/ressources/vcard_input.vcf").unwrap());
34//!
35//! let reader = ical::LineReader::new(buf);
36//!
37//! for line in reader {
38//! println!("{}", line);
39//! }
40//! ```
41
42use std::fmt;
43use std::io::BufRead;
44use std::iter::Iterator;
45
46/// An unfolded raw line.
47///
48/// Its inner is only a raw line from the file. No parsing or checking have
49/// been made yet.
50#[derive(Debug, Clone, Default)]
51pub struct Line {
52 inner: String,
53 number: usize,
54}
55
56impl Line {
57 /// Return a new `Line` object.
58 pub fn new(line: String, line_number: usize) -> Line {
59 Line {
60 inner: line,
61 number: line_number,
62 }
63 }
64
65 /// Return a `&str`
66 pub fn as_str(&self) -> &str {
67 self.inner.as_str()
68 }
69
70 /// Return the line number.
71 pub fn number(&self) -> usize {
72 self.number
73 }
74}
75
76impl fmt::Display for Line {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 write!(f, "Line {}: {}", self.number, self.inner)
79 }
80}
81
82/// A trait generic for implementing line reading use crate::by `PropertyParser`.
83pub trait LineRead {
84 /// Return the next line unwrapped and formated.
85 fn next_line(&mut self) -> Option<Line>;
86}
87
88#[derive(Debug, Clone, Default)]
89/// Take a `BufRead` and return the unfolded `Line`.
90pub struct LineReader<B> {
91 reader: B,
92 saved: Option<String>,
93 number: usize,
94}
95
96impl<B: BufRead> LineReader<B> {
97 /// Return a new `LineReader` from a `Reader`.
98 pub fn new(reader: B) -> LineReader<B> {
99 LineReader {
100 reader,
101 saved: None,
102 number: 0,
103 }
104 }
105}
106
107impl<B: BufRead> LineRead for LineReader<B> {
108 fn next_line(&mut self) -> Option<Line> {
109 let mut next_line = String::new();
110 let mut line_number: usize = 0;
111
112 if let Some(start) = self.saved.take() {
113 // If during the last iteration a new line have been saved, start with.
114 next_line.push_str(start.as_str());
115 self.number += 1;
116 line_number = self.number;
117 } else {
118 // This is the first iteration, next_start isn't been filled yet.
119 for line in self.reader.by_ref().lines() {
120 let line = line.unwrap();
121 self.number += 1;
122
123 if !line.is_empty() {
124 next_line = line.trim_end().to_string();
125 line_number = self.number;
126 break;
127 }
128 }
129 }
130
131 for line in self.reader.by_ref().lines() {
132 let mut line = line.unwrap();
133
134 if line.is_empty() {
135 self.number += 1;
136 } else if line.starts_with(' ') || line.starts_with('\t') {
137 // This is a multi-lines attribute.
138
139 // Remove the whitespace character and join with the current line.
140 line.remove(0);
141 next_line.push_str(line.trim_end());
142 self.number += 1;
143 } else {
144 // This is a new attribute so it need to be saved it for
145 // the next iteration.
146 self.saved = Some(line.trim().to_string());
147 break;
148 }
149 }
150
151 if next_line.is_empty() {
152 None
153 } else {
154 Some(Line::new(next_line, line_number))
155 }
156 }
157}
158
159impl<B: BufRead> Iterator for LineReader<B> {
160 type Item = Line;
161
162 fn next(&mut self) -> Option<Line> {
163 self.next_line()
164 }
165}