lcov_parser/
parser.rs

1// Copyright (c) 2015-2016 lcov-parser developers
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9//! Parser of LCOV report.
10
11use combine:: { parser, Parser, ParseError as CombinatorParseError, State };
12use record:: { LCOVRecord };
13use combinator:: { record, report };
14use std::fs:: { File };
15use std::result:: { Result };
16use std::io:: { Result as IOResult, Error as IOError, Read, BufRead, BufReader };
17use std::path:: { Path };
18use std::convert:: { From };
19use std::fmt:: { Display, Formatter, Result as FormatResult };
20use std::error::Error;
21
22pub type ParseResult<T> = Result<T, RecordParseError>;
23
24#[derive(PartialEq, Debug)]
25pub struct RecordParseError {
26    pub line: u32,
27    pub column: u32,
28    pub message: String
29}
30
31impl<'a> From<CombinatorParseError<State<&'a str>>> for RecordParseError {
32    fn from(error: CombinatorParseError<State<&'a str>>) -> Self {
33        let line = error.position.line;
34        let column = error.position.column;
35
36        RecordParseError {
37            line: line as u32,
38            column: column as u32,
39            message: format!("{}", error)
40        }
41    }
42}
43
44impl Display for RecordParseError {
45    fn fmt(&self, f: &mut Formatter) -> FormatResult {
46        write!(f, "{}", self.message)
47    }
48}
49
50impl Error for RecordParseError {
51    fn description(&self) -> &str {
52        &self.message[..]
53    }
54}
55
56#[derive(Debug)]
57pub enum ParseError {
58    IOError(IOError),
59    RecordParseError(RecordParseError)
60}
61
62impl From<IOError> for ParseError {
63    fn from(err: IOError) -> Self {
64        ParseError::IOError(err)
65    }
66}
67impl From<RecordParseError> for ParseError {
68    fn from(err: RecordParseError) -> Self {
69        ParseError::RecordParseError(err)
70    }
71}
72
73impl Error for ParseError {
74    fn description(&self) -> &str {
75        match self {
76            &ParseError::IOError(ref err) => err.description(),
77            &ParseError::RecordParseError(ref err) => err.description(),
78        }
79    }
80}
81
82impl Display for ParseError {
83    fn fmt(&self, formatter: &mut Formatter) -> FormatResult {
84        match self {
85            &ParseError::IOError(ref err) => err.fmt(formatter),
86            &ParseError::RecordParseError(ref err) => err.fmt(formatter),
87        }
88    }
89}
90
91/// Parse the record one line at a time
92///
93/// # Examples
94///
95/// ```
96/// use std::fs:: { File };
97/// use lcov_parser:: { LCOVRecord, LCOVParser };
98///
99/// let s = File::open("tests/fixtures/parser/report.lcov").unwrap();
100///
101/// let mut parser = LCOVParser::new(s);
102/// let mut records = vec![];
103///
104/// loop {
105///   let result = parser.next().unwrap();
106///   match result {
107///     Some(r) => { records.push(r) },
108///     None => { break; }
109///   }
110/// }
111///
112/// assert_eq!(records[0], LCOVRecord::TestName(Some("test".to_string())));
113/// ```
114pub struct LCOVParser<T> {
115    line: u32,
116    reader: BufReader<T>
117}
118
119impl<T: Read> LCOVParser<T> {
120    pub fn new(reader: T) -> Self {
121        LCOVParser {
122            line: 0,
123            reader: BufReader::new(reader)
124        }
125    }
126    pub fn parse(&mut self) -> Result<Vec<LCOVRecord>, ParseError> {
127        let mut records = vec![];
128        loop {
129            let result = self.next()?;
130            match result {
131                Some(record) => records.push(record),
132                None => { break; }
133            }
134        }
135        Ok(records)
136    }
137    pub fn next(&mut self) -> Result<Option<LCOVRecord>, ParseError> {
138        let mut line = String::new();
139        let size = self.reader.read_line(&mut line)?;
140        if size <= 0 {
141            return Ok(None);
142        }
143        self.line += 1;
144        let record = self.parse_record(line.as_str())?;
145        return Ok( Some(record) );
146    }
147    fn parse_record(&mut self, line: &str) -> Result<LCOVRecord, RecordParseError> {
148        match parse_record(line) {
149            Ok(record) => Ok(record),
150            Err(err) => {
151                Err(RecordParseError {
152                    line: self.line.clone(),
153                    column: err.column,
154                    message: err.message
155                })
156            }
157        }
158    }
159}
160
161pub trait FromFile<T> {
162    fn from_file<P: AsRef<Path>>(path: P) -> IOResult<LCOVParser<T>>;
163}
164
165/// Create a parser from file
166///
167/// # Examples
168///
169/// ```
170/// use lcov_parser:: { LCOVParser, LCOVRecord, FromFile };
171///
172/// let mut parser = LCOVParser::from_file("tests/fixtures/parser/report.lcov").unwrap();
173/// let result = parser.next().unwrap();
174///
175/// assert_eq!(result, Some(LCOVRecord::TestName(Some("test".to_string()))));
176/// ```
177impl FromFile<File> for LCOVParser<File> {
178    fn from_file<P: AsRef<Path>>(path: P) -> IOResult<LCOVParser<File>> {
179        let file = File::open(path)?;
180        Ok(LCOVParser::new(file))
181    }
182}
183
184/// Parse the record
185///
186/// # Examples
187///
188/// ```
189/// use lcov_parser:: { LCOVRecord, parse_record };
190///
191/// let result = parse_record("TN:test_name\n");
192///
193/// assert_eq!(result.unwrap(), LCOVRecord::TestName(Some("test_name".to_string())));
194/// ```
195
196#[inline]
197pub fn parse_record(input: &str) -> ParseResult<LCOVRecord> {
198    let (record, _) = parser(record).parse(State::new(input))?;
199    Ok(record)
200}
201
202/// Parse the LCOV report
203///
204/// # Examples
205///
206/// ```
207/// use lcov_parser:: { LCOVRecord, parse_report };
208///
209/// let result = parse_report("TN:test_name\nSF:/path/to/source.rs\n");
210/// let records = result.unwrap();
211///
212/// assert_eq!(records.get(0).unwrap(), &LCOVRecord::TestName(Some("test_name".to_string())));
213/// assert_eq!(records.get(1).unwrap(), &LCOVRecord::SourceFile("/path/to/source.rs".to_string()));
214/// ```
215
216#[inline]
217pub fn parse_report(input: &str) -> ParseResult<Vec<LCOVRecord>> {
218    let (records, _) = parser(report).parse(State::new(input))?;
219    Ok(records)
220}