commit 94208e39fcc67b43f0befa26808eef091a3e27bf Author: Mike Cugini Date: Mon Mar 15 23:56:58 2021 -0400 initial commit with base feature set diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..749a1dd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,87 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "argh" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91792f088f87cdc7a2cfb1d617fa5ea18d7f1dc22ef0e1b5f82f3157cdc522be" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4eb0c0c120ad477412dc95a4ce31e38f2113e46bd13511253f79196ca68b067" +dependencies = [ + "argh_shared", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781f336cc9826dbaddb9754cb5db61e64cab4f69668bd19dcc4a0394a86f4cb1" + +[[package]] +name = "chop" +version = "0.1.0" +dependencies = [ + "argh", +] + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1071d05 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "chop" +version = "0.1.0" +authors = ["Mike Cugini "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argh = "0.1.4" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..deaf60a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,141 @@ +use std::error::Error; +use std::fs::File; +use std::io::{self, BufReader}; +use std::io::prelude::*; + +use argh::FromArgs; + +#[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 0, not alphabetic + #[argh(option, long = "numeric-start", default = "0")] + numeric_start: usize, + + /// filename to read from, or "-" for stdin (default "-") + #[argh(positional, default = "String::from(\"-\")")] + filename: String, + + +} + +struct AlphabeticSuffixGenerator { + suffix: Vec, + remaining: usize, +} + +impl AlphabeticSuffixGenerator { + fn new(count: usize) -> AlphabeticSuffixGenerator { + // given 27 letters, minimum number of digits is floor( Log 27 (count) + 1 ) + let suffix_length = ((count as f64).log(27f64) + 1.0).floor() as usize; + + let mut suffix = vec![b'a'; suffix_length]; + suffix[suffix_length-1] -= 1; + + AlphabeticSuffixGenerator{suffix, remaining: count} + } +} + +impl Iterator for AlphabeticSuffixGenerator { + type Item = String; + + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None + } + for idx in (0..self.suffix.len()).rev() { + if self.suffix[idx] < b'z' { + self.suffix[idx] += 1; + break + } else { + self.suffix[idx] = b'a'; + } + } + self.remaining -= 1; + Some(String::from_utf8(self.suffix.to_vec()).expect("invalid suffix generated")) + } +} + + +fn try_main() -> Result<(), Box> { + let opts: Options = argh::from_env(); + + 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 prefix = match opts.prefix.as_str() { + "" => String::from(""), + _ => opts.prefix + "_", + }; + + let suffix_gen: Box> = if opts.numeric { + Box::new((opts.numeric_start..(opts.numeric_start + opts.count)).map(|num| format!("{}", num))) + } else { + Box::new(AlphabeticSuffixGenerator::new(opts.count)) + }; + + let mut eof = false; + let mut reader = BufReader::new(input); + for suffix in suffix_gen { + let mut out_file = File::create(format!("{}{}", prefix, suffix))?; + let mut line = String::new(); + for _ in 0..opts.lines { + match reader.read_line(&mut line)? { + 0 => { + eof = true; + break + }, + _ => { + out_file.write_all(line.as_bytes())?; + line.clear(); + } + }; + } + } + // see if there's anything left for the remainder file + if !eof { + let mut out_file = File::create(format!("{}rest", prefix))?; + 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); + } +}