From cbbd746dcc6f9a9950ca4c60cec2aeb892b96820 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sat, 27 May 2023 22:21:26 +0530 Subject: [PATCH] Add support for control placeholders (#9) - `%R` enable raw mode - render but do not format output - `%Y` enable YAML mode - format output into YAML - `%J` enable pretty JSON mode - format output into pretty JSON Example: ```bash jf "%R%*q" a b c "a","b","c" jf "%Y[%*q]" a b c - a - b - c jf "%J[%*q]" a b c [ "a", "b", "c" ] ``` --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 3 +++ assets/jf.1 | 12 ++++++++++++ src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 3 --- src/tests.rs | 43 ++++++++++++++++++++++++++++++++++--------- src/usage.txt | 3 +++ 8 files changed, 94 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3745c03..c7103b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,7 +32,7 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jf" -version = "0.4.1" +version = "0.4.2" dependencies = [ "serde_json", "serde_yaml", diff --git a/Cargo.toml b/Cargo.toml index 8ed103e..78e1d01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jf" -version = "0.4.1" +version = "0.4.2" edition = "2021" authors = ["Arijit Basu "] description = 'A small utility to safely format and print JSON objects in the commandline' diff --git a/README.md b/README.md index 248a833..94c5236 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ Where TEMPLATE may contain the following placeholders: - `%s` JSON values other than string - `%v` the `jf` version number - `%%` a literal `%` character +- `%R` enable raw mode - render but do not format output +- `%Y` enable pretty YAML mode - format output into pretty YAML +- `%J` enable pretty JSON mode - format output into pretty JSON And [VALUE]... [NAME=VALUE]... [NAME@FILE]... are the values for the placeholders. diff --git a/assets/jf.1 b/assets/jf.1 index 29cd852..678c678 100644 --- a/assets/jf.1 +++ b/assets/jf.1 @@ -21,6 +21,18 @@ the `jf` version number .B `%%` a literal `%` character +.TP +.B +`%R` +enable raw mode - render but do not format output +.TP +.B +`%Y` +enable pretty YAML mode - format output into pretty YAML +.TP +.B +`%J` +enable pretty JSON mode - format output into pretty JSON .PP And [VALUE]\.\.\. [NAME=VALUE]\.\.\. [NAME@FILE]\.\.\. are the values for the placeholders. .SH SYNTAX diff --git a/src/lib.rs b/src/lib.rs index 1a71e68..9cc5f9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -436,16 +436,25 @@ where Ok(was_expanded) } -pub fn format_partial<'a, C, A, S>( +#[derive(Debug)] +enum Mode { + Json, + Raw, + Yaml, + PrettyJson, +} + +fn format_partial<'a, C, A, S>( chars: &mut C, args: &mut A, stdin: &mut S, -) -> Result<(String, Option), Error> +) -> Result<(String, Option, Mode), Error> where C: Iterator, A: Iterator)>, S: Iterator>)>, { + let mut mode = Mode::Json; let mut val = "".to_string(); let mut last_char = None; let mut is_reading_named_values = false; @@ -467,6 +476,18 @@ where val.push_str(VERSION); last_char = None; } + ('R', Some('%')) => { + mode = Mode::Raw; + last_char = None; + } + ('Y', Some('%')) => { + mode = Mode::Yaml; + last_char = None; + } + ('J', Some('%')) => { + mode = Mode::PrettyJson; + last_char = None; + } ('%', _) => { last_char = Some(ch); } @@ -545,7 +566,7 @@ where } } - Ok((val, last_char)) + Ok((val, last_char, mode)) } /// Format a string using the given arguments. @@ -561,7 +582,7 @@ where let mut chars = format.chars().enumerate(); let mut stdin = io::stdin().lock().split(b'\0').enumerate(); - let (val, last_char) = format_partial(&mut chars, &mut args, &mut stdin)?; + let (val, last_char, mode) = format_partial(&mut chars, &mut args, &mut stdin)?; if last_char == Some('%') { return Err("template ended with incomplete placeholder".into()); @@ -573,6 +594,19 @@ where ); }; - let val: yaml::Value = yaml::from_str(&val).map_err(Error::from)?; - json::to_string(&val).map_err(Error::from) + match mode { + Mode::Raw => Ok(val), + Mode::Yaml => yaml::from_str::(&val) + .and_then(|y| yaml::to_string(&y)) + .map_err(Error::from), + Mode::Json => yaml::from_str::(&val) + .map_err(Error::from) + .and_then(|y| json::to_string(&y).map_err(Error::from)), + Mode::PrettyJson => yaml::from_str::(&val) + .map_err(Error::from) + .and_then(|y| json::to_string_pretty(&y).map_err(Error::from)), + } } + +#[cfg(test)] +mod tests; diff --git a/src/main.rs b/src/main.rs index d4b7938..a80e8e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod tests; - fn main() { let args = std::env::args().skip(1).map(Into::into); diff --git a/src/tests.rs b/src/tests.rs index d63540e..362c4ed 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,3 +1,4 @@ +use crate as jf; use serde_json as json; use std::borrow::Cow; use std::io; @@ -36,7 +37,7 @@ fn test_format_from_stdin() { .into_iter() .enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!(res, r#"{"one": 1, "two": 2, "three": 3}"#); let mut chars = @@ -52,7 +53,7 @@ fn test_format_from_stdin() { let mut args = ["1", "true", "bar"].map(Cow::from).into_iter().enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!( res, r#"{"1": 1, one: "1", "true": true, truestr: "true", foo: foo, bar: "bar", esc: "%"}"# @@ -71,7 +72,7 @@ fn test_format_expand_items_from_stdin() { let mut args = ["2", "false", "bar"].map(Cow::from).into_iter().enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!(res, r#"[start, 1,true,foo, mid, 2,false,bar, end]"#); } @@ -87,7 +88,7 @@ fn test_format_expand_pairs_from_stdin() { let mut args = ["three", "3"].map(Cow::from).into_iter().enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!( res, r#"{args: {"three":"3"}, stdin: {"one":"1","two":"2"}}"# @@ -177,7 +178,7 @@ fn test_format_named_from_stdin() { .enumerate(); let mut args = ["FOO@-", "BAR@-"].map(Cow::from).into_iter().enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!(res, r#"{"foo": "bar"}"#); } @@ -217,7 +218,7 @@ fn test_format_named_with_default_from_stdin() { .into_iter() .enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!(res, r#""foo""#); let mut chars = "%(foo@-)q".chars().enumerate(); @@ -228,7 +229,7 @@ fn test_format_named_with_default_from_stdin() { .map(io::Result::Ok) .into_iter() .enumerate(); - let (res, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); + let (res, _, _) = jf::format_partial(&mut chars, &mut args, &mut stdin).unwrap(); assert_eq!(res, r#""bar""#); } @@ -332,6 +333,30 @@ fn test_format_named_pairs() { ); } +#[test] +fn test_raw_mode() { + let args = ["%R%*s", "1", "2", "3"].map(Into::into); + assert_eq!(jf::format(args).unwrap(), "1,2,3"); + + let args = ["%R%s %q, (%s)", "1", "2", "3"].map(Into::into); + assert_eq!(jf::format(args).unwrap(), r#"1 "2", (3)"#); +} + +#[test] +fn test_yaml_mode() { + let args = ["%Y{a: b, c: d, e: [f, g]}"].map(Into::into); + assert_eq!(jf::format(args).unwrap(), "a: b\nc: d\ne:\n- f\n- g\n"); +} + +#[test] +fn test_pretty_json_mode() { + let args = ["%J{a: b, c: d, e: [f, g]}"].map(Into::into); + assert_eq!( + jf::format(args).unwrap(), + "{\n \"a\": \"b\",\n \"c\": \"d\",\n \"e\": [\n \"f\",\n \"g\"\n ]\n}" + ); +} + #[test] fn test_optional_placeholder_with_default_value_error() { let args = [r#"%(foo=bar)?q"#].map(Into::into); @@ -676,14 +701,14 @@ fn test_usage_example() { #[test] fn test_print_version() { let arg = ["jf v%v"].map(Into::into); - assert_eq!(jf::format(arg).unwrap(), r#""jf v0.4.1""#); + assert_eq!(jf::format(arg).unwrap(), r#""jf v0.4.2""#); let args = ["{foo: %q, bar: %(bar)q, version: %v}", "foo", "bar=bar"].map(Into::into); assert_eq!( jf::format(args).unwrap(), - r#"{"foo":"foo","bar":"bar","version":"0.4.1"}"# + r#"{"foo":"foo","bar":"bar","version":"0.4.2"}"# ); } diff --git a/src/usage.txt b/src/usage.txt index 628bb41..7141454 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -8,6 +8,9 @@ USAGE `%s` JSON values other than string `%v` the `jf` version number `%%` a literal `%` character + `%R` enable raw mode - render but do not format output + `%Y` enable pretty YAML mode - format output into pretty YAML + `%J` enable pretty JSON mode - format output into pretty JSON And [VALUE]... [NAME=VALUE]... [NAME@FILE]... are the values for the placeholders.