Skip to content

Commit

Permalink
Implement CLI options (#10)
Browse files Browse the repository at this point in the history
This adds:

-r, --raw      output the raw rendered value without formatting
-p, --pretty   pretty print the output
-y, --yaml     output as YAML instead of JSON
-h, --help     print this help message
-v, --version  print the version number
--             stop parsing options

And removes:

- `%v`
- `%R`
- `%Y`
- `%J`

Also the Rust library exposes more functions, each for different
formatting options.
  • Loading branch information
sayanarijit committed May 27, 2023
1 parent cbbd746 commit 178b289
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 153 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.2"
version = "0.5.0"
edition = "2021"
authors = ["Arijit Basu <hi@arijitbasu.in>"]
description = 'A small utility to safely format and print JSON objects in the commandline'
Expand Down
45 changes: 22 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
However, unlike `jo`, where you build the JSON object by nesting `jo` outputs,
`jf` works similar to `printf`, i.e. it expects the template in [YAML][yaml] format as the first argument, and then the values for the placeholders as subsequent arguments.

For example:

```bash
jf "{one: %s, two: %q, three: [%(four)s, %(five=5)q]}" 1 2 four=4
# {"one":1,"two":"2","three":[4,"5"]}
```

### INSTALL

#### [Cargo][cargo]
Expand Down Expand Up @@ -36,23 +43,15 @@ nix-env -f https://github.com/NixOS/nixpkgs/tarball/nixos-unstable -iA jf
### USAGE

```bash
jf TEMPLATE [VALUE]... [NAME=VALUE]... [NAME@FILE]...
jf [OPTION]... [--] TEMPLATE [VALUE]... [NAME=VALUE]... [NAME@FILE]...
```

Where TEMPLATE may contain the following placeholders:

- `%q` quoted and safely escaped JSON string
- `%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
### TEMPLATE

And [VALUE]... [NAME=VALUE]... [NAME@FILE]... are the values for the placeholders.

### SYNTAX
A template is a string that should render into valid YAML. It can contain the
following placeholders:

- `%%` a literal `%` character
- `%s` `%q` read positional argument
- `%-s` `%-q` read stdin
- `%(NAME)s` `%(NAME)q` read named value from argument
Expand All @@ -68,17 +67,20 @@ And [VALUE]... [NAME=VALUE]... [NAME@FILE]... are the values for the placeholder
- `%(NAME)*s` `%(NAME)*q` expand named args as array items
- `%(NAME)**s` `%(NAME)**q` expand named args as key value pairs

Use placeholders with suffix `q` for safely quoted JSON string and `s` for JSON values
other than string.

### RULES

- Pass values for positional placeholders in the same order as in the template.
- Pass values to stdin following the order and separate them with null byte (`\0`).
- 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.
- Pass values to stdin following the order and separate them with null byte (`\0`).
- 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.
- 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.
- Do not pass positional values after named values.
- To allow merging arrays and objects via expansion, trailing comma after `s` and `q`,
if any, will be auto removed if no value is passed for the expandable placeholder.

### EXAMPLES

Expand Down Expand Up @@ -110,9 +112,9 @@ jf '{1: %s, two: %q, 3: %(3)s, four: %(four=4)q, "%%": %(pct?)q}' 1 2 3=3
You can set the following aliases in your shell:

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

Then you can use them like this:
Expand All @@ -136,9 +138,6 @@ obj 1 2 3 $(arr 4 $(str 5))
```rust
let json = match jf::format(["%q", "JSON Formatted"].map(Into::into)) {
Ok(value) => value,
Err(jf::Error::Usage) => {
bail!("usage: mytool: TEMPLATE [VALUE]... [NAME=VALUE]...")
}
Err(jf::Error::Jf(e)) => bail!("mytool: {e}"),
Err(jf::Error::Json(e)) => bail!("mytool: json: {e}"),
Err(jf::Error::Yaml(e)) => bail!("mytool: yaml: {e}"),
Expand Down
55 changes: 29 additions & 26 deletions assets/jf.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,41 @@
.TH jf "1" "" ""
.SH USAGE

jf TEMPLATE [VALUE]\.\.\. [NAME=VALUE]\.\.\. [NAME@FILE]\.\.\.
.PP
Where TEMPLATE may contain the following placeholders:
jf [OPTION]\.\.\. [--] TEMPLATE [VALUE]\.\.\. [NAME=VALUE]\.\.\. [NAME@FILE]\.\.\.
.SH OPTIONS

.TP
.B
`%q`
quoted and safely escaped JSON string
\fB-r\fP, \fB--raw\fP
output the raw rendered value without formatting
.TP
.B
`%s`
JSON values other than string
\fB-p\fP, \fB--pretty\fP
pretty print the output
.TP
.B
`%v`
the `jf` version number
\fB-y\fP, \fB--yaml\fP
output as YAML instead of JSON
.TP
.B
`%%`
a literal `%` character
\fB-h\fP, \fB--help\fP
print this help message
.TP
.B
`%R`
enable raw mode - render but do not format output
\fB-v\fP, \fB--version\fP
print the version number
.TP
.B
`%Y`
enable pretty YAML mode - format output into pretty YAML
--
stop parsing options
.SH TEMPLATE

A template is a string that should render into valid YAML. It can contain the
following placeholders:
.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

`%%`
a literal `%` character
.TP
.B
`%s`
Expand Down Expand Up @@ -93,25 +93,28 @@ And [VALUE]\.\.\. [NAME=VALUE]\.\.\. [NAME@FILE]\.\.\. are the values for the pl
.B
`%(NAME)**s`
`%(NAME)**q` expand named args as key value pairs
.PP
Use placeholders with suffix `q` for safely quoted JSON string and `s` for JSON values
other than string.
.SH RULES

.IP \(bu 3
Pass values for positional placeholders in the same order as in the template.
.IP \(bu 3
Pass values to stdin following the order and separate them with null byte (`\0`).
.IP \(bu 3
Pass values for named placeholders using `NAME=VALUE` syntax.
.IP \(bu 3
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
Pass values to stdin following the order and separate them with null byte (`\0`).
.IP \(bu 3
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.
Do not pass positional values after named values.
.IP \(bu 3
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.
To allow merging arrays and objects via expansion, trailing comma after `s` and `q`,
if any, will be auto removed if no value is passed for the expandable placeholder.
.SH EXAMPLES

.IP \(bu 3
Expand Down
89 changes: 38 additions & 51 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ pub enum Error {
Yaml(yaml::Error),
Jf(String),
Io(io::Error),
Usage,
}

impl Error {
pub fn returncode(&self) -> i32 {
match self {
Self::Usage | Self::Jf(_) => 1,
Self::Jf(_) => 1,
Self::Json(_) => 2,
Self::Yaml(_) => 3,
Self::Io(_) => 4,
Expand Down Expand Up @@ -55,12 +54,6 @@ impl From<io::Error> for Error {
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Usage => {
writeln!(f, "jf: not enough arguments")?;
writeln!(f)?;
write!(f, "{USAGE}")
}

Self::Json(e) => write!(f, "json: {e}"),
Self::Yaml(e) => write!(f, "yaml: {e}"),
Self::Jf(e) => write!(f, "jf: {e}"),
Expand Down Expand Up @@ -436,25 +429,16 @@ where
Ok(was_expanded)
}

#[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<char>, Mode), Error>
) -> Result<(String, Option<char>), Error>
where
C: Iterator<Item = (usize, char)>,
A: Iterator<Item = (usize, Cow<'a, str>)>,
S: Iterator<Item = (usize, io::Result<Vec<u8>>)>,
{
let mut mode = Mode::Json;
let mut val = "".to_string();
let mut last_char = None;
let mut is_reading_named_values = false;
Expand All @@ -472,22 +456,6 @@ where
val.push(ch);
last_char = None;
}
('v', Some('%')) => {
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);
}
Expand Down Expand Up @@ -566,23 +534,23 @@ where
}
}

Ok((val, last_char, mode))
Ok((val, last_char))
}

/// Format a string using the given arguments.
pub fn format<'a, I>(args: I) -> Result<String, Error>
/// Render the template using the given arguments.
pub fn render<'a, I>(args: I) -> Result<String, Error>
where
I: IntoIterator<Item = Cow<'a, str>>,
{
let mut args = args.into_iter().enumerate();
let Some((_, format)) = args.next() else {
return Err(Error::Usage);
return Err("not enough arguments, expected at least one".into());
};

let mut chars = format.chars().enumerate();
let mut stdin = io::stdin().lock().split(b'\0').enumerate();

let (val, last_char, mode) = format_partial(&mut chars, &mut args, &mut stdin)?;
let (val, last_char) = format_partial(&mut chars, &mut args, &mut stdin)?;

if last_char == Some('%') {
return Err("template ended with incomplete placeholder".into());
Expand All @@ -594,18 +562,37 @@ where
);
};

match mode {
Mode::Raw => Ok(val),
Mode::Yaml => yaml::from_str::<yaml::Value>(&val)
.and_then(|y| yaml::to_string(&y))
.map_err(Error::from),
Mode::Json => yaml::from_str::<yaml::Value>(&val)
.map_err(Error::from)
.and_then(|y| json::to_string(&y).map_err(Error::from)),
Mode::PrettyJson => yaml::from_str::<yaml::Value>(&val)
.map_err(Error::from)
.and_then(|y| json::to_string_pretty(&y).map_err(Error::from)),
}
Ok(val)
}

/// Render and format the template into JSON.
pub fn format<'a, I>(args: I) -> Result<String, Error>
where
I: IntoIterator<Item = Cow<'a, str>>,
{
let val = render(args)?;
let yaml: yaml::Value = yaml::from_str(&val).map_err(Error::from)?;
json::to_string(&yaml).map_err(Error::from)
}

/// Render and format the template into pretty JSON.
pub fn format_pretty<'a, I>(args: I) -> Result<String, Error>
where
I: IntoIterator<Item = Cow<'a, str>>,
{
let val = render(args)?;
let yaml: yaml::Value = yaml::from_str(&val).map_err(Error::from)?;
json::to_string_pretty(&yaml).map_err(Error::from)
}

/// Render and format the template into value JSON using the given arguments.
pub fn format_yaml<'a, I>(args: I) -> Result<String, Error>
where
I: IntoIterator<Item = Cow<'a, str>>,
{
let val = render(args)?;
let yaml: yaml::Value = yaml::from_str(&val).map_err(Error::from)?;
yaml::to_string(&yaml).map_err(Error::from)
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 178b289

Please sign in to comment.