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
use std::path::Path;

mod hours;
mod entry;

pub use hours::*;
pub use entry::*;

pub fn parse_file(path: impl AsRef<Path>) -> Result<Vec<Entry>, FileParseError> {
	let data = std::fs::read(path)?;
	parse_bytes(&data).map_err(|e| e.into())
}

pub fn parse_bytes(data: &[u8]) -> Result<Vec<Entry>, FileEntryParseError> {
	let mut result = Vec::new();

	for (i, line) in data.split(|c| *c == b'\n').enumerate() {
		let line = std::str::from_utf8(line).map_err(|_| FileEntryParseError::new(i, EntryParseError::InvalidUtf8))?;
		let line = line.trim();
		if line.is_empty() || line.starts_with('#') {
			continue;
		}

		let entry = Entry::from_str(line).map_err(|e| FileEntryParseError::new(i + 1, e))?;
		result.push(entry);
	}

	Ok(result)
}

#[derive(Debug)]
pub enum FileParseError {
	Io(std::io::Error),
	Entry(FileEntryParseError)
}

#[derive(Debug)]
pub struct FileEntryParseError {
	pub line: usize,
	pub error: EntryParseError,
}

impl FileEntryParseError {
	fn new(line: usize, error: EntryParseError) -> Self {
		Self { line, error }
	}
}

impl std::error::Error for FileParseError {}
impl std::error::Error for FileEntryParseError {}

impl From<std::io::Error> for FileParseError {
	fn from(other: std::io::Error) -> Self {
		Self::Io(other)
	}
}

impl From<FileEntryParseError> for FileParseError {
	fn from(other: FileEntryParseError) -> Self {
		Self::Entry(other)
	}
}

impl std::fmt::Display for FileParseError {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		match self {
			Self::Io(e) => write!(f, "{}", e),
			Self::Entry(e) => write!(f, "{}", e),
		}
	}
}

impl std::fmt::Display for FileEntryParseError {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "on line {}: {}", self.line, self.error)
	}
}