lib.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #[macro_use]
  2. extern crate failure;
  3. pub mod contlines;
  4. use contlines::ContinuationLines;
  5. struct ParsingContext {
  6. current_record_type: Option<String>,
  7. }
  8. #[derive(Eq, PartialEq, Debug)]
  9. pub struct Record {
  10. pub rec_type: Option<String>,
  11. pub fields: Vec<(String, String)>,
  12. }
  13. impl Record {
  14. pub fn write<W>(&self, w: &mut W) -> std::io::Result<()>
  15. where
  16. W: std::io::Write,
  17. {
  18. for &(ref name, ref value) in self.fields.iter() {
  19. write!(w, "{}: {}\n", name, value)?;
  20. }
  21. write!(w, "\n")
  22. }
  23. pub fn size(&self) -> usize {
  24. self.fields.len()
  25. }
  26. }
  27. #[derive(Eq, PartialEq, Debug)]
  28. pub struct Recfile {
  29. pub records: Vec<Record>,
  30. }
  31. impl Recfile {
  32. pub fn write<W>(&self, w: &mut W) -> std::io::Result<()>
  33. where
  34. W: std::io::Write,
  35. {
  36. for r in self.records.iter() {
  37. r.write(w)?;
  38. }
  39. Ok(())
  40. }
  41. pub fn filter_by_type(&mut self, type_name: &str) {
  42. self.records.retain(|r| match r.rec_type {
  43. Some(ref t) => t == type_name,
  44. None => false,
  45. });
  46. }
  47. }
  48. #[derive(Debug, Fail)]
  49. pub enum RecError {
  50. #[fail(display = "Error parsing records: {}", message)]
  51. GenericError { message: String },
  52. #[fail(display = "Found cont line in nonsensical place: {}", ln)]
  53. BadContLine { ln: String },
  54. #[fail(display = "Invalid line: {}", ln)]
  55. InvalidLine { ln: String },
  56. }
  57. impl Recfile {
  58. pub fn parse<I>(i: I) -> Result<Recfile, RecError>
  59. where
  60. I: std::io::BufRead,
  61. {
  62. let mut iter = ContinuationLines::new(i.lines());
  63. let mut current = Record {
  64. fields: vec![],
  65. rec_type: None,
  66. };
  67. let mut buf = vec![];
  68. let mut ctx = ParsingContext {
  69. current_record_type: None,
  70. };
  71. while let Some(Ok(ln)) = iter.next() {
  72. let ln = ln.trim_start_matches(' ');
  73. if ln.starts_with('#') {
  74. // skip comment lines
  75. } else if ln.is_empty() {
  76. if !current.fields.is_empty() {
  77. buf.push(current);
  78. current = Record {
  79. rec_type: ctx.current_record_type.clone(),
  80. fields: vec![],
  81. };
  82. }
  83. } else if ln.starts_with('+') {
  84. if let Some(val) = current.fields.last_mut() {
  85. val.1.push_str("\n");
  86. val.1.push_str(if ln[1..].starts_with(' ') {
  87. &ln[2..]
  88. } else {
  89. &ln[1..]
  90. });
  91. } else {
  92. return Err(RecError::BadContLine { ln: ln.to_owned() });
  93. }
  94. } else if let Some(pos) = ln.find(':') {
  95. let (key, val) = ln.split_at(pos);
  96. current
  97. .fields
  98. .push((key.to_owned(), val[1..].trim_start().to_owned()));
  99. if key == "%rec" {
  100. ctx.current_record_type = Some(val[1..].trim_start().to_owned());
  101. }
  102. } else {
  103. return Err(RecError::InvalidLine { ln: ln.to_owned() });
  104. }
  105. }
  106. if !current.fields.is_empty() {
  107. buf.push(current);
  108. }
  109. Ok(Recfile { records: buf })
  110. }
  111. }
  112. #[cfg(test)]
  113. mod tests {
  114. use {Recfile, Record};
  115. fn test_parse(input: &[u8], expected: Vec<Vec<(&str, &str)>>) {
  116. let file = Recfile {
  117. records: expected
  118. .iter()
  119. .map(|v| Record {
  120. rec_type: None,
  121. fields: v
  122. .iter()
  123. .map(|&(k, v)| (k.to_owned(), v.to_owned()))
  124. .collect(),
  125. })
  126. .collect(),
  127. };
  128. assert_eq!(Recfile::parse(input), Ok(file));
  129. }
  130. #[test]
  131. fn empty_file() {
  132. test_parse(b"\n", vec![]);
  133. }
  134. #[test]
  135. fn only_comments() {
  136. test_parse(b"# an empty file\n", vec![]);
  137. }
  138. #[test]
  139. fn one_section() {
  140. test_parse(b"hello: yes\n", vec![vec![("hello", "yes")]]);
  141. }
  142. #[test]
  143. fn two_sections() {
  144. test_parse(
  145. b"hello: yes\n\ngoodbye: no\n",
  146. vec![vec![("hello", "yes")], vec![("goodbye", "no")]],
  147. );
  148. }
  149. #[test]
  150. fn continuation_with_space() {
  151. test_parse(
  152. b"hello: yes\n+ but also no\n",
  153. vec![vec![("hello", "yes\nbut also no")]],
  154. );
  155. }
  156. #[test]
  157. fn continuation_without_space() {
  158. test_parse(
  159. b"hello: yes\n+but also no\n",
  160. vec![vec![("hello", "yes\nbut also no")]],
  161. );
  162. }
  163. #[test]
  164. fn continuation_with_two_spaces() {
  165. test_parse(
  166. b"hello: yes\n+ but also no\n",
  167. vec![vec![("hello", "yes\n but also no")]],
  168. );
  169. }
  170. }