summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDimagog <dima_kakurin@hotmail.com>2018-08-22 15:23:04 -0700
committerAndrew Gallant <jamslam@gmail.com>2018-08-22 18:23:04 -0400
commit72db9ed39fa200fd86f41e356e23a10cff7b0366 (patch)
tree18277581352b7504337d3804f45f74121da7e4ec
parent7e572e92d568b4d7f4bdfc06eb1d0acbe43ac996 (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.md1
-rw-r--r--src/cmd/mod.rs1
-rw-r--r--src/cmd/reverse.rs52
-rw-r--r--src/main.rs3
-rw-r--r--tests/test_reverse.rs39
-rw-r--r--tests/tests.rs1
6 files changed, 97 insertions, 0 deletions
diff --git a/README.md b/README.md
index ca0264b..faf0e5f 100644
--- a/README.md
+++ b/README.md
@@ -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;