1pub mod util;
20
21use memchr::memmem;
22#[cfg(feature = "parallel")]
23use rayon::prelude::*;
24
25use std::borrow::Cow;
26use std::io::{self, Read, Write};
27
28pub const VERSION: &str = env!("CARGO_PKG_VERSION");
29
30const PARALLEL_THRESHOLD: usize = 64 * 1024;
32
33pub fn decode(s: &str) -> Vec<Vec<String>> {
35 decode_bytes(s.as_bytes())
36 .into_iter()
37 .map(|row| {
38 row.into_iter()
39 .map(|cell| {
40 unsafe { String::from_utf8_unchecked(cell.into_owned()) }
44 })
45 .collect()
46 })
47 .collect()
48}
49
50pub fn decode_bytes<'a>(input: &'a [u8]) -> Vec<Vec<Cow<'a, [u8]>>> {
56 if input.is_empty() {
57 return Vec::new();
58 }
59
60 #[cfg(feature = "parallel")]
61 if input.len() >= PARALLEL_THRESHOLD {
62 return decode_bytes_parallel(input);
63 }
64
65 decode_bytes_sequential(input)
66}
67
68fn decode_bytes_sequential<'a>(input: &'a [u8]) -> Vec<Vec<Cow<'a, [u8]>>> {
70 let mut data = Vec::new();
71 let mut row: Vec<Cow<'a, [u8]>> = Vec::new();
72 let mut start = 0;
73
74 for (pos, &b) in input.iter().enumerate() {
75 if b == b'\n' {
76 if pos > start {
77 row.push(unescape_bytes(&input[start..pos]));
78 } else {
79 data.push(row);
80 row = Vec::new();
81 }
82 start = pos + 1;
83 }
84 }
85
86 if start < input.len() {
87 row.push(unescape_bytes(&input[start..]));
88 }
89
90 if !row.is_empty() {
91 data.push(row);
92 }
93
94 data
95}
96
97#[cfg(feature = "parallel")]
103fn decode_bytes_parallel<'a>(input: &'a [u8]) -> Vec<Vec<Cow<'a, [u8]>>> {
104 let num_threads = rayon::current_num_threads();
105 let chunk_size = input.len() / num_threads;
106
107 if chunk_size == 0 {
108 return decode_bytes_sequential(input);
109 }
110
111 let finder = memmem::Finder::new(b"\n\n");
114 let mut splits = Vec::with_capacity(num_threads + 1);
115 splits.push(0usize);
116
117 for i in 1..num_threads {
118 let nominal = i * chunk_size;
119 if let Some(offset) = finder.find(&input[nominal..]) {
120 let split = nominal + offset + 2; if split < input.len() {
122 splits.push(split);
123 }
124 }
125 }
126 splits.push(input.len());
127 splits.dedup();
128
129 if splits.len() <= 2 {
130 return decode_bytes_sequential(input);
131 }
132
133 let chunks: Vec<&[u8]> = splits.windows(2).map(|w| &input[w[0]..w[1]]).collect();
136
137 let chunk_results: Vec<Vec<Vec<Cow<'a, [u8]>>>> = chunks
138 .par_iter()
139 .map(|chunk| decode_bytes_sequential(chunk))
140 .collect();
141
142 let total_rows: usize = chunk_results.iter().map(|r| r.len()).sum();
143 let mut result = Vec::with_capacity(total_rows);
144 for chunk_rows in chunk_results {
145 result.extend(chunk_rows);
146 }
147 result
148}
149
150pub fn unescape(s: &str) -> Cow<'_, str> {
154 match unescape_bytes(s.as_bytes()) {
155 Cow::Borrowed(b) => Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(b) }),
158 Cow::Owned(v) => Cow::Owned(unsafe { String::from_utf8_unchecked(v) }),
160 }
161}
162
163pub fn unescape_bytes(s: &[u8]) -> Cow<'_, [u8]> {
171 if s == b"\\" {
172 return Cow::Owned(Vec::new());
173 }
174
175 if !s.contains(&b'\\') {
176 return Cow::Borrowed(s);
177 }
178
179 let mut out = Vec::with_capacity(s.len());
180 let mut escaped = false;
181
182 for &b in s {
183 if escaped {
184 match b {
185 b'n' => out.push(b'\n'),
186 b'\\' => out.push(b'\\'),
187 _ => {
188 out.push(b'\\');
189 out.push(b);
190 }
191 }
192 escaped = false;
193 } else if b == b'\\' {
194 escaped = true;
195 } else {
196 out.push(b);
197 }
198 }
199
200 Cow::Owned(out)
201}
202
203pub fn escape(s: &str) -> Cow<'_, str> {
207 match escape_bytes(s.as_bytes()) {
208 Cow::Borrowed(b) => Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(b) }),
211 Cow::Owned(v) => Cow::Owned(unsafe { String::from_utf8_unchecked(v) }),
213 }
214}
215
216pub fn escape_bytes(s: &[u8]) -> Cow<'_, [u8]> {
223 if s.is_empty() {
224 return Cow::Owned(b"\\".to_vec());
225 }
226
227 if s.contains(&b'\n') || s.contains(&b'\\') {
228 let mut out = Vec::with_capacity(s.len() + s.len() / 4);
229 for &b in s {
230 match b {
231 b'\\' => {
232 out.push(b'\\');
233 out.push(b'\\');
234 }
235 b'\n' => {
236 out.push(b'\\');
237 out.push(b'n');
238 }
239 _ => out.push(b),
240 }
241 }
242 Cow::Owned(out)
243 } else {
244 Cow::Borrowed(s)
245 }
246}
247
248fn build_col_map(columns: &[usize]) -> (Vec<usize>, usize) {
257 let max_col = columns.iter().copied().max().unwrap_or(0);
258 let mut col_map = vec![usize::MAX; max_col + 1];
259 for (proj_idx, &orig_col) in columns.iter().enumerate() {
260 col_map[orig_col] = proj_idx;
261 }
262 (col_map, max_col)
263}
264
265pub fn decode_bytes_projected<'a>(input: &'a [u8], columns: &[usize]) -> Vec<Vec<Cow<'a, [u8]>>> {
273 if input.is_empty() || columns.is_empty() {
274 return Vec::new();
275 }
276
277 #[cfg(feature = "parallel")]
278 if input.len() >= PARALLEL_THRESHOLD {
279 return decode_projected_parallel(input, columns);
280 }
281
282 decode_projected_sequential(input, columns)
283}
284
285fn decode_projected_sequential<'a>(input: &'a [u8], columns: &[usize]) -> Vec<Vec<Cow<'a, [u8]>>> {
287 let (col_map, max_col) = build_col_map(columns);
288 let stride = columns.len();
289 let mut data: Vec<Vec<Cow<'a, [u8]>>> = Vec::new();
290 let mut row: Vec<Cow<'a, [u8]>> = vec![Cow::Borrowed(b""); stride];
291 let mut col_idx: usize = 0;
292 let mut start = 0;
293 let mut row_has_cells = false;
294
295 for (pos, &b) in input.iter().enumerate() {
296 if b == b'\n' {
297 if pos > start {
298 if col_idx <= max_col {
299 if let Some(&proj_idx) = col_map.get(col_idx) {
300 if proj_idx != usize::MAX {
301 row[proj_idx] = unescape_bytes(&input[start..pos]);
302 }
303 }
304 }
305 col_idx += 1;
306 row_has_cells = true;
307 } else {
308 if row_has_cells || !data.is_empty() || col_idx == 0 {
309 data.push(row);
310 row = vec![Cow::Borrowed(b""); stride];
311 }
312 col_idx = 0;
313 row_has_cells = false;
314 }
315 start = pos + 1;
316 }
317 }
318
319 if start < input.len() {
320 if col_idx <= max_col {
321 if let Some(&proj_idx) = col_map.get(col_idx) {
322 if proj_idx != usize::MAX {
323 row[proj_idx] = unescape_bytes(&input[start..]);
324 }
325 }
326 }
327 row_has_cells = true;
328 }
329
330 if row_has_cells {
331 data.push(row);
332 }
333
334 data
335}
336
337#[cfg(feature = "parallel")]
339fn decode_projected_parallel<'a>(input: &'a [u8], columns: &[usize]) -> Vec<Vec<Cow<'a, [u8]>>> {
340 let num_threads = rayon::current_num_threads();
341 let chunk_size = input.len() / num_threads;
342
343 if chunk_size == 0 {
344 return decode_projected_sequential(input, columns);
345 }
346
347 let finder = memmem::Finder::new(b"\n\n");
348 let mut splits = Vec::with_capacity(num_threads + 1);
349 splits.push(0usize);
350
351 for i in 1..num_threads {
352 let nominal = i * chunk_size;
353 if let Some(offset) = finder.find(&input[nominal..]) {
354 let split = nominal + offset + 2;
355 if split < input.len() {
356 splits.push(split);
357 }
358 }
359 }
360 splits.push(input.len());
361 splits.dedup();
362
363 if splits.len() <= 2 {
364 return decode_projected_sequential(input, columns);
365 }
366
367 let chunks: Vec<&[u8]> = splits.windows(2).map(|w| &input[w[0]..w[1]]).collect();
368
369 let chunk_results: Vec<Vec<Vec<Cow<'a, [u8]>>>> = chunks
370 .par_iter()
371 .map(|chunk| decode_projected_sequential(chunk, columns))
372 .collect();
373
374 let total_rows: usize = chunk_results.iter().map(|r| r.len()).sum();
375 let mut result = Vec::with_capacity(total_rows);
376 for chunk_rows in chunk_results {
377 result.extend(chunk_rows);
378 }
379 result
380}
381
382pub fn encode(data: &[Vec<String>]) -> String {
384 let mut result = Vec::new();
385
386 for row in data {
387 for cell in row {
388 result.extend_from_slice(&escape_bytes(cell.as_bytes()));
389 result.push(b'\n');
390 }
391 result.push(b'\n');
392 }
393
394 String::from_utf8(result).unwrap()
396}
397
398pub fn encode_bytes(data: &[Vec<Vec<u8>>]) -> Vec<u8> {
400 let mut result = Vec::new();
401
402 for row in data {
403 for cell in row {
404 result.extend_from_slice(&escape_bytes(cell));
405 result.push(b'\n');
406 }
407 result.push(b'\n');
408 }
409
410 result
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
415pub struct Warning {
416 pub kind: WarningKind,
417 pub pos: usize,
418 pub line: usize,
419 pub col: usize,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub enum WarningKind {
424 UnknownEscape(u8),
426 DanglingBackslash,
428 NoTerminalLf,
430}
431
432pub fn check(input: &[u8]) -> Vec<Warning> {
437 if input.is_empty() {
438 return Vec::new();
439 }
440
441 let mut warnings = Vec::new();
442 let len = input.len();
443 let mut line: usize = 1;
444 let mut line_start: usize = 0;
445 let mut escaped = false;
446
447 for (i, &b) in input.iter().enumerate() {
448 if escaped {
449 match b {
450 b'n' | b'\\' => {}
451 b'\n' if i - 1 != line_start => warnings.push(Warning {
452 kind: WarningKind::DanglingBackslash,
453 pos: i - 1,
454 line,
455 col: i - line_start,
456 }),
457 b'\n' => {}
458 _ => warnings.push(Warning {
459 kind: WarningKind::UnknownEscape(b),
460 pos: i - 1,
461 line,
462 col: i - line_start,
463 }),
464 }
465 escaped = false;
466 } else if b == b'\\' {
467 escaped = true;
468 }
469 if b == b'\n' {
470 line += 1;
471 line_start = i + 1;
472 }
473 }
474
475 if escaped {
476 warnings.push(Warning {
477 kind: WarningKind::DanglingBackslash,
478 pos: len - 1,
479 line,
480 col: len - line_start,
481 });
482 }
483
484 if line_start != len {
485 warnings.push(Warning {
486 kind: WarningKind::NoTerminalLf,
487 pos: len,
488 line,
489 col: len - line_start + 1,
490 });
491 }
492
493 warnings
494}
495
496pub struct Reader<R> {
503 inner: io::BufReader<R>,
504 line_buf: Vec<u8>,
505 row: Vec<Vec<u8>>,
506}
507
508impl<R: io::Read> Reader<R> {
509 pub fn new(reader: R) -> Self {
510 Self::from_buf_reader(io::BufReader::new(reader))
511 }
512
513 pub fn from_buf_reader(reader: io::BufReader<R>) -> Self {
514 Reader { inner: reader, line_buf: Vec::new(), row: Vec::new() }
515 }
516
517 pub fn next_row(&mut self) -> io::Result<Option<Vec<Vec<u8>>>> {
518 let mut byte = [0u8; 1];
519 loop {
520 match self.inner.read(&mut byte) {
521 Ok(0) => return Ok(None),
522 Err(e) => return Err(e),
523 Ok(_) if byte[0] != b'\n' => self.line_buf.push(byte[0]),
524 Ok(_) if self.line_buf.is_empty() => return Ok(Some(std::mem::take(&mut self.row))),
525 Ok(_) => {
526 self.row.push(unescape_bytes(&self.line_buf).into_owned());
527 self.line_buf.clear();
528 }
529 }
530 }
531 }
532
533 pub fn partial_row(&self) -> &[Vec<u8>] {
535 &self.row
536 }
537
538 pub fn partial_cell(&self) -> &[u8] {
540 &self.line_buf
541 }
542
543 pub fn into_inner(self) -> io::BufReader<R> {
545 self.inner
546 }
547}
548
549impl<R: io::Read> Iterator for Reader<R> {
550 type Item = io::Result<Vec<Vec<u8>>>;
551 fn next(&mut self) -> Option<Self::Item> {
552 self.next_row().transpose()
553 }
554}
555
556pub struct Writer<W> {
560 inner: W,
561}
562
563impl<W: Write> Writer<W> {
564 pub fn new(writer: W) -> Self {
565 Writer { inner: writer }
566 }
567
568 pub fn write_row<C: AsRef<[u8]>>(&mut self, row: &[C]) -> io::Result<()> {
574 for cell in row {
575 self.inner.write_all(&escape_bytes(cell.as_ref()))?;
576 self.inner.write_all(b"\n")?;
577 }
578 self.inner.write_all(b"\n")
579 }
580
581 pub fn into_inner(self) -> W {
583 self.inner
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 fn owned(rows: Vec<Vec<Cow<[u8]>>>) -> Vec<Vec<Vec<u8>>> {
593 rows.into_iter()
594 .map(|row| row.into_iter().map(|c| c.into_owned()).collect())
595 .collect()
596 }
597
598 #[test]
599 fn test_simple_table() {
600 let nsv = "col1\ncol2\n\na\nb\n\nc\nd\n";
601 let result = decode(nsv);
602 assert_eq!(
603 result,
604 vec![
605 vec!["col1".to_string(), "col2".to_string()],
606 vec!["a".to_string(), "b".to_string()],
607 vec!["c".to_string(), "d".to_string()],
608 ]
609 );
610 }
611
612 #[test]
613 fn test_empty_fields() {
614 let nsv = "a\n\\\nb\n\n\\\nc\n\\\n";
615 let result = decode(nsv);
616 assert_eq!(
617 result,
618 vec![
619 vec!["a".to_string(), "".to_string(), "b".to_string()],
620 vec!["".to_string(), "c".to_string(), "".to_string()],
621 ]
622 );
623 }
624
625 #[test]
626 fn test_escape_sequences() {
627 let nsv = "Line 1\\nLine 2\nBackslash: \\\\\nNot a newline: \\\\n\n";
628 let result = decode(nsv);
629 assert_eq!(
630 result,
631 vec![vec![
632 "Line 1\nLine 2".to_string(),
633 "Backslash: \\".to_string(),
634 "Not a newline: \\n".to_string()
635 ],]
636 );
637 }
638
639 #[test]
640 fn test_empty_rows() {
641 let nsv = "first\n\n\n\nsecond\n";
642 let result = decode(nsv);
643 assert_eq!(
644 result,
645 vec![
646 vec!["first".to_string()],
647 vec![],
648 vec![],
649 vec!["second".to_string()],
650 ]
651 );
652 }
653
654 #[test]
655 fn test_multiple_empty_rows() {
656 let nsv = "a\n\n\n\n\nb\n";
657 let result = decode(nsv);
658 assert_eq!(
659 result,
660 vec![
661 vec!["a".to_string()],
662 vec![],
663 vec![],
664 vec![],
665 vec!["b".to_string()],
666 ]
667 );
668 }
669
670 #[test]
671 fn test_roundtrip() {
672 let original = vec![
673 vec!["col1".to_string(), "col2".to_string()],
674 vec!["a".to_string(), "b".to_string()],
675 vec!["".to_string(), "value\\with\\backslash".to_string()],
676 vec!["multi\nline".to_string(), "normal".to_string()],
677 ];
678
679 let encoded = encode(&original);
680 let decoded = decode(&encoded);
681 assert_eq!(original, decoded);
682 }
683
684 #[test]
685 fn test_unrecognized_escape() {
686 let nsv = "\\x41\\t\\r\n";
687 let result = decode(nsv);
688 assert_eq!(result, vec![vec!["\\x41\\t\\r".to_string()],]);
689 }
690
691 #[test]
692 fn test_dangling_backslash() {
693 let nsv = "text\\\n";
694 let result = decode(nsv);
695 assert_eq!(result, vec![vec!["text".to_string()],]);
696 }
697
698 #[test]
699 fn test_empty_input() {
700 let result = decode("");
701 assert_eq!(result, Vec::<Vec<String>>::new());
702 }
703
704 #[test]
705 fn test_no_trailing_newline() {
706 let nsv = "a\nb";
707 let result = decode(nsv);
708 assert_eq!(result, vec![vec!["a".to_string(), "b".to_string()],]);
709 }
710
711 #[test]
712 fn test_only_empty_rows() {
713 let nsv = "\n\n\n\n";
714 let result = decode(nsv);
715 assert_eq!(
716 result,
717 vec![
718 Vec::<String>::new(),
719 Vec::<String>::new(),
720 Vec::<String>::new(),
721 Vec::<String>::new(),
722 ]
723 );
724 }
725
726 #[test]
727 fn test_starts_with_empty_row() {
728 let nsv = "\n\nfirst\n";
729 let result = decode(nsv);
730 assert_eq!(
731 result,
732 vec![
733 Vec::<String>::new(),
734 Vec::<String>::new(),
735 vec!["first".to_string()],
736 ]
737 );
738 }
739
740 #[test]
741 fn test_large_file() {
742 let large_data: Vec<Vec<String>> = (0..100_000)
745 .map(|i| vec![format!("row{}", i), format!("data{}", i)])
746 .collect();
747
748 let encoded = encode(&large_data);
749
750 assert!(encoded.len() > PARALLEL_THRESHOLD);
752
753 let decoded = decode(&encoded);
754 assert_eq!(large_data, decoded);
755 }
756
757 #[test]
758 fn test_parallel_with_empty_rows() {
759 let mut data = Vec::new();
761
762 for i in 0..10_000 {
764 data.push(vec![format!("value{}", i)]);
765
766 if i % 100 == 0 {
768 data.push(vec![]);
769 }
770 }
771
772 let encoded = encode(&data);
773 assert!(encoded.len() > PARALLEL_THRESHOLD);
774
775 let decoded = decode(&encoded);
776 assert_eq!(data, decoded);
777 }
778
779 #[test]
780 fn test_parallel_with_escape_sequences() {
781 let mut data = Vec::new();
783
784 for i in 0..10_000 {
785 data.push(vec![
786 format!("Line 1\nLine 2 {}", i),
787 format!("Backslash: \\ {}", i),
788 "".to_string(),
789 ]);
790 }
791
792 let encoded = encode(&data);
793 assert!(encoded.len() > PARALLEL_THRESHOLD);
794
795 let decoded = decode(&encoded);
796 assert_eq!(data, decoded);
797 }
798
799 #[test]
802 fn test_bytes_roundtrip() {
803 let original: Vec<Vec<Vec<u8>>> = vec![
804 vec![b"col1".to_vec(), b"col2".to_vec()],
805 vec![b"a".to_vec(), b"b".to_vec()],
806 vec![b"".to_vec(), b"value\\with\\backslash".as_slice().to_vec()],
807 vec![b"multi\nline".to_vec(), b"normal".to_vec()],
808 ];
809
810 let encoded = encode_bytes(&original);
811 let decoded = owned(decode_bytes(&encoded));
812 assert_eq!(original, decoded);
813 }
814
815 #[test]
816 fn test_bytes_non_utf8() {
817 let cell1: Vec<u8> = vec![0xC0, 0xE9, 0xF1]; let cell2: Vec<u8> = vec![0xFF, 0xFE, 0x80]; let original: Vec<Vec<Vec<u8>>> = vec![vec![cell1.clone(), cell2.clone()]];
821
822 let encoded = encode_bytes(&original);
823 let decoded = owned(decode_bytes(&encoded));
824 assert_eq!(original, decoded);
825 }
826
827 #[test]
828 fn test_bytes_empty_cells_and_rows() {
829 let original: Vec<Vec<Vec<u8>>> = vec![
830 vec![b"a".to_vec(), b"".to_vec(), b"b".to_vec()],
831 vec![],
832 vec![b"".to_vec()],
833 ];
834
835 let encoded = encode_bytes(&original);
836 let decoded = owned(decode_bytes(&encoded));
837 assert_eq!(original, decoded);
838 }
839
840 #[test]
841 fn test_bytes_with_special_ascii_bytes() {
842 let cell: Vec<u8> = (0u8..=255)
844 .filter(|&b| b != b'\n' && b != b'\\')
845 .collect();
846 let original = vec![vec![cell.clone()]];
847
848 let encoded = encode_bytes(&original);
849 let decoded = owned(decode_bytes(&encoded));
850 assert_eq!(original, decoded);
851 }
852
853 #[test]
854 fn test_decode_bytes_matches_decode_str() {
855 let inputs = vec![
857 "col1\ncol2\n\na\nb\n\nc\nd\n",
858 "a\n\\\nb\n\n\\\nc\n\\\n",
859 "Line 1\\nLine 2\nBackslash: \\\\\n",
860 "first\n\n\n\nsecond\n",
861 "",
862 "\n\n\n\n",
863 ];
864
865 for input in inputs {
866 let str_result = decode(input);
867 let byte_result = decode_bytes(input.as_bytes());
868
869 let byte_as_str: Vec<Vec<String>> = byte_result
871 .into_iter()
872 .map(|row| {
873 row.into_iter()
874 .map(|cell| String::from_utf8(cell.into_owned()).unwrap())
875 .collect()
876 })
877 .collect();
878
879 assert_eq!(str_result, byte_as_str, "mismatch for input: {:?}", input);
880 }
881 }
882
883 #[test]
884 fn test_bytes_large_parallel() {
885 let large_data: Vec<Vec<Vec<u8>>> = (0..100_000)
887 .map(|i| {
888 vec![
889 format!("row{}", i).into_bytes(),
890 format!("data{}", i).into_bytes(),
891 ]
892 })
893 .collect();
894
895 let encoded = encode_bytes(&large_data);
896 assert!(encoded.len() > PARALLEL_THRESHOLD);
897
898 let decoded = owned(decode_bytes(&encoded));
899 assert_eq!(large_data, decoded);
900 }
901
902 #[test]
905 fn test_check_empty_input() {
906 assert_eq!(check(b""), vec![]);
907 }
908
909 #[test]
910 fn test_check_just_lf() {
911 assert_eq!(check(b"\n"), vec![]);
912 }
913
914 #[test]
915 fn test_check_no_issues() {
916 assert_eq!(check(b"col1\ncol2\n\na\nb\n\n"), vec![]);
917 assert_eq!(check(b"hello\\\\world\n\\n\n\n"), vec![]);
918 }
919
920 #[test]
921 fn test_check_single_unknown_escape() {
922 let warnings = check(b"hello\\tworld\n");
923 assert_eq!(
924 warnings,
925 vec![Warning {
926 kind: WarningKind::UnknownEscape(b't'),
927 pos: 5,
928 line: 1,
929 col: 6,
930 }]
931 );
932 }
933
934 #[test]
935 fn test_check_multiple_unknown_escapes_different_lines() {
936 let warnings = check(b"\\thello\n\\rworld\n\n");
937 assert_eq!(
938 warnings,
939 vec![
940 Warning {
941 kind: WarningKind::UnknownEscape(b't'),
942 pos: 0,
943 line: 1,
944 col: 1,
945 },
946 Warning {
947 kind: WarningKind::UnknownEscape(b'r'),
948 pos: 8,
949 line: 2,
950 col: 1,
951 },
952 ]
953 );
954 }
955
956 #[test]
957 fn test_check_dangling_backslash_mid_file() {
958 let warnings = check(b"text\\\nmore\n\n");
959 assert_eq!(
960 warnings,
961 vec![Warning {
962 kind: WarningKind::DanglingBackslash,
963 pos: 4,
964 line: 1,
965 col: 5,
966 }]
967 );
968 }
969
970 #[test]
971 fn test_check_dangling_backslash_at_eof() {
972 let warnings = check(b"text\\");
973 assert_eq!(
974 warnings,
975 vec![
976 Warning {
977 kind: WarningKind::DanglingBackslash,
978 pos: 4,
979 line: 1,
980 col: 5,
981 },
982 Warning {
983 kind: WarningKind::NoTerminalLf,
984 pos: 5,
985 line: 1,
986 col: 6,
987 },
988 ]
989 );
990 }
991
992 #[test]
993 fn test_check_empty_cell_token_no_warning() {
994 assert_eq!(check(b"\\\n\n"), vec![]);
995 }
996
997 #[test]
998 fn test_check_empty_cell_token_unterminated_row() {
999 let warnings = check(b"\\\n");
1000 assert!(
1001 !warnings
1002 .iter()
1003 .any(|w| w.kind == WarningKind::DanglingBackslash),
1004 "empty-cell token must not warn as dangling backslash: {warnings:?}"
1005 );
1006 }
1007
1008 #[test]
1009 fn test_check_dangling_backslash_with_content_still_warns() {
1010 let warnings = check(b"text\\\n\n");
1011 assert_eq!(
1012 warnings,
1013 vec![Warning {
1014 kind: WarningKind::DanglingBackslash,
1015 pos: 4,
1016 line: 1,
1017 col: 5,
1018 }]
1019 );
1020 }
1021
1022 #[test]
1023 fn test_check_empty_cell_and_dangling_mixed() {
1024 let warnings = check(b"\\\nabc\\\n\n");
1025 assert_eq!(
1026 warnings,
1027 vec![Warning {
1028 kind: WarningKind::DanglingBackslash,
1029 pos: 5,
1030 line: 2,
1031 col: 4,
1032 }]
1033 );
1034 }
1035
1036 #[test]
1037 fn test_check_no_terminal_lf() {
1038 let warnings = check(b"hello");
1039 assert_eq!(
1040 warnings,
1041 vec![Warning {
1042 kind: WarningKind::NoTerminalLf,
1043 pos: 5,
1044 line: 1,
1045 col: 6,
1046 }]
1047 );
1048 }
1049
1050 #[test]
1051 fn test_check_combination() {
1052 let warnings = check(b"\\thello\\\nworld");
1054 assert_eq!(
1055 warnings,
1056 vec![
1057 Warning {
1058 kind: WarningKind::UnknownEscape(b't'),
1059 pos: 0,
1060 line: 1,
1061 col: 1,
1062 },
1063 Warning {
1064 kind: WarningKind::DanglingBackslash,
1065 pos: 7,
1066 line: 1,
1067 col: 8,
1068 },
1069 Warning {
1070 kind: WarningKind::NoTerminalLf,
1071 pos: 14,
1072 line: 2,
1073 col: 6,
1074 },
1075 ]
1076 );
1077 }
1078
1079 #[test]
1080 fn test_check_non_utf8() {
1081 let input: &[u8] = &[0xFF, 0xFE, b'\\', b't', 0x80, b'\n'];
1083 let warnings = check(input);
1084 assert_eq!(
1085 warnings,
1086 vec![Warning {
1087 kind: WarningKind::UnknownEscape(b't'),
1088 pos: 2,
1089 line: 1,
1090 col: 3,
1091 }]
1092 );
1093 }
1094
1095 #[test]
1098 fn test_project_subset() {
1099 let nsv = b"c0\nc1\nc2\nc3\n\na\nb\nc\nd\n\ne\nf\ng\nh\n\n";
1100 let projected = owned(decode_bytes_projected(nsv, &[0, 2]));
1101 assert_eq!(projected.len(), 3);
1102 assert_eq!(projected[0], vec![b"c0".to_vec(), b"c2".to_vec()]);
1103 assert_eq!(projected[1], vec![b"a".to_vec(), b"c".to_vec()]);
1104 assert_eq!(projected[2], vec![b"e".to_vec(), b"g".to_vec()]);
1105 }
1106
1107 #[test]
1108 fn test_project_single_column() {
1109 let nsv = b"name\nage\nsalary\n\nAlice\n30\n50000\n\nBob\n25\n75000\n\n";
1110 let projected = owned(decode_bytes_projected(nsv, &[1]));
1111 assert_eq!(projected.len(), 3);
1112 assert_eq!(projected[0], vec![b"age".to_vec()]);
1113 assert_eq!(projected[1], vec![b"30".to_vec()]);
1114 assert_eq!(projected[2], vec![b"25".to_vec()]);
1115 }
1116
1117 #[test]
1118 fn test_project_reorder() {
1119 let nsv = b"a\nb\nc\n\n1\n2\n3\n\n";
1120 let projected = owned(decode_bytes_projected(nsv, &[2, 0]));
1121 assert_eq!(projected[0], vec![b"c".to_vec(), b"a".to_vec()]);
1122 assert_eq!(projected[1], vec![b"3".to_vec(), b"1".to_vec()]);
1123 }
1124
1125 #[test]
1126 fn test_project_out_of_range() {
1127 let nsv = b"a\nb\n\n";
1128 let projected = owned(decode_bytes_projected(nsv, &[0, 5]));
1129 assert_eq!(projected[0], vec![b"a".to_vec(), b"".to_vec()]);
1130 }
1131
1132 #[test]
1133 fn test_projected_matches_full() {
1134 let nsv = b"c0\nc1\nc2\n\na\nb\nc\n\n";
1135 let full = owned(decode_bytes(nsv));
1136 let projected = owned(decode_bytes_projected(nsv, &[0, 1, 2]));
1137 assert_eq!(projected, full);
1138 }
1139
1140 #[test]
1141 fn test_project_with_escapes_parallel() {
1142 let mut data = Vec::new();
1143 for i in 0..10_000 {
1144 data.push(vec![
1145 format!("Line 1\nLine 2 {}", i),
1146 format!("Backslash: \\ {}", i),
1147 format!("plain{}", i),
1148 ]);
1149 }
1150 let encoded = encode(&data);
1151 let encoded_bytes = encoded.as_bytes();
1152 assert!(encoded_bytes.len() > PARALLEL_THRESHOLD);
1153
1154 let projected = decode_bytes_projected(encoded_bytes, &[2]);
1155 assert_eq!(projected.len(), data.len());
1156 for (ri, row) in data.iter().enumerate() {
1157 assert_eq!(
1158 String::from_utf8(projected[ri][0].to_vec()).unwrap(),
1159 row[2]
1160 );
1161 }
1162
1163 let full = owned(decode_bytes(encoded_bytes));
1164 let projected_all = owned(decode_bytes_projected(encoded_bytes, &[0, 1, 2]));
1165 assert_eq!(projected_all, full);
1166 }
1167
1168 use std::io::Cursor;
1171
1172 #[test]
1173 fn test_bytes_reader_matches_batch() {
1174 for input in [
1175 &b"a\nb\n\nc\nd\n\n"[..],
1176 b"a\n\\\nb\n\n\\\nc\n\\\n\n", b"Line 1\\nLine 2\n\\\\\n\\\\n\n\n", b"first\n\n\n\nsecond\n\n", b"\\\n\\\n\\\n\n", b"\n\n\n\n", b"",
1182 ] {
1183 let streaming: Vec<_> = Reader::new(Cursor::new(input))
1184 .map(|r| r.unwrap())
1185 .collect();
1186 assert_eq!(streaming, owned(decode_bytes(input)), "input: {:?}", input);
1187 }
1188 }
1189
1190 #[test]
1191 fn test_bytes_reader_incomplete_row_not_emitted() {
1192 let mut r = Reader::new(Cursor::new(&b"a\nb\n\nc\nd"[..]));
1193 assert_eq!(r.next_row().unwrap(), Some(vec![b"a".to_vec(), b"b".to_vec()]));
1194 assert_eq!(r.next_row().unwrap(), None); }
1196
1197 use std::cell::RefCell;
1200
1201 struct GrowableStream(RefCell<(Vec<u8>, usize)>);
1202 impl GrowableStream {
1203 fn new() -> Self { GrowableStream(RefCell::new((Vec::new(), 0))) }
1204 fn append(&self, b: &[u8]) { self.0.borrow_mut().0.extend_from_slice(b); }
1205 }
1206 impl io::Read for &GrowableStream {
1207 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1208 let mut s = self.0.borrow_mut();
1209 let n = buf.len().min(s.0.len() - s.1);
1210 buf[..n].copy_from_slice(&s.0[s.1..s.1 + n]);
1211 s.1 += n;
1212 Ok(n)
1213 }
1214 }
1215
1216 #[test]
1217 fn test_bytes_reader_resumable() {
1218 let s = GrowableStream::new();
1219 let mut r = Reader::new(&s);
1220
1221 s.append(b"a\nb\n\nc\n");
1223 assert_eq!(r.next_row().unwrap(), Some(vec![b"a".to_vec(), b"b".to_vec()]));
1224 assert_eq!(r.next_row().unwrap(), None);
1225 s.append(b"d\n\n");
1226 assert_eq!(r.next_row().unwrap(), Some(vec![b"c".to_vec(), b"d".to_vec()]));
1227
1228 s.append(b"hel");
1230 assert_eq!(r.next_row().unwrap(), None);
1231 s.append(b"lo\n\n");
1232 assert_eq!(r.next_row().unwrap(), Some(vec![b"hello".to_vec()]));
1233 }
1234
1235 #[test]
1238 fn test_bytes_reader() {
1239 for input in [
1240 &b"col1\ncol2\n\na\nb\n\nc\nd\n\n"[..],
1241 b"\n\n\n\n", b"", b"text\\\n\n",
1242 ] {
1243 let streaming: Vec<_> = Reader::new(Cursor::new(input))
1244 .map(|r| r.unwrap())
1245 .collect();
1246 assert_eq!(streaming, owned(decode_bytes(input)));
1247 }
1248 let orig = vec![vec![vec![0xC0, 0xE9], vec![0xFF, 0xFE]]];
1250 let enc = encode_bytes(&orig);
1251 let dec: Vec<_> = Reader::new(Cursor::new(&enc[..]))
1252 .map(|r| r.unwrap())
1253 .collect();
1254 assert_eq!(dec, orig);
1255 }
1256
1257 #[test]
1260 fn test_writer() {
1261 let mut buf = Vec::new();
1262 let mut w = Writer::new(&mut buf);
1263 w.write_row(&["hello", "world"]).unwrap();
1264 assert_eq!(buf, b"hello\nworld\n\n");
1265
1266 buf.clear();
1267 Writer::new(&mut buf).write_row(&["line1\nline2", "back\\slash"]).unwrap();
1268 assert_eq!(buf, b"line1\\nline2\nback\\\\slash\n\n");
1269
1270 buf.clear();
1271 Writer::new(&mut buf).write_row(&["", "", ""]).unwrap();
1272 assert_eq!(buf, b"\\\n\\\n\\\n\n");
1273
1274 buf.clear();
1275 let empty: &[&str] = &[];
1276 Writer::new(&mut buf).write_row(empty).unwrap();
1277 assert_eq!(buf, b"\n");
1278 }
1279
1280 #[test]
1281 fn test_writer_matches_batch_encode() {
1282 let data = vec![
1283 vec!["a".to_string(), "b".to_string()],
1284 vec!["".to_string()],
1285 vec!["line\none".to_string(), "back\\slash".to_string()],
1286 ];
1287 let mut buf = Vec::new();
1288 {
1289 let mut w = Writer::new(&mut buf);
1290 for row in &data { w.write_row(row).unwrap(); }
1291 }
1292 assert_eq!(buf, encode(&data).as_bytes());
1293 }
1294
1295 #[test]
1298 fn test_roundtrip_streaming() {
1299 let original: Vec<Vec<Vec<u8>>> = vec![
1300 vec![b"a".to_vec(), b"b".to_vec()],
1301 vec![b"".to_vec(), b"val\\ue".to_vec()],
1302 vec![b"multi\nline".to_vec(), b"normal".to_vec()],
1303 vec![],
1304 ];
1305 let mut buf = Vec::new();
1306 {
1307 let mut w = Writer::new(&mut buf);
1308 for row in &original {
1309 let refs: Vec<&[u8]> = row.iter().map(|c| c.as_slice()).collect();
1310 w.write_row(&refs).unwrap();
1311 }
1312 }
1313 let decoded: Vec<_> = Reader::new(Cursor::new(&buf[..]))
1314 .map(|r| r.unwrap())
1315 .collect();
1316 assert_eq!(decoded, original);
1317 }
1318}