|
@@ -1,15 +1,14 @@
|
|
|
-#[macro_use] extern crate failure;
|
|
|
+#[macro_use]
|
|
|
+extern crate failure;
|
|
|
|
|
|
pub mod contlines;
|
|
|
|
|
|
use contlines::ContinuationLines;
|
|
|
|
|
|
-
|
|
|
struct ParsingContext {
|
|
|
current_record_type: Option<String>,
|
|
|
}
|
|
|
|
|
|
-
|
|
|
#[derive(Eq, PartialEq, Debug)]
|
|
|
pub struct Record {
|
|
|
pub rec_type: Option<String>,
|
|
@@ -18,7 +17,8 @@ pub struct Record {
|
|
|
|
|
|
impl Record {
|
|
|
pub fn write<W>(&self, w: &mut W) -> std::io::Result<()>
|
|
|
- where W: std::io::Write
|
|
|
+ where
|
|
|
+ W: std::io::Write,
|
|
|
{
|
|
|
for &(ref name, ref value) in self.fields.iter() {
|
|
|
write!(w, "{}: {}\n", name, value)?;
|
|
@@ -30,8 +30,16 @@ impl Record {
|
|
|
pub fn size(&self) -> usize {
|
|
|
self.fields.len()
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
+ pub fn get(&self, key: &str) -> Option<&str> {
|
|
|
+ for (f, v) in self.fields.iter() {
|
|
|
+ if f == key {
|
|
|
+ return Some(v);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ None
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
#[derive(Eq, PartialEq, Debug)]
|
|
|
pub struct Recfile {
|
|
@@ -40,7 +48,8 @@ pub struct Recfile {
|
|
|
|
|
|
impl Recfile {
|
|
|
pub fn write<W>(&self, w: &mut W) -> std::io::Result<()>
|
|
|
- where W: std::io::Write
|
|
|
+ where
|
|
|
+ W: std::io::Write,
|
|
|
{
|
|
|
for r in self.records.iter() {
|
|
|
r.write(w)?;
|
|
@@ -55,30 +64,28 @@ impl Recfile {
|
|
|
None => false,
|
|
|
});
|
|
|
}
|
|
|
+
|
|
|
+ pub fn iter(&self) -> impl Iterator<Item=&Record> {
|
|
|
+ self.records.iter()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-#[derive(Debug, Fail)]
|
|
|
+#[derive(Debug, Fail, PartialEq, Eq)]
|
|
|
pub enum RecError {
|
|
|
#[fail(display = "Error parsing records: {}", message)]
|
|
|
- GenericError {
|
|
|
- message: String,
|
|
|
- },
|
|
|
+ GenericError { message: String },
|
|
|
|
|
|
#[fail(display = "Found cont line in nonsensical place: {}", ln)]
|
|
|
- BadContLine {
|
|
|
- ln: String,
|
|
|
- },
|
|
|
+ BadContLine { ln: String },
|
|
|
|
|
|
#[fail(display = "Invalid line: {}", ln)]
|
|
|
- InvalidLine {
|
|
|
- ln: String,
|
|
|
- },
|
|
|
+ InvalidLine { ln: String },
|
|
|
}
|
|
|
|
|
|
-
|
|
|
impl Recfile {
|
|
|
pub fn parse<I>(i: I) -> Result<Recfile, RecError>
|
|
|
- where I: std::io::BufRead
|
|
|
+ where
|
|
|
+ I: std::io::BufRead,
|
|
|
{
|
|
|
let mut iter = ContinuationLines::new(i.lines());
|
|
|
let mut current = Record {
|
|
@@ -91,7 +98,7 @@ impl Recfile {
|
|
|
};
|
|
|
|
|
|
while let Some(Ok(ln)) = iter.next() {
|
|
|
- let ln = ln.trim_left_matches(' ');
|
|
|
+ let ln = ln.trim_start_matches(' ');
|
|
|
|
|
|
if ln.starts_with('#') {
|
|
|
// skip comment lines
|
|
@@ -106,22 +113,21 @@ impl Recfile {
|
|
|
} else if ln.starts_with('+') {
|
|
|
if let Some(val) = current.fields.last_mut() {
|
|
|
val.1.push_str("\n");
|
|
|
- val.1.push_str(
|
|
|
- if ln[1..].starts_with(' ') {
|
|
|
- &ln[2..]
|
|
|
- } else {
|
|
|
- &ln[1..]
|
|
|
- });
|
|
|
+ val.1.push_str(if ln[1..].starts_with(' ') {
|
|
|
+ &ln[2..]
|
|
|
+ } else {
|
|
|
+ &ln[1..]
|
|
|
+ });
|
|
|
} else {
|
|
|
- return Err(RecError::BadContLine{ ln: ln.to_owned() });
|
|
|
+ return Err(RecError::BadContLine { ln: ln.to_owned() });
|
|
|
}
|
|
|
} else if let Some(pos) = ln.find(':') {
|
|
|
let (key, val) = ln.split_at(pos);
|
|
|
- current.fields.push((
|
|
|
- key.to_owned(),
|
|
|
- val[1..].trim_left().to_owned()));
|
|
|
+ current
|
|
|
+ .fields
|
|
|
+ .push((key.to_owned(), val[1..].trim_start().to_owned()));
|
|
|
if key == "%rec" {
|
|
|
- ctx.current_record_type = Some(val[1..].trim_left().to_owned());
|
|
|
+ ctx.current_record_type = Some(val[1..].trim_start().to_owned());
|
|
|
}
|
|
|
} else {
|
|
|
return Err(RecError::InvalidLine { ln: ln.to_owned() });
|
|
@@ -134,25 +140,26 @@ impl Recfile {
|
|
|
|
|
|
Ok(Recfile { records: buf })
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
- use ::{Recfile,Record};
|
|
|
+ use {Recfile, RecError, Record};
|
|
|
|
|
|
fn test_parse(input: &[u8], expected: Vec<Vec<(&str, &str)>>) {
|
|
|
let file = Recfile {
|
|
|
- records: expected.iter().map( |v| {
|
|
|
- Record {
|
|
|
+ records: expected
|
|
|
+ .iter()
|
|
|
+ .map(|v| Record {
|
|
|
rec_type: None,
|
|
|
- fields: v.iter().map( |&(k, v)| {
|
|
|
- (k.to_owned(), v.to_owned())
|
|
|
- }).collect(),
|
|
|
- }
|
|
|
- }).collect(),
|
|
|
+ fields: v
|
|
|
+ .iter()
|
|
|
+ .map(|&(k, v)| (k.to_owned(), v.to_owned()))
|
|
|
+ .collect(),
|
|
|
+ })
|
|
|
+ .collect(),
|
|
|
};
|
|
|
- assert_eq!(Recfile::parse(input), Ok(file));
|
|
|
+ assert_eq!(Recfile::parse(input).unwrap(), file);
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
@@ -167,17 +174,14 @@ mod tests {
|
|
|
|
|
|
#[test]
|
|
|
fn one_section() {
|
|
|
- test_parse(b"hello: yes\n", vec![ vec![ ("hello", "yes") ] ]);
|
|
|
+ test_parse(b"hello: yes\n", vec![vec![("hello", "yes")]]);
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
fn two_sections() {
|
|
|
test_parse(
|
|
|
b"hello: yes\n\ngoodbye: no\n",
|
|
|
- vec![
|
|
|
- vec![ ("hello", "yes") ],
|
|
|
- vec![ ("goodbye", "no") ],
|
|
|
- ],
|
|
|
+ vec![vec![("hello", "yes")], vec![("goodbye", "no")]],
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -185,9 +189,7 @@ mod tests {
|
|
|
fn continuation_with_space() {
|
|
|
test_parse(
|
|
|
b"hello: yes\n+ but also no\n",
|
|
|
- vec![
|
|
|
- vec![ ("hello", "yes\nbut also no") ],
|
|
|
- ],
|
|
|
+ vec![vec![("hello", "yes\nbut also no")]],
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -195,9 +197,7 @@ mod tests {
|
|
|
fn continuation_without_space() {
|
|
|
test_parse(
|
|
|
b"hello: yes\n+but also no\n",
|
|
|
- vec![
|
|
|
- vec![ ("hello", "yes\nbut also no") ],
|
|
|
- ],
|
|
|
+ vec![vec![("hello", "yes\nbut also no")]],
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -205,10 +205,7 @@ mod tests {
|
|
|
fn continuation_with_two_spaces() {
|
|
|
test_parse(
|
|
|
b"hello: yes\n+ but also no\n",
|
|
|
- vec![
|
|
|
- vec![ ("hello", "yes\n but also no") ],
|
|
|
- ],
|
|
|
+ vec![vec![("hello", "yes\n but also no")]],
|
|
|
);
|
|
|
}
|
|
|
-
|
|
|
}
|