diff --git a/.gitignore b/.gitignore index 02866f6..0049502 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ *.iml # test data -*.json \ No newline at end of file +*.json + +# local install script +install.sh \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a62a213..35e42b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,253 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "clap" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "jsonformat" version = "0.1.0" +dependencies = [ + "clap", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" + +[[package]] +name = "os_str_bytes" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +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 = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 155214b..35961f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ description = "Reads raw json from stdin and formats it to stdout" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = "3.0.0-beta.2" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9f12788..614bd44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,14 @@ -pub fn format_json(json: &str, indent_str: &str) -> String { - let mut out = String::with_capacity(json.len()); // at least as big as the input +/// +/// # Formats a json string +/// +/// The indentation can be set to any custom value +/// The default value is two spaces +/// The default indentation is faster than a custom one +/// +pub fn format_json(json: &str, indentation: Option<&str>) -> String { + // at least as big as the input to avoid resizing + // this might be too big if the input string is formatted in a weird way, but that's not expected + let mut out = String::with_capacity(json.len()); let mut escaped = false; let mut in_string = false; @@ -42,9 +51,9 @@ pub fn format_json(json: &str, indent_str: &str) -> String { '}' | ']' => { indent_level -= 1; if !newline_requested { - // see comment above + // see comment below about newline_requested out.push('\n'); - indent(&mut out, indent_level, indent_str); + indent(&mut out, indent_level, indentation); } } ':' => { @@ -59,8 +68,10 @@ pub fn format_json(json: &str, indent_str: &str) -> String { } if newline_requested && char != ']' && char != '}' { // newline only happens after { [ and , + // this means we can safely assume that it being followed up by } or ] + // means an empty object/array out.push('\n'); - indent(&mut out, old_level, indent_str); + indent(&mut out, old_level, indentation); } if auto_push { @@ -74,76 +85,83 @@ pub fn format_json(json: &str, indent_str: &str) -> String { out } -fn indent(buf: &mut String, level: usize, indent_str: &str) { +fn indent(buf: &mut String, level: usize, indent_str: Option<&str>) { for _ in 0..level { - buf.push_str(indent_str); + match indent_str { + None => { + buf.push(' '); + buf.push(' '); + } + Some(indent) => { + buf.push_str(indent); + } + } } } #[cfg(test)] mod test { - const INDENT: &str = " "; use super::*; #[test] fn echoes_primitive() { let json = "1.35"; - assert_eq!(json, format_json(json, INDENT)); + assert_eq!(json, format_json(json, None)); } #[test] fn ignore_whitespace_in_string() { let json = "\" hallo \""; - assert_eq!(json, format_json(json, INDENT)); + assert_eq!(json, format_json(json, None)); } #[test] fn remove_leading_whitespace() { let json = " 0"; let expected = "0"; - assert_eq!(expected, format_json(json, INDENT)); + assert_eq!(expected, format_json(json, None)); } #[test] fn handle_escaped_strings() { let json = " \" hallo \\\" \" "; let expected = "\" hallo \\\" \""; - assert_eq!(expected, format_json(json, INDENT)); + assert_eq!(expected, format_json(json, None)); } #[test] fn simple_object() { let json = "{\"a\":0}"; let expected = "{ - \"a\": 0 + \"a\": 0 }"; - assert_eq!(expected, format_json(json, INDENT)); + assert_eq!(expected, format_json(json, None)); } #[test] fn simple_array() { let json = "[1,2,null]"; let expected = "[ - 1, - 2, - null + 1, + 2, + null ]"; - assert_eq!(expected, format_json(json, INDENT)); + assert_eq!(expected, format_json(json, None)); } #[test] fn array_of_object() { let json = "[{\"a\": 0}, {}, {\"a\": null}]"; let expected = "[ - { - \"a\": 0 - }, - {}, - { - \"a\": null - } + { + \"a\": 0 + }, + {}, + { + \"a\": null + } ]"; - assert_eq!(expected, format_json(json, INDENT)); + assert_eq!(expected, format_json(json, None)); } } diff --git a/src/main.rs b/src/main.rs index bbbf69b..d3ff159 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,21 @@ +use clap::clap_app; use jsonformat::format_json; use std::fs; use std::io; use std::io::Read; fn main() -> Result<(), io::Error> { - let filename = std::env::args().skip(1).next(); + let matches = clap_app!(jsonformat => + (version: "1.0") + (author: "nilstrieb ") + (about: "Formats json") + (@arg indentation: -i --indent +takes_value "Set the indentation used (\\s for space, \\t for tab)") + (@arg output: -o --output +takes_value "The output file for the formatted json") + (@arg input: "The input file to format") + ) + .get_matches(); - let str = match filename { + let str = match matches.value_of("input") { Some(path) => fs::read_to_string(path)?, None => { let mut buf = String::new(); @@ -16,7 +25,18 @@ fn main() -> Result<(), io::Error> { } }; - println!("{}", format_json(&str, " ")); + let replaced_indent = matches + .value_of("indentation") + .map(|value| value.replace("s", " ").replace("t", "\t")); + + let formatted = format_json(&str, replaced_indent.as_deref()); + + match matches.value_of("output") { + Some(file) => { + fs::write(file, formatted)?; + } + None => println!("{}", formatted), + } Ok(()) }