// Copyright (C) 2021 Mike Cugini // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use std::error::Error; use std::fs::File; use std::io::prelude::*; use std::io::{self, BufReader}; use argh::FromArgs; mod suffix; use crate::suffix::{AlphabeticSuffixGenerator, NumericSuffixGenerator}; #[derive(FromArgs)] #[argh(description = "\ chop off bits of file\n\n\ chop will take produce --count files of --lines lines from the beginning of a\n\ file or stdin. Any remaining lines will be written to a final catchall file.")] struct Options { /// number of lines in each chunk #[argh(option, short = 'n')] lines: usize, /// count of chunks to produce (default 1) #[argh(option, short = 'c', default = "1")] count: usize, /// optional prefix to use for generated files (default "") #[argh(option, short = 'p', default = "String::from(\"\")")] prefix: String, /// use numeric suffixes starting with 0, not alphabetic #[argh(switch, short = 'd')] numeric: bool, /// use numeric suffixes starting with this value #[argh(option, long = "numeric-start")] numeric_start: Option, /// filename to read from, or "-" for stdin (default "-") #[argh(positional, default = "String::from(\"-\")")] filename: String, } fn try_main() -> Result<(), Box> { let opts: Options = argh::from_env(); // open source file let input: Box = match opts.filename.as_str() { "-" => Box::new(io::stdin()), path => match File::open(path) { Err(why) => panic!("failed to open {}: {}", path, why), Ok(file) => Box::new(file), }, }; let mut reader = BufReader::new(input); // build prefix format let prefix = match opts.prefix.as_str() { "" => String::from(""), _ => opts.prefix + "_", }; // use numeric suffixes if --numeric or --numeric-start are passed let use_numeric = opts.numeric || opts.numeric_start.is_some(); let suffix_gen: Box> = if use_numeric { let numeric_start = match opts.numeric_start { Some(num) => num, None => 0, }; Box::new(NumericSuffixGenerator::new(numeric_start, opts.count)) } else { Box::new(AlphabeticSuffixGenerator::new(opts.count)) }; let mut line = String::new(); for suffix in suffix_gen { // do a test read to see if there's any/another line before creating a new file let read = reader.read_line(&mut line)?; if read == 0 { break; } let mut out_file = File::create(format!("{}{}", prefix, suffix))?; out_file.write_all(line.as_bytes())?; for _ in 1..opts.lines { line.clear(); match reader.read_line(&mut line)? { 0 => { break; } _ => { out_file.write_all(line.as_bytes())?; } }; } line.clear(); } // see if there's anything left for the remainder file let read = reader.read_line(&mut line)?; if read > 0 { let mut out_file = File::create(format!("{}rest", prefix))?; out_file.write_all(line.as_bytes())?; for result in reader.lines() { // lines() strips newline characters, so add one out_file.write_all((result? + "\n").as_bytes())?; } } Ok(()) } fn main() { // display nicer, non-debug representation of the error if let Err(err) = try_main() { eprintln!("{}", err); std::process::exit(1); } }