Skip to content

Commit

Permalink
Add support for nullable placeholder (#6)
Browse files Browse the repository at this point in the history
* Minor cleanups

* Add support for nullable placeholder

- Use `%(NAME?)q`/`%(NAME?)s` syntax to define nullable placeholder.
- As opposed to optional placeholders that defaults to blank, nullable
  placeholders will default to `null`.
- Useful for defining nullable string or array items.
  • Loading branch information
sayanarijit committed May 24, 2023
1 parent d725fad commit e144c97
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 46 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.3.1"
version = "0.3.2"
edition = "2021"
authors = ["Arijit Basu <hi@arijitbasu.in>"]
description = 'A small utility to safely format and print JSON objects in the commandline'
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ And [VALUE]... [NAME=VALUE]... are the values for the placeholders.
- `%s` `%q` posiitonal placeholder
- `%(NAME)s` `%(NAME)q` named placeholder
- `%(NAME=DEFAULT)s` `%(NAME=DEFAULT)q` placeholder with default value
- `%(NAME)?s` `%(NAME)?q` placeholder with optional value
- `%(NAME?)s` `%(NAME?)q` nullable placeholder that defaults to `null`
- `%(NAME)?s` `%(NAME)?q` optional placeholder that defaults to blank
- `%*s` `%*q` expand positional values as array items
- `%**s` `%**q` expand positional values as key value pairs
- `%(NAME)*s` `%(NAME)*q` expand named values as array items
Expand All @@ -65,7 +66,6 @@ And [VALUE]... [NAME=VALUE]... are the values for the placeholders.
- Pass values for named placeholders using `NAME=VALUE` syntax.
- Pass values for named array items using `NAME=ITEM_N` syntax.
- Pass values for named key value pairs using `NAME=KEY_N NAME=VALUE_N` syntax.
- Optional placeholders default to empty string, which is considered as null.
- Do not declare or pass positional placeholders or values after named ones.
- Expandable positional placeholder should be the last placeholder in a template.

Expand All @@ -87,22 +87,25 @@ jf {%**q} one 1 two 2 three 3
jf "{%q: %(value=default)q, %(bar)**q}" foo value=bar bar=biz bar=baz
# {"foo":"bar","biz":"baz"}

jf "{str or bool: %(str)?q %(bool)?s, optional: %(optional)?q}" str=true
# {"str or bool":"true","optional":null}

jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct)?q}' 1 2 3=3
jf "{str or bool: %(str)?q %(bool)?s, nullable: %(nullable?)q}" str=true
# {"str or bool":"true","nullable":null}

jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct?)q}' 1 2 3=3
# {"1":1,"two":"2","3":3,"four":"4","%":null}
```

### USEFUL ALIASES
### SHELL ALIASES

You can set the following aliases in your shell:

```bash
alias str='jf %q'
alias arr='jf "[%*s]"'
alias obj='jf "{%**s}"'
```

### ALIAS USAGE
Then you can use them like this:

```bash
str 1
Expand Down
34 changes: 17 additions & 17 deletions assets/jf.1
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,36 @@ And [VALUE]\.\.\. [NAME=VALUE]\.\.\. are the values for the placeholders.
.TP
.B
`%s`
`%q` posiitonal placeholder
`%q` posiitonal placeholder
.TP
.B
`%(NAME)s`
`%(NAME)q` named placeholder
`%(NAME)q` named placeholder
`%(NAME=DEFAULT)s` `%(NAME=DEFAULT)q` placeholder with default value
.TP
.B
`%(NAME=DEFAULT)s`
`%(NAME=DEFAULT)q` placeholder with default value
`%(NAME?)s`
`%(NAME?)q` optional placeholder that defaults to `null`
.TP
.B
`%(NAME)?s`
`%(NAME)?q` placeholder with optional value
`%(NAME)?q` nullable placeholder that defaults to blank
.TP
.B
`%*s`
`%*q` expand positional values as array items
`%*q` expand positional values as array items
.TP
.B
`%**s`
`%**q` expand positional values as key value pairs
`%**q` expand positional values as key value pairs
.TP
.B
`%(NAME)*s`
`%(NAME)*q` expand named values as array items
`%(NAME)*q` expand named values as array items
.TP
.B
`%(NAME)**s`
`%(NAME)**q` expand named values as key value pairs
`%(NAME)**q` expand named values as key value pairs
.SH RULES

.IP \(bu 3
Expand All @@ -68,8 +69,6 @@ Pass values for named array items using `NAME=ITEM_N` syntax.
.IP \(bu 3
Pass values for named key value pairs using `NAME=KEY_N NAME=VALUE_N` syntax.
.IP \(bu 3
Optional placeholders default to empty string, which is considered as null.
.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.
Expand All @@ -96,23 +95,24 @@ Run: jf "{%q: %(value=default)q, %(bar)**q}" foo value=bar bar=biz bar=baz
.IP \(bu 3
Out: {"foo":"bar","biz":"baz"}
.IP \(bu 3
Run: jf "{str or bool: %(str)?q %(bool)?s, optional: %(optional)?q}" str=true
Run: jf "{str or bool: %(str)?q %(bool)?s, nullable: %(nullable?)q}" str=true
.IP \(bu 3
Out: {"str or bool":"true","optional":null}
Out: {"str or bool":"true","nullable":null}
.IP \(bu 3
Run: jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct)?q}' 1 2 3=3
Run: jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct?)q}' 1 2 3=3
.IP \(bu 3
Out: {"1":1,"two":"2","3":3,"four":"4","%":null}
.SH USEFUL ALIASES
.SH SHELL ALIASES

You can set the following aliases in your shell:
.IP \(bu 3
alias str='jf %q'
.IP \(bu 3
alias arr='jf "[%*s]"'
.IP \(bu 3
alias obj='jf "{%**s}"'
.SH ALIAS USAGE

.PP
Then you can use them like this:
.IP \(bu 3
Run: str 1
.IP \(bu 3
Expand Down
13 changes: 13 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ where
let mut name = "".to_string();
let mut default_value: Option<String> = None;
let mut is_optional = false;
let mut is_nullable = false;
let mut is_reading_expandable_items = false;
let mut is_reading_expandable_pairs = false;

Expand All @@ -123,8 +124,18 @@ where
if default_value.is_some() {
return Err(format!("optional placeholder '{name}' at column {col} cannot have a default value").as_str().into());
}
if is_nullable {
return Err(format!("optional placeholder '{name}' at column {col} cannot also be nullable").as_str().into());
}
is_optional = true;
}
('?', None) => {
is_nullable = true;
last_char = chars.next().map(|(_, ch)| ch);
if last_char != Some(')') {
return Err(format!("nullable placeholder '{name}' at column {col} must end with '?)'", col = col).as_str().into());
}
}
('*', Some(')')) => {
is_reading_expandable_items = true;
is_reading_expandable_pairs = false;
Expand Down Expand Up @@ -152,6 +163,8 @@ where
} else {
val.push_str(value);
}
} else if is_nullable {
val.push_str("null");
} else if !is_optional {
return Err(format!(
"no value for placeholder '%({name}){ch}' at column {col}"
Expand Down
22 changes: 16 additions & 6 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ fn test_optional_placeholder_with_default_value_error() {
);
}

#[test]
fn test_nullable_placeholder_must_end_with_error() {
let args = [r#"%(foo?bar)q"#].map(Into::into);

assert_eq!(
jf::format(args).unwrap_err().to_string(),
"jf: nullable placeholder 'foo' at column 5 must end with '?)'"
);
}

#[test]
fn test_named_expandable_placeholder_with_default_value_error() {
let args = [r#"%(foo=default)*q"#].map(Into::into);
Expand Down Expand Up @@ -328,7 +338,7 @@ fn test_no_value_for_placeholder_name_error() {

#[test]
fn test_invalid_character_in_placeholder_name_error() {
for ch in [' ', '\t', '\n', '\r', '\0', '\'', '"', '{', '}', '?'].iter() {
for ch in [' ', '\t', '\n', '\r', '\0', '\'', '"', '{', '}'].iter() {
let args = [format!("%(foo{ch}bar)s)")].map(Into::into);
assert_eq!(
jf::format(args.clone()).unwrap_err().to_string(),
Expand Down Expand Up @@ -404,17 +414,17 @@ fn test_usage_example() {
);

let args = [
"{str or bool: %(str)?q %(bool)?s, optional: %(optional)?q}",
"{str or bool: %(str)?q %(bool)?s, nullable: %(nullable?)q}",
"str=true",
]
.map(Into::into);
assert_eq!(
jf::format(args).unwrap().to_string(),
r#"{"str or bool":"true","optional":null}"#
r#"{"str or bool":"true","nullable":null}"#
);

let args = [
r#"{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct)?q}"#,
r#"{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct?)q}"#,
"1",
"2",
"3=3",
Expand All @@ -429,14 +439,14 @@ fn test_usage_example() {
#[test]
fn test_print_version() {
let arg = ["jf v%v"].map(Into::into);
assert_eq!(jf::format(arg).unwrap().to_string(), r#""jf v0.3.1""#);
assert_eq!(jf::format(arg).unwrap().to_string(), r#""jf v0.3.2""#);

let args =
["{foo: %q, bar: %(bar)q, version: %v}", "foo", "bar=bar"].map(Into::into);

assert_eq!(
jf::format(args).unwrap().to_string(),
r#"{"foo":"foo","bar":"bar","version":"0.3.1"}"#
r#"{"foo":"foo","bar":"bar","version":"0.3.2"}"#
);
}

Expand Down
30 changes: 16 additions & 14 deletions src/usage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ USAGE

SYNTAX

`%s` `%q` posiitonal placeholder
`%(NAME)s` `%(NAME)q` named placeholder
`%(NAME=DEFAULT)s` `%(NAME=DEFAULT)q` placeholder with default value
`%(NAME)?s` `%(NAME)?q` placeholder with optional value
`%*s` `%*q` expand positional values as array items
`%**s` `%**q` expand positional values as key value pairs
`%(NAME)*s` `%(NAME)*q` expand named values as array items
`%(NAME)**s` `%(NAME)**q` expand named values as key value pairs
`%s` `%q` posiitonal placeholder
`%(NAME)s` `%(NAME)q` named placeholder
`%(NAME=DEFAULT)s` `%(NAME=DEFAULT)q` placeholder with default value
`%(NAME?)s` `%(NAME?)q` optional placeholder that defaults to `null`
`%(NAME)?s` `%(NAME)?q` nullable placeholder that defaults to blank
`%*s` `%*q` expand positional values as array items
`%**s` `%**q` expand positional values as key value pairs
`%(NAME)*s` `%(NAME)*q` expand named values as array items
`%(NAME)**s` `%(NAME)**q` expand named values as key value pairs

RULES

* Pass values for positional placeholders in the same order as in the template.
* Pass values for named placeholders using `NAME=VALUE` syntax.
* Pass values for named array items using `NAME=ITEM_N` syntax.
* Pass values for named key value pairs using `NAME=KEY_N NAME=VALUE_N` syntax.
* Optional placeholders default to empty string, which is considered as null.
* Do not declare or pass positional placeholders or values after named ones.
* Expandable positional placeholder should be the last placeholder in a template.

Expand All @@ -49,19 +49,21 @@ EXAMPLES
- Run: jf "{%q: %(value=default)q, %(bar)**q}" foo value=bar bar=biz bar=baz
- Out: {"foo":"bar","biz":"baz"}

- Run: jf "{str or bool: %(str)?q %(bool)?s, optional: %(optional)?q}" str=true
- Out: {"str or bool":"true","optional":null}
- Run: jf "{str or bool: %(str)?q %(bool)?s, nullable: %(nullable?)q}" str=true
- Out: {"str or bool":"true","nullable":null}

- Run: jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct)?q}' 1 2 3=3
- Run: jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct?)q}' 1 2 3=3
- Out: {"1":1,"two":"2","3":3,"four":"4","%":null}

USEFUL ALIASES
SHELL ALIASES

You can set the following aliases in your shell:

- alias str='jf %q'
- alias arr='jf "[%*s]"'
- alias obj='jf "{%**s}"'

ALIAS USAGE
Then you can use them like this:

- Run: str 1
- Out: "1"
Expand Down

0 comments on commit e144c97

Please sign in to comment.