1#![allow(rustdoc::private_intra_doc_links)]
7
8use std::borrow::Borrow;
9use std::cmp::Ordering;
10use std::ffi::OsString;
11use std::io::{self, BufReader, ErrorKind};
12use std::{
13 fs::{File, remove_file},
14 io::{BufRead, BufWriter, Write},
15};
16
17use clap::{Arg, ArgAction, ArgMatches, Command};
18use regex::Regex;
19use uucore::display::Quotable;
20use uucore::error::{FromIo, UResult};
21use uucore::format_usage;
22
23mod csplit_error;
24mod patterns;
25mod split_name;
26
27use crate::csplit_error::CsplitError;
28use crate::split_name::SplitName;
29
30use uucore::translate;
31
32mod options {
33 pub const SUFFIX_FORMAT: &str = "suffix-format";
34 pub const SUPPRESS_MATCHED: &str = "suppress-matched";
35 pub const DIGITS: &str = "digits";
36 pub const PREFIX: &str = "prefix";
37 pub const KEEP_FILES: &str = "keep-files";
38 pub const QUIET: &str = "quiet";
39 pub const ELIDE_EMPTY_FILES: &str = "elide-empty-files";
40 pub const FILE: &str = "file";
41 pub const PATTERN: &str = "pattern";
42}
43
44pub struct CsplitOptions {
46 split_name: SplitName,
47 keep_files: bool,
48 quiet: bool,
49 elide_empty_files: bool,
50 suppress_matched: bool,
51}
52
53impl CsplitOptions {
54 fn new(matches: &ArgMatches) -> Result<Self, CsplitError> {
55 let keep_files = matches.get_flag(options::KEEP_FILES);
56 let quiet = matches.get_flag(options::QUIET);
57 let elide_empty_files = matches.get_flag(options::ELIDE_EMPTY_FILES);
58 let suppress_matched = matches.get_flag(options::SUPPRESS_MATCHED);
59
60 Ok(Self {
61 split_name: SplitName::new(
62 matches.get_one::<String>(options::PREFIX).cloned(),
63 matches.get_one::<String>(options::SUFFIX_FORMAT).cloned(),
64 matches.get_one::<String>(options::DIGITS).cloned(),
65 )?,
66 keep_files,
67 quiet,
68 elide_empty_files,
69 suppress_matched,
70 })
71 }
72}
73
74pub struct LinesWithNewlines<T: BufRead> {
75 inner: T,
76}
77
78impl<T: BufRead> LinesWithNewlines<T> {
79 fn new(s: T) -> Self {
80 Self { inner: s }
81 }
82}
83
84impl<T: BufRead> Iterator for LinesWithNewlines<T> {
85 type Item = io::Result<String>;
86
87 fn next(&mut self) -> Option<Self::Item> {
88 fn ret(v: Vec<u8>) -> io::Result<String> {
89 String::from_utf8(v).map_err(|_| {
90 io::Error::new(ErrorKind::InvalidData, translate!("csplit-stream-not-utf8"))
91 })
92 }
93
94 let mut v = Vec::new();
95 match self.inner.read_until(b'\n', &mut v) {
96 Ok(0) => None,
97 Ok(_) => Some(ret(v)),
98 Err(e) => Some(Err(e)),
99 }
100 }
101}
102
103pub fn csplit<T>(options: &CsplitOptions, patterns: &[&str], input: T) -> Result<(), CsplitError>
116where
117 T: BufRead,
118{
119 let enumerated_input_lines = LinesWithNewlines::new(input)
120 .map(|line| line.map_err_context(|| translate!("csplit-read-error")))
121 .enumerate();
122 let mut input_iter = InputSplitter::new(enumerated_input_lines);
123 let mut split_writer = SplitWriter::new(options);
124 let patterns_vec: Vec<patterns::Pattern> = patterns::get_patterns(patterns)?;
125 let all_up_to_line = patterns_vec
126 .iter()
127 .all(|p| matches!(p, patterns::Pattern::UpToLine(_, _)));
128 let ret = do_csplit(&mut split_writer, patterns_vec, &mut input_iter);
129
130 let ret = if ret.is_ok() {
132 input_iter.rewind_buffer();
133 if let Some((_, line)) = input_iter.next() {
134 split_writer.new_writer()?;
136 split_writer.writeln(&line?)?;
137 for (_, line) in input_iter {
138 split_writer.writeln(&line?)?;
139 }
140 split_writer.finish_split()
141 } else if all_up_to_line && options.suppress_matched {
142 split_writer.new_writer()?;
145 split_writer.finish_split()
146 } else {
147 Ok(())
148 }
149 } else {
150 ret
151 };
152 if ret.is_err() && !options.keep_files {
154 split_writer.delete_all_splits()?;
155 }
156 ret
157}
158
159fn do_csplit<I>(
160 split_writer: &mut SplitWriter,
161 patterns: Vec<patterns::Pattern>,
162 input_iter: &mut InputSplitter<I>,
163) -> Result<(), CsplitError>
164where
165 I: Iterator<Item = (usize, UResult<String>)>,
166{
167 for pattern in patterns {
169 let pattern_as_str = pattern.to_string();
170 let is_skip = matches!(pattern, patterns::Pattern::SkipToMatch(_, _, _));
171 match pattern {
172 patterns::Pattern::UpToLine(n, ex) => {
173 let mut up_to_line = n;
174 for (_, ith) in ex.iter() {
175 split_writer.new_writer()?;
176 match split_writer.do_to_line(&pattern_as_str, up_to_line, input_iter) {
177 Err(CsplitError::LineOutOfRange(_)) if ith != 1 => {
179 return Err(CsplitError::LineOutOfRangeOnRepetition(
180 pattern_as_str,
181 ith - 1,
182 ));
183 }
184 Err(err) => return Err(err),
185 Ok(()) => (),
187 }
188 up_to_line += n;
189 }
190 }
191 patterns::Pattern::UpToMatch(regex, offset, ex)
192 | patterns::Pattern::SkipToMatch(regex, offset, ex) => {
193 for (max, ith) in ex.iter() {
194 if is_skip {
195 split_writer.as_dev_null();
197 } else {
198 split_writer.new_writer()?;
199 }
200 match (
201 split_writer.do_to_match(&pattern_as_str, ®ex, offset, input_iter),
202 max,
203 ) {
204 (Err(CsplitError::MatchNotFound(_)), None) => {
207 return Ok(());
208 }
209 (Err(CsplitError::MatchNotFound(_)), Some(m)) if m != 1 && ith != 1 => {
211 return Err(CsplitError::MatchNotFoundOnRepetition(
212 pattern_as_str,
213 ith - 1,
214 ));
215 }
216 (Err(err), _) => return Err(err),
217 (Ok(()), _) => (),
219 }
220 }
221 }
222 }
223 }
224 Ok(())
225}
226
227struct SplitWriter<'a> {
230 options: &'a CsplitOptions,
232 counter: usize,
234 current_writer: Option<BufWriter<File>>,
236 size: usize,
238 dev_null: bool,
240}
241
242impl Drop for SplitWriter<'_> {
243 fn drop(&mut self) {
244 if self.options.elide_empty_files && self.size == 0 {
245 let file_name = self.options.split_name.get(self.counter);
246 let _ = remove_file(file_name);
252 }
253 }
254}
255
256impl SplitWriter<'_> {
257 fn new(options: &CsplitOptions) -> SplitWriter<'_> {
258 SplitWriter {
259 options,
260 counter: 0,
261 current_writer: None,
262 size: 0,
263 dev_null: false,
264 }
265 }
266
267 fn new_writer(&mut self) -> Result<(), CsplitError> {
273 let file_name = self.options.split_name.get(self.counter);
274 let file = File::create(&file_name)
275 .map_err_context(|| file_name.clone())
276 .map_err(CsplitError::from)?;
277 self.current_writer = Some(BufWriter::new(file));
278 self.counter += 1;
279 self.size = 0;
280 self.dev_null = false;
281 Ok(())
282 }
283
284 fn as_dev_null(&mut self) {
286 self.dev_null = true;
287 }
288
289 fn writeln(&mut self, line: &str) -> io::Result<()> {
296 if !self.dev_null {
297 if let Some(ref mut current_writer) = self.current_writer {
298 let bytes = line.as_bytes();
299 current_writer.write_all(bytes)?;
300 self.size += bytes.len();
301 } else {
302 panic!("{}", translate!("csplit-write-split-not-created"))
303 }
304 }
305 Ok(())
306 }
307
308 fn finish_split(&mut self) -> Result<(), CsplitError> {
316 if !self.dev_null {
317 if let Some(ref mut writer) = self.current_writer {
319 let file_name = self.options.split_name.get(self.counter - 1);
320 writer
321 .flush()
322 .map_err_context(|| file_name.clone())
323 .map_err(CsplitError::from)?;
324 }
325 if self.options.elide_empty_files && self.size == 0 {
326 self.counter -= 1;
327 } else if !self.options.quiet {
328 println!("{}", self.size);
329 }
330 }
331 Ok(())
332 }
333
334 fn delete_all_splits(&self) -> io::Result<()> {
340 let mut ret = Ok(());
341 for ith in 0..self.counter {
342 let file_name = self.options.split_name.get(ith);
343 if let Err(err) = remove_file(file_name) {
344 ret = Err(err);
345 }
346 }
347 ret
348 }
349
350 fn do_to_line<I>(
361 &mut self,
362 pattern_as_str: &str,
363 n: usize,
364 input_iter: &mut InputSplitter<I>,
365 ) -> Result<(), CsplitError>
366 where
367 I: Iterator<Item = (usize, UResult<String>)>,
368 {
369 input_iter.rewind_buffer();
370 input_iter.set_size_of_buffer(1);
371
372 let mut ret = Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
373 while let Some((ln, line)) = input_iter.next() {
374 let line = line?;
375 match n.cmp(&(&ln + 1)) {
376 Ordering::Less => {
377 assert!(
378 input_iter.add_line_to_buffer(ln, line).is_none(),
379 "the buffer is big enough to contain 1 line"
380 );
381 ret = Ok(());
382 break;
383 }
384 Ordering::Equal => {
385 assert!(
386 self.options.suppress_matched
387 || input_iter.add_line_to_buffer(ln, line).is_none(),
388 "the buffer is big enough to contain 1 line"
389 );
390 ret = Ok(());
391 break;
392 }
393 Ordering::Greater => (),
394 }
395 self.writeln(&line)?;
396 }
397 self.finish_split()?;
398 ret
399 }
400
401 #[allow(clippy::cognitive_complexity)]
412 fn do_to_match<I>(
413 &mut self,
414 pattern_as_str: &str,
415 regex: &Regex,
416 mut offset: i32,
417 input_iter: &mut InputSplitter<I>,
418 ) -> Result<(), CsplitError>
419 where
420 I: Iterator<Item = (usize, UResult<String>)>,
421 {
422 if offset >= 0 {
423 for line in input_iter.drain_buffer() {
426 self.writeln(&line)?;
427 }
428 input_iter.set_size_of_buffer(1);
430
431 while let Some((ln, line)) = input_iter.next() {
432 let line = line?;
433 let l = line
434 .strip_suffix("\r\n")
435 .unwrap_or_else(|| line.strip_suffix('\n').unwrap_or(&line));
436 if regex.is_match(l) {
437 let mut next_line_suppress_matched = false;
438 match (self.options.suppress_matched, offset) {
439 (false, 0) => {
441 assert!(
442 input_iter.add_line_to_buffer(ln, line).is_none(),
443 "the buffer is big enough to contain 1 line"
444 );
445 }
446 (false, _) => self.writeln(&line)?,
448 (true, 1..) => {
450 next_line_suppress_matched = true;
451 self.writeln(&line)?;
452 }
453 _ => (),
454 }
455 offset -= 1;
456
457 while offset > 0 {
459 if let Some((_, line)) = input_iter.next() {
460 self.writeln(&line?)?;
461 } else {
462 self.finish_split()?;
463 return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
464 }
465 offset -= 1;
466 }
467 self.finish_split()?;
468
469 if next_line_suppress_matched {
471 input_iter.next();
472 }
473 return Ok(());
474 }
475 self.writeln(&line)?;
476 }
477 } else {
478 let offset_usize = -offset as usize;
484 input_iter.set_size_of_buffer(offset_usize);
485 while let Some((ln, line)) = input_iter.next() {
486 let line = line?;
487 let l = line
488 .strip_suffix("\r\n")
489 .unwrap_or_else(|| line.strip_suffix('\n').unwrap_or(&line));
490 if regex.is_match(l) {
491 for line in input_iter.shrink_buffer_to_size() {
492 self.writeln(&line)?;
493 }
494 if self.options.suppress_matched {
495 input_iter.add_line_to_buffer(ln, line);
499 } else {
500 input_iter.set_size_of_buffer(offset_usize + 1);
502 assert!(
503 input_iter.add_line_to_buffer(ln, line).is_none(),
504 "should be big enough to hold every lines"
505 );
506 }
507
508 self.finish_split()?;
509 if input_iter.buffer_len() < offset_usize {
510 return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
511 }
512 return Ok(());
513 }
514 if let Some(line) = input_iter.add_line_to_buffer(ln, line) {
515 self.writeln(&line)?;
516 }
517 }
518 for line in input_iter.drain_buffer() {
520 self.writeln(&line)?;
521 }
522 }
523
524 self.finish_split()?;
525 Err(CsplitError::MatchNotFound(pattern_as_str.to_string()))
526 }
527}
528
529struct InputSplitter<I>
532where
533 I: Iterator<Item = (usize, UResult<String>)>,
534{
535 iter: I,
536 buffer: Vec<<I as Iterator>::Item>,
537 size: usize,
539 rewind: bool,
542}
543
544impl<I> InputSplitter<I>
545where
546 I: Iterator<Item = (usize, UResult<String>)>,
547{
548 fn new(iter: I) -> Self {
549 Self {
550 iter,
551 buffer: Vec::new(),
552 rewind: false,
553 size: 1,
554 }
555 }
556
557 fn rewind_buffer(&mut self) {
559 self.rewind = true;
560 }
561
562 fn shrink_buffer_to_size(&mut self) -> impl Iterator<Item = String> + '_ {
565 let shrink_offset = if self.buffer.len() > self.size {
566 self.buffer.len() - self.size
567 } else {
568 0
569 };
570 self.buffer
571 .drain(..shrink_offset)
572 .map(|(_, line)| line.unwrap())
573 }
574
575 fn drain_buffer(&mut self) -> impl Iterator<Item = String> + '_ {
577 self.buffer.drain(..).map(|(_, line)| line.unwrap())
578 }
579
580 fn set_size_of_buffer(&mut self, size: usize) {
582 self.size = size;
583 }
584
585 fn add_line_to_buffer(&mut self, ln: usize, line: String) -> Option<String> {
589 if self.rewind {
590 self.buffer.insert(0, (ln, Ok(line)));
591 None
592 } else if self.buffer.len() >= self.size {
593 let (_, head_line) = self.buffer.remove(0);
594 self.buffer.push((ln, Ok(line)));
595 Some(head_line.unwrap())
596 } else {
597 self.buffer.push((ln, Ok(line)));
598 None
599 }
600 }
601
602 fn buffer_len(&self) -> usize {
604 self.buffer.len()
605 }
606}
607
608impl<I> Iterator for InputSplitter<I>
609where
610 I: Iterator<Item = (usize, UResult<String>)>,
611{
612 type Item = <I as Iterator>::Item;
613
614 fn next(&mut self) -> Option<Self::Item> {
615 if self.rewind {
616 if !self.buffer.is_empty() {
617 return Some(self.buffer.remove(0));
618 }
619 self.rewind = false;
620 }
621 self.iter.next()
622 }
623}
624
625#[uucore::main]
626pub fn uumain(args: impl uucore::Args) -> UResult<()> {
627 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
628
629 let file_name = matches.get_one::<OsString>(options::FILE).unwrap();
631
632 let patterns: Vec<_> = matches
634 .get_many::<String>(options::PATTERN)
635 .unwrap()
636 .map(Borrow::borrow)
637 .collect();
638 let options = CsplitOptions::new(&matches)?;
639 if file_name == "-" {
640 let stdin = io::stdin();
641 Ok(csplit(&options, &patterns, stdin.lock())?)
642 } else {
643 let file = File::open(file_name)
644 .map_err_context(|| format!("cannot open {} for reading", file_name.quote()))?;
645 Ok(csplit(&options, &patterns, BufReader::new(file))?)
646 }
647}
648
649pub fn uu_app() -> Command {
650 Command::new("csplit")
651 .version(uucore::crate_version!())
652 .help_template(uucore::localized_help_template(uucore::util_name()))
653 .about(translate!("csplit-about"))
654 .override_usage(format_usage(&translate!("csplit-usage")))
655 .args_override_self(true)
656 .infer_long_args(true)
657 .arg(
658 Arg::new(options::SUFFIX_FORMAT)
659 .short('b')
660 .long(options::SUFFIX_FORMAT)
661 .value_name("FORMAT")
662 .help(translate!("csplit-help-suffix-format")),
663 )
664 .arg(
665 Arg::new(options::PREFIX)
666 .short('f')
667 .long(options::PREFIX)
668 .value_name("PREFIX")
669 .help(translate!("csplit-help-prefix")),
670 )
671 .arg(
672 Arg::new(options::KEEP_FILES)
673 .short('k')
674 .long(options::KEEP_FILES)
675 .help(translate!("csplit-help-keep-files"))
676 .action(ArgAction::SetTrue),
677 )
678 .arg(
679 Arg::new(options::SUPPRESS_MATCHED)
680 .long(options::SUPPRESS_MATCHED)
681 .help(translate!("csplit-help-suppress-matched"))
682 .action(ArgAction::SetTrue),
683 )
684 .arg(
685 Arg::new(options::DIGITS)
686 .short('n')
687 .long(options::DIGITS)
688 .value_name("DIGITS")
689 .help(translate!("csplit-help-digits")),
690 )
691 .arg(
692 Arg::new(options::QUIET)
693 .short('q')
694 .long(options::QUIET)
695 .visible_short_alias('s')
696 .visible_alias("silent")
697 .help(translate!("csplit-help-quiet"))
698 .action(ArgAction::SetTrue),
699 )
700 .arg(
701 Arg::new(options::ELIDE_EMPTY_FILES)
702 .short('z')
703 .long(options::ELIDE_EMPTY_FILES)
704 .help(translate!("csplit-help-elide-empty-files"))
705 .action(ArgAction::SetTrue),
706 )
707 .arg(
708 Arg::new(options::FILE)
709 .hide(true)
710 .required(true)
711 .value_hint(clap::ValueHint::FilePath)
712 .value_parser(clap::value_parser!(OsString)),
713 )
714 .arg(
715 Arg::new(options::PATTERN)
716 .hide(true)
717 .action(ArgAction::Append)
718 .required(true),
719 )
720 .after_help(translate!("csplit-after-help"))
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726
727 #[test]
728 #[allow(clippy::cognitive_complexity)]
729 fn input_splitter() {
730 let input = vec![
731 Ok(String::from("aaa")),
732 Ok(String::from("bbb")),
733 Ok(String::from("ccc")),
734 Ok(String::from("ddd")),
735 ];
736 let mut input_splitter = InputSplitter::new(input.into_iter().enumerate());
737
738 input_splitter.set_size_of_buffer(2);
739 assert_eq!(input_splitter.buffer_len(), 0);
740
741 match input_splitter.next() {
742 Some((0, Ok(line))) => {
743 assert_eq!(line, String::from("aaa"));
744 assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
745 assert_eq!(input_splitter.buffer_len(), 1);
746 }
747 item => panic!("wrong item: {item:?}"),
748 }
749
750 match input_splitter.next() {
751 Some((1, Ok(line))) => {
752 assert_eq!(line, String::from("bbb"));
753 assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
754 assert_eq!(input_splitter.buffer_len(), 2);
755 }
756 item => panic!("wrong item: {item:?}"),
757 }
758
759 match input_splitter.next() {
760 Some((2, Ok(line))) => {
761 assert_eq!(line, String::from("ccc"));
762 assert_eq!(
763 input_splitter.add_line_to_buffer(2, line),
764 Some(String::from("aaa"))
765 );
766 assert_eq!(input_splitter.buffer_len(), 2);
767 }
768 item => panic!("wrong item: {item:?}"),
769 }
770
771 input_splitter.rewind_buffer();
772
773 match input_splitter.next() {
774 Some((1, Ok(line))) => {
775 assert_eq!(line, String::from("bbb"));
776 assert_eq!(input_splitter.buffer_len(), 1);
777 }
778 item => panic!("wrong item: {item:?}"),
779 }
780
781 match input_splitter.next() {
782 Some((2, Ok(line))) => {
783 assert_eq!(line, String::from("ccc"));
784 assert_eq!(input_splitter.buffer_len(), 0);
785 }
786 item => panic!("wrong item: {item:?}"),
787 }
788
789 match input_splitter.next() {
790 Some((3, Ok(line))) => {
791 assert_eq!(line, String::from("ddd"));
792 assert_eq!(input_splitter.buffer_len(), 0);
793 }
794 item => panic!("wrong item: {item:?}"),
795 }
796
797 assert!(input_splitter.next().is_none());
798 }
799
800 #[test]
801 #[allow(clippy::cognitive_complexity)]
802 fn input_splitter_interrupt_rewind() {
803 let input = vec![
804 Ok(String::from("aaa")),
805 Ok(String::from("bbb")),
806 Ok(String::from("ccc")),
807 Ok(String::from("ddd")),
808 ];
809 let mut input_splitter = InputSplitter::new(input.into_iter().enumerate());
810
811 input_splitter.set_size_of_buffer(3);
812 assert_eq!(input_splitter.buffer_len(), 0);
813
814 match input_splitter.next() {
815 Some((0, Ok(line))) => {
816 assert_eq!(line, String::from("aaa"));
817 assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
818 assert_eq!(input_splitter.buffer_len(), 1);
819 }
820 item => panic!("wrong item: {item:?}"),
821 }
822
823 match input_splitter.next() {
824 Some((1, Ok(line))) => {
825 assert_eq!(line, String::from("bbb"));
826 assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
827 assert_eq!(input_splitter.buffer_len(), 2);
828 }
829 item => panic!("wrong item: {item:?}"),
830 }
831
832 match input_splitter.next() {
833 Some((2, Ok(line))) => {
834 assert_eq!(line, String::from("ccc"));
835 assert_eq!(input_splitter.add_line_to_buffer(2, line), None);
836 assert_eq!(input_splitter.buffer_len(), 3);
837 }
838 item => panic!("wrong item: {item:?}"),
839 }
840
841 input_splitter.rewind_buffer();
842
843 match input_splitter.next() {
844 Some((0, Ok(line))) => {
845 assert_eq!(line, String::from("aaa"));
846 assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
847 assert_eq!(input_splitter.buffer_len(), 3);
848 }
849 item => panic!("wrong item: {item:?}"),
850 }
851
852 match input_splitter.next() {
853 Some((0, Ok(line))) => {
854 assert_eq!(line, String::from("aaa"));
855 assert_eq!(input_splitter.buffer_len(), 2);
856 }
857 item => panic!("wrong item: {item:?}"),
858 }
859
860 match input_splitter.next() {
861 Some((1, Ok(line))) => {
862 assert_eq!(line, String::from("bbb"));
863 assert_eq!(input_splitter.buffer_len(), 1);
864 }
865 item => panic!("wrong item: {item:?}"),
866 }
867
868 match input_splitter.next() {
869 Some((2, Ok(line))) => {
870 assert_eq!(line, String::from("ccc"));
871 assert_eq!(input_splitter.buffer_len(), 0);
872 }
873 item => panic!("wrong item: {item:?}"),
874 }
875
876 match input_splitter.next() {
877 Some((3, Ok(line))) => {
878 assert_eq!(line, String::from("ddd"));
879 assert_eq!(input_splitter.buffer_len(), 0);
880 }
881 item => panic!("wrong item: {item:?}"),
882 }
883
884 assert!(input_splitter.next().is_none());
885 }
886}