lib.rs 4.7 KB

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