1#![deny(rustdoc::broken_intra_doc_links)]
58#![deny(rustdoc::invalid_codeblock_attributes)]
59#![deny(rustdoc::invalid_rust_codeblocks)]
60#![deny(rustdoc::private_intra_doc_links)]
61#![deny(unused_must_use)]
62#![doc(test(attr(allow(unused_extern_crates))))]
63#![doc(test(attr(deny(unused_must_use))))]
64#![doc(test(attr(warn(unused))))]
65#![warn(missing_docs)]
66
67use encoding_rs::CoderResult;
68use encoding_rs::Decoder;
69use encoding_rs::Encoder;
70use encoding_rs::EncoderResult;
71use encoding_rs::Encoding;
72use encoding_rs::WINDOWS_1252;
73use lazy_static::lazy_static;
74use regex::Regex;
75use std::collections::HashMap;
76use std::collections::VecDeque;
77use std::convert::From;
78use std::error::Error;
79use std::fmt;
80use std::fmt::Display;
81use std::fmt::Formatter;
82use std::io;
83use std::io::Read;
84use std::io::Write;
85use std::iter::Peekable;
86use std::ops::Deref;
87
88#[derive(Debug)]
92pub struct PropertiesError {
93 description: String,
94 cause: Option<Box<dyn Error + 'static + Send + Sync>>,
95 line_number: Option<usize>,
96}
97
98impl PropertiesError {
99 fn new<S: Into<String>>(
100 description: S,
101 cause: Option<Box<dyn Error + 'static + Send + Sync>>,
102 line_number: Option<usize>,
103 ) -> Self {
104 PropertiesError {
105 description: description.into(),
106 cause,
107 line_number,
108 }
109 }
110
111 pub fn line_number(&self) -> Option<usize> {
113 self.line_number
114 }
115}
116
117impl Error for PropertiesError {
118 fn description(&self) -> &str {
119 &self.description
120 }
121
122 #[allow(clippy::manual_map)]
124 fn source(&self) -> Option<&(dyn Error + 'static)> {
125 match self.cause {
126 Some(ref c) => Some(c.deref()),
127 None => None,
128 }
129 }
130}
131
132impl From<io::Error> for PropertiesError {
133 fn from(e: io::Error) -> Self {
134 PropertiesError::new("I/O error", Some(Box::new(e)), None)
135 }
136}
137
138impl Display for PropertiesError {
139 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
140 write!(f, "{}", &self.description)?;
141 match self.line_number {
142 Some(n) => write!(f, " (line_number = {})", n),
143 None => write!(f, " (line_number = unknown)"),
144 }
145 }
146}
147
148struct DecodeIter<R: Read> {
151 decoder: Decoder,
152 reader: R,
153 input_buffer: Vec<u8>,
154 output_buffer: String,
155 chars: VecDeque<char>,
156}
157
158impl<R: Read> DecodeIter<R> {
159 fn new(reader: R, encoding: &'static Encoding) -> Self {
160 Self {
161 decoder: encoding.new_decoder(),
162 reader,
163 input_buffer: Vec::with_capacity(64),
165 output_buffer: String::with_capacity(64),
167 chars: VecDeque::new(),
168 }
169 }
170}
171
172impl<R: Read> Iterator for DecodeIter<R> {
173 type Item = Result<char, io::Error>;
174
175 fn next(&mut self) -> Option<Self::Item> {
176 loop {
177 if let Some(c) = self.chars.pop_front() {
178 return Some(Ok(c));
179 }
180 let reader_eof = if self.input_buffer.is_empty() {
181 self.input_buffer.resize(self.input_buffer.capacity(), 0);
182 let bytes_read = match self.reader.read(&mut self.input_buffer) {
183 Ok(x) => x,
184 Err(e) => {
185 self.input_buffer.clear();
186 return Some(Err(e));
187 }
188 };
189 self.input_buffer.truncate(bytes_read);
190 bytes_read == 0
191 } else {
192 false
193 };
194 let (result, bytes_read, _) = self.decoder.decode_to_string(
195 &self.input_buffer,
196 &mut self.output_buffer,
197 reader_eof,
198 );
199 self.input_buffer.drain(..bytes_read);
200 match result {
201 CoderResult::InputEmpty => (),
202 CoderResult::OutputFull => {
203 self.output_buffer.reserve(self.output_buffer.capacity());
204 }
205 };
206 self.chars.extend(self.output_buffer.drain(..));
207 if self.chars.is_empty() && reader_eof {
208 return None;
209 }
210 }
211 }
212}
213
214#[derive(PartialEq, Eq, Debug)]
217struct NaturalLine(usize, String);
218
219struct NaturalLines<R: Read> {
221 chars: Peekable<DecodeIter<R>>,
222 eof: bool,
223 line_count: usize,
224}
225
226impl<R: Read> NaturalLines<R> {
227 fn new(reader: R, encoding: &'static Encoding) -> Self {
228 NaturalLines {
229 chars: DecodeIter::new(reader, encoding).peekable(),
230 eof: false,
231 line_count: 0,
232 }
233 }
234}
235
236const LF: char = '\n';
237const CR: char = '\r';
238
239impl<R: Read> Iterator for NaturalLines<R> {
240 type Item = Result<NaturalLine, PropertiesError>;
241
242 fn next(&mut self) -> Option<Self::Item> {
243 if self.eof {
244 return None;
245 }
246 let mut buf = String::new();
247 loop {
248 match self.chars.next() {
249 Some(Ok(CR)) => {
250 if let Some(&Ok(LF)) = self.chars.peek() {
251 self.chars.next();
252 }
253 self.line_count += 1;
254 return Some(Ok(NaturalLine(self.line_count, buf)));
255 }
256 Some(Ok(LF)) => {
257 self.line_count += 1;
258 return Some(Ok(NaturalLine(self.line_count, buf)));
259 }
260 Some(Ok(c)) => buf.push(c),
261 Some(Err(e)) => {
262 return Some(Err(PropertiesError::new(
263 "I/O error",
264 Some(Box::new(e)),
265 Some(self.line_count + 1),
266 )))
267 }
268 None => {
269 self.eof = true;
270 self.line_count += 1;
271 return Some(Ok(NaturalLine(self.line_count, buf)));
272 }
273 }
274 }
275 }
276}
277
278#[derive(PartialEq, Eq, Debug)]
281struct LogicalLine(usize, String);
282
283struct LogicalLines<I: Iterator<Item = Result<NaturalLine, PropertiesError>>> {
284 physical_lines: I,
285 eof: bool,
286}
287
288impl<I: Iterator<Item = Result<NaturalLine, PropertiesError>>> LogicalLines<I> {
289 fn new(physical_lines: I) -> Self {
290 LogicalLines {
291 physical_lines,
292 eof: false,
293 }
294 }
295}
296
297fn count_ending_backslashes(s: &str) -> usize {
298 let mut n = 0;
299 for c in s.chars() {
300 if c == '\\' {
301 n += 1;
302 } else {
303 n = 0;
304 }
305 }
306 n
307}
308
309impl<I: Iterator<Item = Result<NaturalLine, PropertiesError>>> Iterator for LogicalLines<I> {
310 type Item = Result<LogicalLine, PropertiesError>;
311
312 fn next(&mut self) -> Option<Self::Item> {
313 if self.eof {
314 return None;
315 }
316 let mut buf = String::new();
317 let mut first = true;
318 let mut line_number = 0;
319 loop {
320 match self.physical_lines.next() {
321 Some(Err(e)) => return Some(Err(e)),
322 Some(Ok(NaturalLine(line_no, line))) => {
323 if first {
324 line_number = line_no;
325 }
326 buf.push_str(if first { &line } else { line.trim_start() });
327 lazy_static! {
328 static ref COMMENT_RE: Regex = Regex::new("^[ \t\r\n\x0c]*[#!]").unwrap();
329 }
330 if first && COMMENT_RE.is_match(&line) {
331 assert!(line_number != 0);
335 return Some(Ok(LogicalLine(line_number, buf)));
336 }
337 if count_ending_backslashes(&line) % 2 == 1 {
338 buf.pop();
339 } else {
340 assert!(line_number != 0);
341 return Some(Ok(LogicalLine(line_number, buf)));
342 }
343 }
344 None => {
345 self.eof = true;
346 return None;
347 }
348 }
349 first = false;
350 }
351 }
352}
353
354#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
357enum ParsedLine<'a> {
358 Comment(&'a str),
359 KVPair(&'a str, &'a str),
360}
361
362#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash)]
364pub struct Line {
365 line_number: usize,
366 data: LineContent,
367}
368
369impl Line {
370 pub fn line_number(&self) -> usize {
372 self.line_number
373 }
374
375 pub fn content(&self) -> &LineContent {
377 &self.data
378 }
379
380 pub fn consume_content(self) -> LineContent {
382 self.data
383 }
384
385 fn mk_pair(line_number: usize, key: String, value: String) -> Line {
386 Line {
387 line_number,
388 data: LineContent::KVPair(key, value),
389 }
390 }
391
392 fn mk_comment(line_number: usize, text: String) -> Line {
393 Line {
394 line_number,
395 data: LineContent::Comment(text),
396 }
397 }
398}
399
400impl Display for Line {
401 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
402 write!(
403 f,
404 "Line {{line_number: {}, content: {}}}",
405 self.line_number, self.data
406 )
407 }
408}
409
410#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash)]
412pub enum LineContent {
413 Comment(String),
415
416 KVPair(String, String),
418}
419
420impl Display for LineContent {
421 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
422 match *self {
423 LineContent::Comment(ref s) => write!(f, "Comment({:?})", s),
424 LineContent::KVPair(ref k, ref v) => write!(f, "KVPair({:?}, {:?})", k, v),
425 }
426 }
427}
428
429impl From<Line> for LineContent {
430 fn from(line: Line) -> LineContent {
431 line.data
432 }
433}
434
435fn unescape(s: &str, line_number: usize) -> Result<String, PropertiesError> {
438 let mut buf = String::new();
439 let mut iter = s.chars();
440 loop {
441 match iter.next() {
442 None => break,
443 Some(c) => {
444 if c == '\\' {
445 match iter.next() {
446 Some(c) => {
447 match c {
448 't' => buf.push('\t'),
450 'n' => buf.push('\n'),
451 'f' => buf.push('\x0c'),
452 'r' => buf.push('\r'),
453 'u' => {
454 let mut tmp = String::new();
455 for _ in 0..4 {
456 match iter.next() {
457 Some(c) => tmp.push(c),
458 None => return Err(PropertiesError::new(
459 "Malformed \\uxxxx encoding: not enough digits.",
460 None,
461 Some(line_number),
462 )),
463 }
464 }
465 let val = match u16::from_str_radix(&tmp, 16) {
466 Ok(x) => x,
467 Err(e) => {
468 return Err(PropertiesError::new(
469 "Malformed \\uxxxx encoding: not hex.",
470 Some(Box::new(e)),
471 Some(line_number),
472 ))
473 }
474 };
475 match std::char::from_u32(val as u32) {
476 Some(c) => buf.push(c),
477 None => {
478 return Err(PropertiesError::new(
479 "Malformed \\uxxxx encoding: invalid character.",
480 None,
481 Some(line_number),
482 ))
483 }
484 }
485 }
486 _ => buf.push(c),
487 }
488 }
489 None => {
490 buf.push('\x00');
495 break;
496 }
497 }
498 } else {
499 buf.push(c);
500 }
501 }
502 }
503 }
504 Ok(buf)
505}
506
507lazy_static! {
508 static ref LINE_RE: Regex = Regex::new(r"(?x) # allow whitespace and comments
510 ^
511 [\x20\t\r\n\x0c]* # ignorable whitespace
512 (?:
513 [\x23!] # start of comment (# or !)
514 [\x20\t\r\n\x0c]* # ignorable whitespace
515 (.*?) # comment text
516 [\x20\t\r\n\x0c]* # ignorable whitespace
517 |
518 (
519 (?:[^\\:=\x20\t\r\n\x0c]|\\.)* # key
520 (?:\\$)? # end of line backslash, can't show up in real input because it's caught by LogicalLines
521 )
522 (?:
523 (?:
524 [\x20\t\r\n\x0c]*[:=][\x20\t\r\n\x0c]* # try matching an actual separator (: or =)
525 |
526 [\x20\t\r\n\x0c]+ # try matching whitespace only
527 )
528 (
529 (?:[^\\]|\\.)*? # value
530 (?:\\$)? # end of line backslash, can't show up in real input because it's caught by LogicalLines
531 )
532 )?
533 )
534 $
535 ").unwrap();
536}
537
538fn parse_line(line: &str) -> Option<ParsedLine> {
539 if let Some(c) = LINE_RE.captures(line) {
540 if let Some(comment_match) = c.get(1) {
541 Some(ParsedLine::Comment(comment_match.as_str()))
542 } else if let Some(key_match) = c.get(2) {
543 let key = key_match.as_str();
544 if let Some(value_match) = c.get(3) {
545 Some(ParsedLine::KVPair(key, value_match.as_str()))
546 } else if !key.is_empty() {
547 Some(ParsedLine::KVPair(key, ""))
548 } else {
549 None
550 }
551 } else {
552 panic!("Failed to get any groups out of the regular expression.")
553 }
554 } else {
555 panic!("Failed to match on {:?}", line);
557 }
558}
559
560pub struct PropertiesIter<R: Read> {
565 lines: LogicalLines<NaturalLines<R>>,
566}
567
568impl<R: Read> PropertiesIter<R> {
569 pub fn new(input: R) -> Self {
571 Self::new_with_encoding(input, WINDOWS_1252)
572 }
573
574 pub fn new_with_encoding(input: R, encoding: &'static Encoding) -> Self {
579 PropertiesIter {
580 lines: LogicalLines::new(NaturalLines::new(input, encoding)),
581 }
582 }
583
584 pub fn read_into<F: FnMut(String, String)>(&mut self, mut f: F) -> Result<(), PropertiesError> {
590 for line in self {
591 if let LineContent::KVPair(key, value) = line?.data {
592 f(key, value);
593 }
594 }
595 Ok(())
596 }
597
598 fn parsed_line_to_line(
599 &self,
600 parsed_line: ParsedLine<'_>,
601 line_number: usize,
602 ) -> Result<Line, PropertiesError> {
603 Ok(match parsed_line {
604 ParsedLine::Comment(c) => {
605 let comment = unescape(c, line_number)?;
606 Line::mk_comment(line_number, comment)
607 }
608 ParsedLine::KVPair(k, v) => {
609 let key = unescape(k, line_number)?;
610 let value = unescape(v, line_number)?;
611 Line::mk_pair(line_number, key, value)
612 }
613 })
614 }
615}
616
617impl<R: Read> Iterator for PropertiesIter<R> {
619 type Item = Result<Line, PropertiesError>;
620
621 fn next(&mut self) -> Option<Self::Item> {
625 loop {
626 match self.lines.next() {
627 Some(Ok(LogicalLine(line_no, line))) => {
628 if let Some(parsed_line) = parse_line(&line) {
629 return Some(self.parsed_line_to_line(parsed_line, line_no));
630 }
631 }
632 Some(Err(e)) => return Some(Err(e)),
633 None => return None,
634 }
635 }
636 }
637}
638
639#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Hash)]
643pub enum LineEnding {
644 CR,
646 LF,
648 #[allow(clippy::upper_case_acronyms)]
651 CRLF,
652}
653
654impl Display for LineEnding {
655 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
656 f.write_str(match *self {
657 LineEnding::CR => "LineEnding::CR",
658 LineEnding::LF => "LineEnding::LF",
659 LineEnding::CRLF => "LineEnding::CRLF",
660 })
661 }
662}
663
664struct EncodingWriter<W: Write> {
665 writer: W,
666 lines_written: usize,
667 encoder: Encoder,
668 buffer: Vec<u8>,
669}
670
671impl<W: Write> EncodingWriter<W> {
672 fn write(&mut self, mut data: &str) -> Result<(), PropertiesError> {
673 while !data.is_empty() {
674 let (result, bytes_read) = self.encoder.encode_from_utf8_to_vec_without_replacement(
675 data,
676 &mut self.buffer,
677 false,
678 );
679 data = &data[bytes_read..];
680 match result {
681 EncoderResult::InputEmpty => (),
682 EncoderResult::OutputFull => {
683 self.buffer.reserve(self.buffer.capacity() * 2);
685 }
686 EncoderResult::Unmappable(c) => {
687 let escaped = format!("\\u{:x}", c as isize);
688 let (result2, _) = self.encoder.encode_from_utf8_to_vec_without_replacement(
689 &escaped,
690 &mut self.buffer,
691 false,
692 );
693 match result2 {
694 EncoderResult::InputEmpty => (),
695 EncoderResult::OutputFull => {
696 self.buffer.reserve(self.buffer.capacity() * 2);
698 }
699 EncoderResult::Unmappable(_) => {
700 return Err(PropertiesError::new(
701 format!(
702 "Encoding error: unable to write UTF-8 escaping {:?} for {:?}",
703 escaped, c
704 ),
705 None,
706 Some(self.lines_written),
707 ))
708 }
709 }
710 }
711 }
712 }
713 self.flush_buffer()?;
714 Ok(())
715 }
716
717 fn flush_buffer(&mut self) -> Result<(), PropertiesError> {
718 self.writer.write_all(&self.buffer).map_err(|e| {
719 PropertiesError::new("I/O error", Some(Box::new(e)), Some(self.lines_written))
720 })?;
721 self.buffer.clear();
722 Ok(())
723 }
724
725 fn flush(&mut self) -> Result<(), PropertiesError> {
726 self.flush_buffer()?;
727 self.writer.flush()?;
728 Ok(())
729 }
730
731 fn finish(&mut self) -> Result<(), PropertiesError> {
732 let (result, _) =
733 self.encoder
734 .encode_from_utf8_to_vec_without_replacement("", &mut self.buffer, true);
735 match result {
736 EncoderResult::InputEmpty => (),
737 EncoderResult::OutputFull => {
738 return Err(PropertiesError::new(
739 "Encoding error: output full",
740 None,
741 Some(self.lines_written),
742 ))
743 }
744 EncoderResult::Unmappable(c) => {
745 return Err(PropertiesError::new(
746 format!("Encoding error: unmappable character {:?}", c),
747 None,
748 Some(self.lines_written),
749 ))
750 }
751 }
752 self.flush()?;
753 Ok(())
754 }
755}
756
757pub struct PropertiesWriter<W: Write> {
761 comment_prefix: String,
762 kv_separator: String,
763 line_ending: LineEnding,
764 writer: EncodingWriter<W>,
765}
766
767impl<W: Write> PropertiesWriter<W> {
768 pub fn new(writer: W) -> Self {
770 Self::new_with_encoding(writer, WINDOWS_1252)
771 }
772
773 pub fn new_with_encoding(writer: W, encoding: &'static Encoding) -> Self {
777 PropertiesWriter {
778 comment_prefix: "# ".to_string(),
779 kv_separator: "=".to_string(),
780 line_ending: LineEnding::LF,
781 writer: EncodingWriter {
782 writer,
783 lines_written: 0,
784 encoder: encoding.new_encoder(),
785 buffer: Vec::with_capacity(256),
787 },
788 }
789 }
790
791 fn write_eol(&mut self) -> Result<(), PropertiesError> {
792 self.writer.write(match self.line_ending {
793 LineEnding::CR => "\r",
794 LineEnding::LF => "\n",
795 LineEnding::CRLF => "\r\n",
796 })?;
797 Ok(())
798 }
799
800 pub fn write_comment(&mut self, comment: &str) -> Result<(), PropertiesError> {
802 self.writer.lines_written += 1;
803 self.writer.write(&self.comment_prefix)?;
804 self.writer.write(comment)?;
805 self.write_eol()?;
806 Ok(())
807 }
808
809 fn write_escaped(&mut self, s: &str) -> Result<(), PropertiesError> {
810 self.writer.lines_written += 1;
811 let mut escaped = String::new();
812 for c in s.chars() {
813 match c {
814 '\\' => escaped.push_str("\\\\"),
815 ' ' => escaped.push_str("\\ "),
816 '\t' => escaped.push_str("\\t"),
817 '\r' => escaped.push_str("\\r"),
818 '\n' => escaped.push_str("\\n"),
819 '\x0c' => escaped.push_str("\\f"),
820 ':' => escaped.push_str("\\:"),
821 '=' => escaped.push_str("\\="),
822 '!' => escaped.push_str("\\!"),
823 '#' => escaped.push_str("\\#"),
824 _ if c < ' ' => escaped.push_str(&format!("\\u{:x}", c as u16)),
825 _ => escaped.push(c), }
827 }
828 self.writer.write(&escaped)?;
829 Ok(())
830 }
831
832 pub fn write(&mut self, key: &str, value: &str) -> Result<(), PropertiesError> {
834 self.write_escaped(key)?;
835 self.writer.write(&self.kv_separator)?;
836 self.write_escaped(value)?;
837 self.write_eol()?;
838 Ok(())
839 }
840
841 pub fn flush(&mut self) -> Result<(), PropertiesError> {
843 self.writer.flush()?;
844 Ok(())
845 }
846
847 pub fn set_comment_prefix(&mut self, prefix: &str) -> Result<(), PropertiesError> {
852 lazy_static! {
853 static ref RE: Regex = Regex::new(r"^[ \t\x0c]*[#!][^\r\n]*$").unwrap();
854 }
855 if !RE.is_match(prefix) {
856 return Err(PropertiesError::new(
857 &format!("Bad comment prefix: {:?}", prefix),
858 None,
859 None,
860 ));
861 }
862 self.comment_prefix = prefix.to_string();
863 Ok(())
864 }
865
866 pub fn set_kv_separator(&mut self, separator: &str) -> Result<(), PropertiesError> {
871 lazy_static! {
872 static ref RE: Regex = Regex::new(r"^([ \t\x0c]*[:=][ \t\x0c]*|[ \t\x0c]+)$").unwrap();
873 }
874 if !RE.is_match(separator) {
875 return Err(PropertiesError::new(
876 &format!("Bad key/value separator: {:?}", separator),
877 None,
878 None,
879 ));
880 }
881 self.kv_separator = separator.to_string();
882 Ok(())
883 }
884
885 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
887 self.line_ending = line_ending;
888 }
889
890 pub fn finish(&mut self) -> Result<(), PropertiesError> {
892 self.writer.finish()?;
893 Ok(())
894 }
895}
896
897pub fn write<W: Write>(writer: W, map: &HashMap<String, String>) -> Result<(), PropertiesError> {
903 let mut writer = PropertiesWriter::new(writer);
904 for (k, v) in map {
905 writer.write(k, v)?;
906 }
907 writer.finish()?;
908 Ok(())
909}
910
911pub fn read<R: Read>(input: R) -> Result<HashMap<String, String>, PropertiesError> {
915 let mut p = PropertiesIter::new(input);
916 let mut map = HashMap::new();
917 p.read_into(|k, v| {
918 map.insert(k, v);
919 })?;
920 Ok(map)
921}
922
923#[cfg(test)]
926mod tests {
927 use super::Line;
928 use super::LineEnding;
929 use super::LogicalLine;
930 use super::LogicalLines;
931 use super::NaturalLine;
932 use super::NaturalLines;
933 use super::ParsedLine;
934 use super::PropertiesError;
935 use super::PropertiesIter;
936 use super::PropertiesWriter;
937 use encoding_rs::UTF_8;
938 use encoding_rs::WINDOWS_1252;
939 use std::io;
940 use std::io::ErrorKind;
941 use std::io::Read;
942
943 const LF: u8 = b'\n';
944 const CR: u8 = b'\r';
945 const SP: u8 = b' '; #[test]
948 fn natural_lines() {
949 let data = [
950 (vec![], vec![""]),
951 (vec![SP], vec![" "]),
952 (vec![SP, CR], vec![" ", ""]),
953 (vec![SP, LF], vec![" ", ""]),
954 (vec![SP, CR, LF], vec![" ", ""]),
955 (vec![SP, CR, SP], vec![" ", " "]),
956 (vec![SP, LF, SP], vec![" ", " "]),
957 (vec![SP, CR, LF, SP], vec![" ", " "]),
958 (vec![CR], vec!["", ""]),
959 (vec![LF], vec!["", ""]),
960 (vec![CR, LF], vec!["", ""]),
961 (vec![CR, SP], vec!["", " "]),
962 (vec![LF, SP], vec!["", " "]),
963 (vec![CR, LF, SP], vec!["", " "]),
964 ];
965 for &(ref bytes, ref lines) in &data {
966 let reader = &bytes as &[u8];
967 let mut iter = NaturalLines::new(reader, WINDOWS_1252);
968 let mut count = 1;
969 for line in lines {
970 match (line.to_string(), iter.next()) {
971 (ref e, Some(Ok(NaturalLine(a_ln, ref a)))) => {
972 if (count, e) != (a_ln, a) {
973 panic!("Failure while processing {:?}. Expected Some(Ok({:?})), but was {:?}", bytes, (count, e), (a_ln, a));
974 }
975 }
976 (e, a) => panic!(
977 "Failure while processing {:?}. Expected Some(Ok({:?})), but was {:?}",
978 bytes,
979 (count, e),
980 a
981 ),
982 }
983 count += 1;
984 }
985 match iter.next() {
986 None => (),
987 a => panic!(
988 "Failure while processing {:?}. Expected None, but was {:?}",
989 bytes, a
990 ),
991 }
992 }
993 }
994
995 #[test]
996 fn logical_lines() {
997 let data = [
998 (vec![], vec![]),
999 (vec!["foo"], vec!["foo"]),
1000 (vec!["foo", "bar"], vec!["foo", "bar"]),
1001 (vec!["foo\\", "bar"], vec!["foobar"]),
1002 (vec!["foo\\\\", "bar"], vec!["foo\\\\", "bar"]),
1003 (vec!["foo\\\\\\", "bar"], vec!["foo\\\\bar"]),
1004 (vec!["foo\\", " bar"], vec!["foobar"]),
1005 (vec!["#foo\\", " bar"], vec!["#foo\\", " bar"]),
1006 (vec!["foo\\", "# bar"], vec!["foo# bar"]),
1007 (vec!["\u{1F41E}\\", "\u{1F41E}"], vec!["\u{1F41E}\u{1F41E}"]),
1008 (
1009 vec!["\u{1F41E}\\", " \u{1F41E}"],
1010 vec!["\u{1F41E}\u{1F41E}"],
1011 ),
1012 ];
1013 for &(ref input_lines, ref lines) in &data {
1014 let mut count = 0;
1015 let mut iter = LogicalLines::new(input_lines.iter().map(|x| {
1016 count += 1;
1017 Ok(NaturalLine(count, x.to_string()))
1018 }));
1019 let mut e_ln = 0;
1020 for line in lines {
1021 e_ln += 1;
1022 match (line.to_string(), iter.next()) {
1023 (ref e, Some(Ok(LogicalLine(a_ln, ref a)))) => {
1024 if (e_ln, e) != (a_ln, a) {
1025 panic!("Failure while processing {:?}. Expected Some(Ok({:?})), but was {:?}", input_lines, (e_ln, e), (a_ln, a));
1026 }
1027 }
1028 (e, a) => panic!(
1029 "Failure while processing {:?}. Expected Some(Ok({:?})), but was {:?}",
1030 input_lines,
1031 (e_ln, e),
1032 a
1033 ),
1034 }
1035 }
1036 match iter.next() {
1037 None => (),
1038 a => panic!(
1039 "Failure while processing {:?}. Expected None, but was {:?}",
1040 input_lines, a
1041 ),
1042 }
1043 }
1044 }
1045
1046 #[test]
1047 fn count_ending_backslashes() {
1048 assert_eq!(0, super::count_ending_backslashes(""));
1049
1050 assert_eq!(0, super::count_ending_backslashes("x"));
1051 assert_eq!(1, super::count_ending_backslashes("\\"));
1052
1053 assert_eq!(0, super::count_ending_backslashes("xx"));
1054 assert_eq!(0, super::count_ending_backslashes("\\x"));
1055 assert_eq!(1, super::count_ending_backslashes("x\\"));
1056 assert_eq!(2, super::count_ending_backslashes("\\\\"));
1057
1058 assert_eq!(0, super::count_ending_backslashes("xxx"));
1059 assert_eq!(0, super::count_ending_backslashes("\\xx"));
1060 assert_eq!(0, super::count_ending_backslashes("x\\x"));
1061 assert_eq!(0, super::count_ending_backslashes("\\\\x"));
1062 assert_eq!(1, super::count_ending_backslashes("xx\\"));
1063 assert_eq!(1, super::count_ending_backslashes("\\x\\"));
1064 assert_eq!(2, super::count_ending_backslashes("x\\\\"));
1065 assert_eq!(3, super::count_ending_backslashes("\\\\\\"));
1066
1067 assert_eq!(0, super::count_ending_backslashes("x\u{1F41E}"));
1068 assert_eq!(0, super::count_ending_backslashes("\\\u{1F41E}"));
1069 assert_eq!(0, super::count_ending_backslashes("\u{1F41E}x"));
1070 assert_eq!(1, super::count_ending_backslashes("\u{1F41E}\\"));
1071 }
1072
1073 #[test]
1074 fn parse_line() {
1075 let data = [
1076 ("", None),
1077 (" ", None),
1078 ("\\", Some(ParsedLine::KVPair("\\", ""))),
1079 ("a=\\", Some(ParsedLine::KVPair("a", "\\"))),
1080 ("\\ ", Some(ParsedLine::KVPair("\\ ", ""))),
1081 ("# foo", Some(ParsedLine::Comment("foo"))),
1082 (" # foo", Some(ParsedLine::Comment("foo"))),
1083 ("a # foo", Some(ParsedLine::KVPair("a", "# foo"))),
1084 ("a", Some(ParsedLine::KVPair("a", ""))),
1085 ("a = b", Some(ParsedLine::KVPair("a", "b"))),
1086 ("a : b", Some(ParsedLine::KVPair("a", "b"))),
1087 ("a b", Some(ParsedLine::KVPair("a", "b"))),
1088 (" a = b ", Some(ParsedLine::KVPair("a", "b "))),
1089 (" a : b", Some(ParsedLine::KVPair("a", "b"))),
1090 (" a b", Some(ParsedLine::KVPair("a", "b"))),
1091 ("a:=b", Some(ParsedLine::KVPair("a", "=b"))),
1092 ("a=:b", Some(ParsedLine::KVPair("a", ":b"))),
1093 ("a b:c", Some(ParsedLine::KVPair("a", "b:c"))),
1094 (
1095 "a\\ \\:\\=b c",
1096 Some(ParsedLine::KVPair("a\\ \\:\\=b", "c")),
1097 ),
1098 (
1099 "a\\ \\:\\=b=c",
1100 Some(ParsedLine::KVPair("a\\ \\:\\=b", "c")),
1101 ),
1102 (
1103 "a\\\\ \\\\:\\\\=b c",
1104 Some(ParsedLine::KVPair("a\\\\", "\\\\:\\\\=b c")),
1105 ),
1106 ("\\ b", Some(ParsedLine::KVPair("\\ ", "b"))),
1107 ("=", Some(ParsedLine::KVPair("", ""))),
1108 ("=x", Some(ParsedLine::KVPair("", "x"))),
1109 ("x=", Some(ParsedLine::KVPair("x", ""))),
1110 ("\\=x", Some(ParsedLine::KVPair("\\=x", ""))),
1111 (
1112 "\u{1F41E}=\u{1F41E}",
1113 Some(ParsedLine::KVPair("\u{1F41E}", "\u{1F41E}")),
1114 ),
1115 ];
1116 for &(line, ref expected) in &data {
1117 let actual = super::parse_line(line);
1118 if expected != &actual {
1119 panic!(
1120 "Failed when splitting {:?}. Expected {:?} but got {:?}",
1121 line, expected, actual
1122 );
1123 }
1124 }
1125 }
1126
1127 #[test]
1128 fn unescape() {
1129 let data = [
1130 (r"", Some("")),
1131 (r"x", Some("x")),
1132 (r"\\", Some("\\")),
1133 (r"\#", Some("#")),
1134 (r"\!", Some("!")),
1135 (r"\\\n\r\t\f\u0001\b", Some("\\\n\r\t\x0c\u{0001}b")),
1136 (r"\", Some("\x00")),
1137 (r"\u", None),
1138 (r"\uasfd", None),
1139 ];
1140 for &(input, expected) in &data {
1141 let actual = &super::unescape(input, 1);
1142 let is_match = match (expected, actual) {
1143 (Some(e), &Ok(ref a)) => e == a,
1144 (None, &Err(_)) => true,
1145 _ => false,
1146 };
1147 if !is_match {
1148 panic!(
1149 "Failed when unescaping {:?}. Expected {:?} but got {:?}",
1150 input, expected, actual
1151 );
1152 }
1153 }
1154 }
1155
1156 #[test]
1157 fn properties_iter() {
1158 fn mk_comment(line_no: usize, text: &str) -> Line {
1159 Line::mk_comment(line_no, text.to_string())
1160 }
1161 fn mk_pair(line_no: usize, key: &str, value: &str) -> Line {
1162 Line::mk_pair(line_no, key.to_string(), value.to_string())
1163 }
1164 let data = vec![
1165 (
1166 WINDOWS_1252,
1167 vec![
1168 ("", vec![]),
1169 ("a=b", vec![mk_pair(1, "a", "b")]),
1170 ("a=\\#b", vec![mk_pair(1, "a", "#b")]),
1171 ("\\!a=b", vec![mk_pair(1, "!a", "b")]),
1172 ("a=b\nc=d\\\ne=f\ng=h\r#comment1\r\n#comment2\\\ni=j\\\n#comment3\n \n#comment4", vec![
1173 mk_pair(1, "a", "b"),
1174 mk_pair(2, "c", "de=f"),
1175 mk_pair(4, "g", "h"),
1176 mk_comment(5, "comment1"),
1177 mk_comment(6, "comment2\u{0}"),
1178 mk_pair(7, "i", "j#comment3"),
1179 mk_comment(10, "comment4"),
1180 ]),
1181 ("a = b\\\n c, d ", vec![mk_pair(1, "a", "bc, d ")]),
1182 ("x=\\\\\\\nty", vec![mk_pair(1, "x", "\\ty")]),
1183 ],
1184 ),
1185 (
1186 UTF_8,
1187 vec![(
1188 "a=日本語\nb=Français",
1189 vec![mk_pair(1, "a", "日本語"), mk_pair(2, "b", "Français")],
1190 )],
1191 ),
1192 ];
1193 for &(encoding, ref dataset) in &data {
1194 for &(input, ref lines) in dataset {
1195 let mut iter = PropertiesIter::new_with_encoding(input.as_bytes(), encoding);
1196 for line in lines {
1197 match (line, iter.next()) {
1198 (ref e, Some(Ok(ref a))) => {
1199 if e != &a {
1200 panic!("Failure while processing {:?}. Expected Some(Ok({:?})), but was {:?}", input, e, a);
1201 }
1202 }
1203 (e, a) => panic!(
1204 "Failure while processing {:?}. Expected Some(Ok({:?})), but was {:?}",
1205 input, e, a
1206 ),
1207 }
1208 }
1209 match iter.next() {
1210 None => (),
1211 a => panic!(
1212 "Failure while processing {:?}. Expected None, but was {:?}",
1213 input, a
1214 ),
1215 }
1216 }
1217 }
1218 }
1219
1220 #[test]
1221 fn properties_writer_kv() {
1222 let data = [
1223 ("", "", "=\n"),
1224 ("a", "b", "a=b\n"),
1225 (" :=", " :=", "\\ \\:\\==\\ \\:\\=\n"),
1226 ("!", "#", "\\!=\\#\n"),
1227 ("\u{1F41E}", "\u{1F41E}", "\\u1f41e=\\u1f41e\n"),
1228 ];
1229 for &(key, value, expected) in &data {
1230 let mut buf = Vec::new();
1231 {
1232 let mut writer = PropertiesWriter::new(&mut buf);
1233 writer.write(key, value).unwrap();
1234 writer.finish().unwrap();
1235 }
1236 let actual = WINDOWS_1252.decode(&buf).0;
1237 if expected != actual {
1238 panic!("Failure while processing key {:?} and value {:?}. Expected {:?}, but was {:?}", key, value, expected, actual);
1239 }
1240 }
1241 }
1242
1243 #[test]
1244 fn properties_writer_kv_custom_encoding() {
1245 let data = [
1246 ("", "", "=\n"),
1247 ("a", "b", "a=b\n"),
1248 (" :=", " :=", "\\ \\:\\==\\ \\:\\=\n"),
1249 ("!", "#", "\\!=\\#\n"),
1250 ("\u{1F41E}", "\u{1F41E}", "\u{1F41E}=\u{1F41E}\n"),
1251 ];
1252 for &(key, value, expected) in &data {
1253 let mut buf = Vec::new();
1254 {
1255 let mut writer = PropertiesWriter::new_with_encoding(&mut buf, UTF_8);
1256 writer.write(key, value).unwrap();
1257 writer.finish().unwrap();
1258 }
1259 let actual = UTF_8.decode(&buf).0;
1260 if expected != actual {
1261 panic!("Failure while processing key {:?} and value {:?}. Expected {:?}, but was {:?}", key, value, expected, actual);
1262 }
1263 }
1264 }
1265
1266 #[test]
1267 fn properties_writer_comment() {
1268 let data = [
1269 ("", "# \n"),
1270 ("a", "# a\n"),
1271 (" :=", "# :=\n"),
1272 ("\u{1F41E}", "# \\u1f41e\n"),
1273 ];
1274 for &(comment, expected) in &data {
1275 let mut buf = Vec::new();
1276 {
1277 let mut writer = PropertiesWriter::new(&mut buf);
1278 writer.write_comment(comment).unwrap();
1279 writer.finish().unwrap();
1280 }
1281 let actual = UTF_8.decode(&buf).0;
1282 if expected != actual {
1283 panic!(
1284 "Failure while processing {:?}. Expected {:?}, but was {:?}",
1285 comment, expected, actual
1286 );
1287 }
1288 }
1289 }
1290
1291 #[test]
1292 fn properties_writer_good_comment_prefix() {
1293 let prefixes = ["#", "!", " #", " !", "#x", "!x", "\x0c#"];
1294 let mut buf = Vec::new();
1295 for prefix in &prefixes {
1296 let mut writer = PropertiesWriter::new(&mut buf);
1297 writer.set_comment_prefix(prefix).unwrap();
1298 }
1299 }
1300
1301 #[test]
1302 fn properties_writer_bad_comment_prefix() {
1303 let prefixes = ["", " ", "x", "\n#", "#\n", "#\r"];
1304 let mut buf = Vec::new();
1305 for prefix in &prefixes {
1306 let mut writer = PropertiesWriter::new(&mut buf);
1307 match writer.set_comment_prefix(prefix) {
1308 Ok(_) => panic!("Unexpectedly succeded with prefix {:?}", prefix),
1309 Err(_) => (),
1310 }
1311 }
1312 }
1313
1314 #[test]
1315 fn properties_writer_custom_comment_prefix() {
1316 let data = [
1317 ("", " !\n"),
1318 ("a", " !a\n"),
1319 (" :=", " ! :=\n"),
1320 ("\u{1F41E}", " !\\u1f41e\n"),
1321 ];
1322 for &(comment, expected) in &data {
1323 let mut buf = Vec::new();
1324 {
1325 let mut writer = PropertiesWriter::new(&mut buf);
1326 writer.set_comment_prefix(" !").unwrap();
1327 writer.write_comment(comment).unwrap();
1328 writer.finish().unwrap();
1329 }
1330 let actual = WINDOWS_1252.decode(&buf).0;
1331 if expected != actual {
1332 panic!(
1333 "Failure while processing {:?}. Expected {:?}, but was {:?}",
1334 comment, expected, actual
1335 );
1336 }
1337 }
1338 }
1339
1340 #[test]
1341 fn properties_writer_good_kv_separator() {
1342 let separators = [":", "=", " ", " :", ": ", " =", "= ", "\x0c", "\t"];
1343 let mut buf = Vec::new();
1344 for separator in &separators {
1345 let mut writer = PropertiesWriter::new(&mut buf);
1346 writer.set_kv_separator(separator).unwrap();
1347 }
1348 }
1349
1350 #[test]
1351 fn properties_writer_bad_kv_separator() {
1352 let separators = ["", "x", ":=", "=:", "\n", "\r"];
1353 let mut buf = Vec::new();
1354 for separator in &separators {
1355 let mut writer = PropertiesWriter::new(&mut buf);
1356 match writer.set_kv_separator(separator) {
1357 Ok(_) => panic!("Unexpectedly succeded with separator {:?}", separator),
1358 Err(_) => (),
1359 }
1360 }
1361 }
1362
1363 #[test]
1364 fn properties_writer_custom_kv_separator() {
1365 let data = [
1366 (":", "x:y\n"),
1367 ("=", "x=y\n"),
1368 (" ", "x y\n"),
1369 (" :", "x :y\n"),
1370 (": ", "x: y\n"),
1371 (" =", "x =y\n"),
1372 ("= ", "x= y\n"),
1373 ("\x0c", "x\x0cy\n"),
1374 ("\t", "x\ty\n"),
1375 ];
1376 for &(separator, expected) in &data {
1377 let mut buf = Vec::new();
1378 {
1379 let mut writer = PropertiesWriter::new(&mut buf);
1380 writer.set_kv_separator(separator).unwrap();
1381 writer.write("x", "y").unwrap();
1382 writer.finish().unwrap();
1383 }
1384 let actual = WINDOWS_1252.decode(&buf).0;
1385 if expected != actual {
1386 panic!(
1387 "Failure while processing {:?}. Expected {:?}, but was {:?}",
1388 separator, expected, actual
1389 );
1390 }
1391 }
1392 }
1393
1394 #[test]
1395 fn properties_writer_custom_line_ending() {
1396 let data = [
1397 (LineEnding::CR, "# foo\rx=y\r"),
1398 (LineEnding::LF, "# foo\nx=y\n"),
1399 (LineEnding::CRLF, "# foo\r\nx=y\r\n"),
1400 ];
1401 for &(line_ending, expected) in &data {
1402 let mut buf = Vec::new();
1403 {
1404 let mut writer = PropertiesWriter::new(&mut buf);
1405 writer.set_line_ending(line_ending);
1406 writer.write_comment("foo").unwrap();
1407 writer.write("x", "y").unwrap();
1408 writer.finish().unwrap();
1409 }
1410 let actual = WINDOWS_1252.decode(&buf).0;
1411 if expected != actual {
1412 panic!(
1413 "Failure while processing {:?}. Expected {:?}, but was {:?}",
1414 line_ending, expected, actual
1415 );
1416 }
1417 }
1418 }
1419
1420 struct ErrorReader;
1421
1422 impl Read for ErrorReader {
1423 fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
1424 Err(io::Error::new(ErrorKind::InvalidData, "dummy error"))
1425 }
1426 }
1427
1428 #[test]
1429 fn properties_error_line_number() {
1430 let data = [
1431 ("", 1),
1432 ("\n", 2),
1433 ("\r", 2),
1434 ("\r\n", 2),
1435 ("\\uxxxx", 1),
1436 ("\n\\uxxxx", 2),
1437 ("a\\\nb\n\\uxxxx", 3),
1438 ];
1439 for &(input, line_number) in &data {
1440 let iter = PropertiesIter::new(input.as_bytes().chain(ErrorReader));
1441 let mut got_error = false;
1442 for line in iter {
1443 if let Err(e) = line {
1444 assert_eq!(e.line_number(), Some(line_number));
1445 got_error = true;
1446 break;
1447 }
1448 }
1449 assert!(got_error);
1450 }
1451 }
1452
1453 #[test]
1454 fn properties_error_display() {
1455 assert_eq!(
1456 format!("{}", PropertiesError::new("foo", None, None)),
1457 "foo (line_number = unknown)"
1458 );
1459 assert_eq!(
1460 format!("{}", PropertiesError::new("foo", None, Some(1))),
1461 "foo (line_number = 1)"
1462 );
1463 }
1464
1465 #[test]
1466 fn line_display() {
1467 assert_eq!(
1468 format!("{}", Line::mk_pair(1, "foo".to_string(), "bar".to_string())),
1469 "Line {line_number: 1, content: KVPair(\"foo\", \"bar\")}"
1470 );
1471 assert_eq!(
1472 format!("{}", Line::mk_comment(1, "baz".to_string())),
1473 "Line {line_number: 1, content: Comment(\"baz\")}"
1474 );
1475 }
1476}