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
//! A reader of [LCOV records].
//!
//! The [`Reader`] structure reads LCOV records from arbitrary buffered reader.
//!
//! If you want to create a reader which reads am LCOV tracefile, you can use [`open_file`] function.
//!
//! [LCOV records]: ../enum.Record.html
//! [`Reader`]: struct.Reader.html
//! [`open_file`]: ../fn.open_file.html
use super::record::{ParseRecordError, Record};
use std::fs::File;
use std::io::{self, BufRead, BufReader, Lines};
use std::path::Path;

/// Reading an LCOV records from a buffered reader.
#[derive(Debug)]
pub struct Reader<B> {
    lines: Lines<B>,
    line: u32,
}

impl<B> Reader<B> {
    /// Creates a new `Reader`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use std::io::BufReader;
    /// use std::fs::File;
    /// use lcov::Reader;
    ///
    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
    /// let input = "\
    /// TN:test_name
    /// SF:/path/to/source/file.rs
    /// DA:1,2
    /// DA:3,0
    /// DA:5,6
    /// LF:3
    /// LH:2
    /// end_of_record
    /// ";
    ///
    /// let reader = Reader::new(input.as_bytes());
    /// # Ok(())
    /// # }
    /// # fn main() {}
    /// ```
    pub fn new(buf: B) -> Self
    where
        B: BufRead,
    {
        Reader {
            lines: buf.lines(),
            line: 0,
        }
    }
}

impl Reader<BufReader<File>> {
    /// Opens an LCOV tracefile.
    ///
    /// # Example
    ///
    /// ```rust
    /// use lcov::Reader;
    /// #
    /// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
    /// let reader = Reader::open_file("report.info")?;
    /// # Ok(())
    /// # }
    /// # fn main() {}
    /// ```
    pub fn open_file<P>(path: P) -> Result<Self, io::Error>
    where
        P: AsRef<Path>,
    {
        Ok(Reader::new(BufReader::new(File::open(path)?)))
    }
}

/// All possible errors that can occur when reading LCOV tracefile.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// An error indicating that I/O operation failed.
    ///
    /// This error occurs when the underlying reader returns an error.
    #[error("{}", _0)]
    Io(#[from] io::Error),

    /// An error indicating that record parsing failed.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use matches::assert_matches;
    /// # fn main() {
    /// use lcov::Reader;
    /// use lcov::reader::Error as ReadError;
    /// use lcov::record::ParseRecordError;
    /// let mut reader = Reader::new("FOO:1,2".as_bytes());
    /// assert_matches!(reader.next(), Some(Err(ReadError::ParseRecord(1, ParseRecordError::UnknownRecord))));
    /// # }
    /// ```
    #[error("invalid record syntax at line {}: {}", _0, _1)]
    ParseRecord(u32, #[source] ParseRecordError),
}

impl<B> Iterator for Reader<B>
where
    B: BufRead,
{
    type Item = Result<Record, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        self.lines.next().map(|line| {
            line.map_err(Error::Io).and_then(|line| {
                self.line += 1;
                line.parse().map_err(|e| Error::ParseRecord(self.line, e))
            })
        })
    }
}