Skip to content

Commit

Permalink
Allow smart merging via expansion (#8)
Browse files Browse the repository at this point in the history
To allow merging arrays and objects via expansion, trailing commas after `s`
and `q` will be auto removed after the expansion if no value is passed for the
placeholder.

Example:

```bash
jf "[%(a)*s, %(b)*s]" b=2 b=1

jf "{%(a)**s, %(b)**s}" b=2 b=1
```
  • Loading branch information
sayanarijit authored May 27, 2023
1 parent b7fb344 commit 6e85ea9
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jf"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
authors = ["Arijit Basu <[email protected]>"]
description = 'A small utility to safely format and print JSON objects in the commandline'
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ And [VALUE]... [NAME=VALUE]... [NAME@FILE]... are the values for the placeholder
- Pass values for named key value pairs using `NAME=KEY_N NAME=VALUE_N` syntax.
- Use `NAME@FILE` syntax to read from file where FILE can be `-` for stdin.
- Do not declare or pass positional placeholders or values after named ones.
- Expandable positional placeholder should be the last placeholder in a template.
- To allow merging arrays and objects via expansion, trailing comma after `s` and `q`
will be auto removed after the expansion if no value is passed for the placeholder.

### EXAMPLES

Expand Down
3 changes: 2 additions & 1 deletion assets/jf.1
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ Use `NAME@FILE` syntax to read from file where FILE can be `-` for stdin.
.IP \(bu 3
Do not declare or pass positional placeholders or values after named ones.
.IP \(bu 3
Expandable positional placeholder should be the last placeholder in a template.
To allow merging arrays and objects via expansion, trailing comma after `s` and `q`
will be auto removed after the expansion if no value is passed for the placeholder.
.SH EXAMPLES

.IP \(bu 3
Expand Down
43 changes: 26 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fn read_named_placeholder<C, S>(
chars: &mut C,
named_values: &HashMap<String, Vec<String>>,
stdin: &mut S,
) -> Result<(), Error>
) -> Result<bool, Error>
where
C: Iterator<Item = (usize, char)>,
S: Iterator<Item = (usize, io::Result<Vec<u8>>)>,
Expand All @@ -138,6 +138,7 @@ where
let mut is_nullable = false;
let mut expand_items = false;
let mut expand_pairs = false;
let mut empty_expansion = false;

loop {
let Some((col, ch)) = chars.next() else {
Expand Down Expand Up @@ -232,11 +233,11 @@ where
.enumerate();

if expand_pairs {
read_positional_pairs_placeholder(
empty_expansion = !read_positional_pairs_placeholder(
val, ch, col, false, &mut args, stdin,
)?;
} else if expand_items {
read_positional_items_placeholder(
empty_expansion = !read_positional_items_placeholder(
val, ch, col, false, &mut args, stdin,
)?;
} else {
Expand Down Expand Up @@ -272,7 +273,7 @@ where
}
}

Ok(())
Ok(empty_expansion)
}

fn collect_named_values<'a, A, S>(
Expand Down Expand Up @@ -367,15 +368,15 @@ fn read_positional_items_placeholder<'a, A, S>(
is_stdin: bool,
args: &mut A,
stdin: &mut S,
) -> Result<(), Error>
) -> Result<bool, Error>
where
A: Iterator<Item = (usize, Cow<'a, str>)>,
S: Iterator<Item = (usize, io::Result<Vec<u8>>)>,
{
let mut is_empty = true;
let mut was_expanded = false;

while let Ok((_, arg)) = read(is_stdin, col, args, stdin) {
is_empty = false;
was_expanded = true;
if ch == 'q' {
val.push_str(&json::to_string(&arg)?);
} else {
Expand All @@ -384,10 +385,10 @@ where
val.push(',');
}

if !is_empty {
if was_expanded {
val.pop();
}
Ok(())
Ok(was_expanded)
}

fn read_positional_pairs_placeholder<'a, A, S>(
Expand All @@ -397,15 +398,15 @@ fn read_positional_pairs_placeholder<'a, A, S>(
is_stdin: bool,
args: &mut A,
stdin: &mut S,
) -> Result<(), Error>
) -> Result<bool, Error>
where
A: Iterator<Item = (usize, Cow<'a, str>)>,
S: Iterator<Item = (usize, io::Result<Vec<u8>>)>,
{
let mut is_reading_key = true;
let mut is_empty = true;
let mut was_expanded = false;
while let Ok((_, arg)) = read(is_stdin, col, args, stdin) {
is_empty = false;
was_expanded = true;
let arg = if is_reading_key || ch == 'q' {
json::to_string(&arg)?
} else {
Expand All @@ -429,10 +430,10 @@ where
.into());
}

if !is_empty {
if was_expanded {
val.pop();
}
Ok(())
Ok(was_expanded)
}

pub fn format_partial<'a, C, A, S>(
Expand All @@ -452,6 +453,7 @@ where
let mut expand_items = false;
let mut expand_pairs = false;
let mut is_stdin = false;
let mut empty_expansion = false;

while let Some((col, ch)) = chars.next() {
// Reading a named placeholder
Expand All @@ -473,7 +475,8 @@ where
is_reading_named_values = true;
collect_named_values(args, stdin, &mut named_values)?;
};
read_named_placeholder(&mut val, chars, &named_values, stdin)?;
empty_expansion =
read_named_placeholder(&mut val, chars, &named_values, stdin)?;
last_char = None;
}
('*', Some('%')) if !expand_items && !expand_pairs => {
Expand All @@ -489,6 +492,10 @@ where
is_stdin = true;
last_char = Some('%');
}
(',', None) if empty_expansion => {
empty_expansion = false;
last_char = None;
}
(ch, Some('%')) if ch == 's' || ch == 'q' => {
if is_reading_named_values {
return Err(
Expand All @@ -499,19 +506,20 @@ where
};

if expand_items {
read_positional_items_placeholder(
empty_expansion = !read_positional_items_placeholder(
&mut val, ch, col, is_stdin, args, stdin,
)?;
expand_items = false;
} else if expand_pairs {
read_positional_pairs_placeholder(
empty_expansion = !read_positional_pairs_placeholder(
&mut val, ch, col, is_stdin, args, stdin,
)?;
expand_pairs = false;
} else {
read_positional_placeholder(
&mut val, ch, col, is_stdin, args, stdin,
)?;
empty_expansion = false;
}
is_stdin = false;
last_char = None;
Expand All @@ -532,6 +540,7 @@ where
expand_items = false;
expand_pairs = false;
is_stdin = false;
empty_expansion = false;
}
}
}
Expand Down
34 changes: 32 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,36 @@ fn test_format_expand_pairs_from_stdin() {
);
}

#[test]
fn test_format_merge_arrays() {
let args = ["[%(a)*s, %(b)*s]"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), "[]");

let args = ["[%(a)*s, %(b)*s]", "a=1", "a=2"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), "[1,2]");

let args = ["[%(a)*s, %(b)*s]", "b=1", "b=2"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), "[1,2]");

let args = ["[%(a)*s, %(b)*s]", "a=1", "b=2", "a=3", "b=4"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), "[1,3,2,4]");
}

#[test]
fn test_format_merge_objs() {
let args = ["{%(a)*s, %(b)*s}"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), "{}");

let args = ["{%(a)**s, %(b)**s}", "a=1", "a=2"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), r#"{"1":2}"#);

let args = ["{%(a)**s, %(b)**s}", "b=1", "b=2"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), r#"{"1":2}"#);

let args = ["{%(a)**s, %(b)**s}", "a=1", "b=2", "a=3", "b=4"].map(Into::into);
assert_eq!(jf::format(args).unwrap(), r#"{"1":3,"2":4}"#);
}

#[test]
fn test_format_named() {
let args = [
Expand Down Expand Up @@ -646,14 +676,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.0""#);
assert_eq!(jf::format(arg).unwrap(), r#""jf v0.4.1""#);

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.0"}"#
r#"{"foo":"foo","bar":"bar","version":"0.4.1"}"#
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/usage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ RULES
* Pass values for named key value pairs using `NAME=KEY_N NAME=VALUE_N` syntax.
* Use `NAME@FILE` syntax to read from file where FILE can be `-` for stdin.
* Do not declare or pass positional placeholders or values after named ones.
* Expandable positional placeholder should be the last placeholder in a template.
* To allow merging arrays and objects via expansion, trailing comma after `s` and `q`
will be auto removed after the expansion if no value is passed for the placeholder.

EXAMPLES

Expand Down

0 comments on commit 6e85ea9

Please sign in to comment.