From 7237ef0d975df9637e0324519a52162dba161796 Mon Sep 17 00:00:00 2001 From: Barnaby Keene Date: Tue, 8 Oct 2019 17:31:02 +0100 Subject: [PATCH] add format flag with json support A small addition that adds a --format flag with two options: `table` which is the existing functionality and `json` which formats the output as a JSON object to facilitate automation and integration with other tools. --- Cargo.lock | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +++ src/lib.rs | 7 +++-- src/main.rs | 51 +++++++++++++++++++++++++++++++++-- 4 files changed, 135 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97eb9e9..4da8271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,6 +118,11 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.4.0" @@ -139,6 +144,9 @@ dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -163,6 +171,22 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex" version = "1.3.1" @@ -179,6 +203,11 @@ name = "regex-syntax" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ryu" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "same-file" version = "1.0.5" @@ -187,6 +216,31 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "smallvec" version = "0.6.10" @@ -197,6 +251,16 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -218,6 +282,11 @@ name = "unicode-width" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -275,19 +344,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" "checksum ignore 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0ec16832258409d571aaef8273f3c3cc5b060d784e159d1a0f3b0017308f84a7" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" diff --git a/Cargo.toml b/Cargo.toml index ad8a4e8..75ce64b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ num_cpus = "1.8.0" ignore = "0.4.2" edit-distance = "2.0.1" smallvec = "0.6.5" +serde = "1.0.101" +serde_json = "1.0.41" +serde_derive = "1.0.101" [profile.release] incremental = false diff --git a/src/lib.rs b/src/lib.rs index 7e94562..bd0c0b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ extern crate memchr; extern crate smallvec; +#[macro_use] +extern crate serde_derive; use std::path::Path; use std::fs::File; @@ -11,7 +13,7 @@ use memchr::memchr; use smallvec::*; // Why is it called partialEq? -#[derive(Debug, PartialEq, Default, Clone)] +#[derive(Debug, PartialEq, Default, Copy, Clone, Serialize)] pub struct Count { pub code: u32, pub comment: u32, @@ -28,6 +30,7 @@ impl Count { } } +#[derive(Serialize, Copy, Clone)] pub struct LangTotal { pub files: u32, pub count: Count, @@ -37,7 +40,7 @@ pub struct LangTotal { // We can probably do something with the encoding crate where we decode // as ascii, and then use unsafe_from_utf8. If decoding fails, // we catch it and just use the safe from_utf8 as we're doing now. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Serialize)] pub enum Lang { ActionScript, Ada, diff --git a/src/main.rs b/src/main.rs index 912cd79..43c943a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ extern crate num_cpus; extern crate regex; extern crate ignore; extern crate edit_distance; +extern crate serde_json; use clap::{Arg, App, AppSettings}; use ignore::WalkBuilder; @@ -99,6 +100,23 @@ impl FromStr for Sort { } } +#[derive(PartialEq)] +enum Format { + Table, + JSON +} + +impl FromStr for Format { + type Err = Option; + fn from_str(s: &str) -> Result { + match s { + "table" | "Table" => Ok(Format::Table), + "json" | "Json" | "JSON" => Ok(Format::JSON), + _ => Err(None) + } + } +} + // TODO(cgag): tune smallvec array sizes // TODO(cgag): try smallstring // TODO(cgag): more tests for nested comments @@ -133,6 +151,12 @@ fn main() { .value_name("COLUMN") .takes_value(true) .help("Column to sort by")) + .arg(Arg::with_name("format") + .required(false) + .long("format") + .short("f") + .takes_value(true) + .help("The output format, defaults to 'table' and accepts 'json'.")) .arg(Arg::with_name("unrestricted") .required(false) .multiple(true) @@ -169,6 +193,18 @@ fn main() { None => Sort::Code, }; + let format = match matches.value_of("format") { + Some(string) => match Format::from_str(string) { + Ok(format) => format, + Err(_) => { + println!("Error: invalid value for --format: '{}'", string); + println!(" Hint: legal values are Table and Json"); + return + } + }, + None => Format::Table, + }; + let by_file: bool = matches.is_present("files"); if by_file && (sort == Sort::Language || sort == Sort::Files) { @@ -339,7 +375,10 @@ fn main() { Sort::Lines => totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.lines.cmp(&c1.count.lines)), } - print_totals_by_lang(&linesep, &totals_by_lang); + match format { + Format::Table => print_totals_by_lang_table(&linesep, &totals_by_lang), + Format::JSON => print_totals_by_lang_json(&totals_by_lang), + } } } @@ -357,7 +396,15 @@ fn str_repeat(s: &str, n: usize) -> String { std::iter::repeat(s).take(n).collect::>().join("") } -fn print_totals_by_lang(linesep: &str, totals_by_lang: &[(&&Lang, &LangTotal)]) { +fn print_totals_by_lang_json(totals_by_lang: &[(&&Lang, &LangTotal)]) { + let mut langs = std::collections::HashMap::new(); + for (l, t) in totals_by_lang { + langs.insert((***l).to_string(), **t); + } + println!("{}", serde_json::to_string(&langs).unwrap()); +} + +fn print_totals_by_lang_table(linesep: &str, totals_by_lang: &[(&&Lang, &LangTotal)]) { println!("{}", linesep); println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}", "Language",