This commit is contained in:
nora 2021-07-17 22:04:54 +02:00
parent d0733b1a7c
commit 9b7260660e
5 changed files with 320 additions and 30 deletions

5
.gitignore vendored
View file

@ -3,4 +3,7 @@
*.iml *.iml
# test data # test data
*.json *.json
# local install script
install.sh

248
Cargo.lock generated
View file

@ -1,5 +1,253 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # 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]] [[package]]
name = "jsonformat" name = "jsonformat"
version = "0.1.0" 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"

View file

@ -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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = "3.0.0-beta.2"

View file

@ -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 escaped = false;
let mut in_string = false; let mut in_string = false;
@ -42,9 +51,9 @@ pub fn format_json(json: &str, indent_str: &str) -> String {
'}' | ']' => { '}' | ']' => {
indent_level -= 1; indent_level -= 1;
if !newline_requested { if !newline_requested {
// see comment above // see comment below about newline_requested
out.push('\n'); 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 != '}' { if newline_requested && char != ']' && char != '}' {
// newline only happens after { [ and , // 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'); out.push('\n');
indent(&mut out, old_level, indent_str); indent(&mut out, old_level, indentation);
} }
if auto_push { if auto_push {
@ -74,76 +85,83 @@ pub fn format_json(json: &str, indent_str: &str) -> String {
out 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 { 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)] #[cfg(test)]
mod test { mod test {
const INDENT: &str = " ";
use super::*; use super::*;
#[test] #[test]
fn echoes_primitive() { fn echoes_primitive() {
let json = "1.35"; let json = "1.35";
assert_eq!(json, format_json(json, INDENT)); assert_eq!(json, format_json(json, None));
} }
#[test] #[test]
fn ignore_whitespace_in_string() { fn ignore_whitespace_in_string() {
let json = "\" hallo \""; let json = "\" hallo \"";
assert_eq!(json, format_json(json, INDENT)); assert_eq!(json, format_json(json, None));
} }
#[test] #[test]
fn remove_leading_whitespace() { fn remove_leading_whitespace() {
let json = " 0"; let json = " 0";
let expected = "0"; let expected = "0";
assert_eq!(expected, format_json(json, INDENT)); assert_eq!(expected, format_json(json, None));
} }
#[test] #[test]
fn handle_escaped_strings() { fn handle_escaped_strings() {
let json = " \" hallo \\\" \" "; let json = " \" hallo \\\" \" ";
let expected = "\" hallo \\\" \""; let expected = "\" hallo \\\" \"";
assert_eq!(expected, format_json(json, INDENT)); assert_eq!(expected, format_json(json, None));
} }
#[test] #[test]
fn simple_object() { fn simple_object() {
let json = "{\"a\":0}"; let json = "{\"a\":0}";
let expected = "{ let expected = "{
\"a\": 0 \"a\": 0
}"; }";
assert_eq!(expected, format_json(json, INDENT)); assert_eq!(expected, format_json(json, None));
} }
#[test] #[test]
fn simple_array() { fn simple_array() {
let json = "[1,2,null]"; let json = "[1,2,null]";
let expected = "[ let expected = "[
1, 1,
2, 2,
null null
]"; ]";
assert_eq!(expected, format_json(json, INDENT)); assert_eq!(expected, format_json(json, None));
} }
#[test] #[test]
fn array_of_object() { fn array_of_object() {
let json = "[{\"a\": 0}, {}, {\"a\": null}]"; let json = "[{\"a\": 0}, {}, {\"a\": null}]";
let expected = "[ let expected = "[
{ {
\"a\": 0 \"a\": 0
}, },
{}, {},
{ {
\"a\": null \"a\": null
} }
]"; ]";
assert_eq!(expected, format_json(json, INDENT)); assert_eq!(expected, format_json(json, None));
} }
} }

View file

@ -1,12 +1,21 @@
use clap::clap_app;
use jsonformat::format_json; use jsonformat::format_json;
use std::fs; use std::fs;
use std::io; use std::io;
use std::io::Read; use std::io::Read;
fn main() -> Result<(), io::Error> { fn main() -> Result<(), io::Error> {
let filename = std::env::args().skip(1).next(); let matches = clap_app!(jsonformat =>
(version: "1.0")
(author: "nilstrieb <nilstrieb@gmail.com>")
(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)?, Some(path) => fs::read_to_string(path)?,
None => { None => {
let mut buf = String::new(); 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(()) Ok(())
} }