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}