diff options
author | Dimagog <dima_kakurin@hotmail.com> | 2018-08-22 15:23:04 -0700 |
---|---|---|
committer | Andrew Gallant <jamslam@gmail.com> | 2018-08-22 18:23:04 -0400 |
commit | 72db9ed39fa200fd86f41e356e23a10cff7b0366 (patch) | |
tree | 18277581352b7504337d3804f45f74121da7e4ec | |
parent | 7e572e92d568b4d7f4bdfc06eb1d0acbe43ac996 (diff) |
xsv: add reverse command
reverse command to reverse rows of CSV data.
Unlike sort command, it preserves order of rows with the same key.
PR #134
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | src/cmd/mod.rs | 1 | ||||
-rw-r--r-- | src/cmd/reverse.rs | 52 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | tests/test_reverse.rs | 39 | ||||
-rw-r--r-- | tests/tests.rs | 1 |
6 files changed, 97 insertions, 0 deletions
@@ -38,6 +38,7 @@ Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org). * **partition** - Partition CSV data based on a column value. * **sample** - Randomly draw rows from CSV data using reservoir sampling (i.e., use memory proportional to the size of the sample). +* **reverse** - Reverse order of rows in CSV data. * **search** - Run a regex over CSV data. Applies the regex to each field individually and shows only matching rows. * **select** - Select or re-order columns from CSV data. diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index a3fb63a..921ad00 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -9,6 +9,7 @@ pub mod index; pub mod input; pub mod join; pub mod partition; +pub mod reverse; pub mod sample; pub mod search; pub mod select; diff --git a/src/cmd/reverse.rs b/src/cmd/reverse.rs new file mode 100644 index 0000000..a04ff3a --- /dev/null +++ b/src/cmd/reverse.rs @@ -0,0 +1,52 @@ +use CliResult; +use config::{Config, Delimiter}; +use util; + +static USAGE: &'static str = " +Reverses rows of CSV data. + +Useful for cases when there is no column that can be used for sorting in reverse order, +or when keys are not unique and order of rows with the same key needs to be preserved. + +Note that this requires reading all of the CSV data into memory. + +Usage: + xsv reverse [options] [<input>] + +Common options: + -h, --help Display this message + -o, --output <file> Write output to <file> instead of stdout. + -n, --no-headers When set, the first row will not be interpreted + as headers. Namely, it will be reversed with the rest + of the rows. Otherwise, the first row will always + appear as the header row in the output. + -d, --delimiter <arg> The field delimiter for reading CSV data. + Must be a single character. (default: ,) +"; + +#[derive(Deserialize)] +struct Args { + arg_input: Option<String>, + flag_output: Option<String>, + flag_no_headers: bool, + flag_delimiter: Option<Delimiter>, +} + +pub fn run(argv: &[&str]) -> CliResult<()> { + let args: Args = util::get_args(USAGE, argv)?; + let rconfig = Config::new(&args.arg_input) + .delimiter(args.flag_delimiter) + .no_headers(args.flag_no_headers); + + let mut rdr = rconfig.reader()?; + + let mut all = rdr.byte_records().collect::<Result<Vec<_>, _>>()?; + all.reverse(); + + let mut wtr = Config::new(&args.flag_output).writer()?; + rconfig.write_headers(&mut rdr, &mut wtr)?; + for r in all.into_iter() { + wtr.write_byte_record(&r)?; + } + Ok(wtr.flush()?) +} diff --git a/src/main.rs b/src/main.rs index 31c9958..c1465a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,7 @@ macro_rules! command_list { join Join CSV files partition Partition CSV data based on a column value sample Randomly sample CSV data + reverse Reverse rows of CSV data search Search CSV data with regexes select Select columns from CSV slice Slice records from CSV @@ -150,6 +151,7 @@ enum Command { Input, Join, Partition, + Reverse, Sample, Search, Select, @@ -178,6 +180,7 @@ impl Command { Command::Input => cmd::input::run(argv), Command::Join => cmd::join::run(argv), Command::Partition => cmd::partition::run(argv), + Command::Reverse => cmd::reverse::run(argv), Command::Sample => cmd::sample::run(argv), Command::Search => cmd::search::run(argv), Command::Select => cmd::select::run(argv), diff --git a/tests/test_reverse.rs b/tests/test_reverse.rs new file mode 100644 index 0000000..c0bce3f --- /dev/null +++ b/tests/test_reverse.rs @@ -0,0 +1,39 @@ +use workdir::Workdir; + +use {Csv, CsvData, qcheck}; + +fn prop_reverse(name: &str, rows: CsvData, headers: bool) -> bool { + let wrk = Workdir::new(name); + wrk.create("in.csv", rows.clone()); + + let mut cmd = wrk.command("reverse"); + cmd.arg("in.csv"); + if !headers { cmd.arg("--no-headers"); } + + let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd); + let mut expected = rows.to_vecs(); + let headers = if headers && !expected.is_empty() { + expected.remove(0) + } else { + vec![] + }; + expected.reverse(); + if !headers.is_empty() { expected.insert(0, headers); } + rassert_eq!(got, expected) +} + +#[test] +fn prop_reverse_headers() { + fn p(rows: CsvData) -> bool { + prop_reverse("prop_reverse_headers", rows, true) + } + qcheck(p as fn(CsvData) -> bool); +} + +#[test] +fn prop_reverse_no_headers() { + fn p(rows: CsvData) -> bool { + prop_reverse("prop_reverse_no_headers", rows, false) + } + qcheck(p as fn(CsvData) -> bool); +} diff --git a/tests/tests.rs b/tests/tests.rs index afd2832..db2f3b3 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -43,6 +43,7 @@ mod test_headers; mod test_index; mod test_join; mod test_partition; +mod test_reverse; mod test_search; mod test_select; mod test_slice; |