Skip to content

Commit 6a4a508

Browse files
authored
ENH: Add jsonlogic binary (#23)
* ENH: Add jsonlogic binary Add a binary to use jsonlogic from the commandline. Input data can be provided as an argument or via stdin. Multiple calls can be chained together, parsing the output of the previous call as data. * FIX: typo on optional * DOC: more extensive cmdline example
1 parent 6ae2eb1 commit 6a4a508

File tree

4 files changed

+187
-11
lines changed

4 files changed

+187
-11
lines changed

Diff for: CHANGELOG.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## Added
10+
11+
- A new `cmdline` feature that builds a `jsonlogic` binary for JsonLogic on
12+
the commandline
13+
14+
## [0.1.3] - 2020-07-15
15+
16+
- More minor CI fixes
17+
918
## [0.1.2] - 2020-07-14
1019

1120
### Chore
@@ -15,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1524
## [0.1.1] - 2020-07-14
1625

1726
### Fixed
18-
- The Python source dist wasn't generating a Cargo lockfile prior to attempting
27+
- The Python source dist wasn't generating a Cargo lockfile prior to attempting
1928
to determine the package version, causing the `cargo pkgid` command to fail
2029

2130
### Chore

Diff for: Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ version = "0.1.3"
1818
# lib for regular rust stuff
1919
crate-type = ["cdylib", "lib"]
2020

21+
[[bin]]
22+
name = "jsonlogic"
23+
path = "src/bin.rs"
24+
required-features = ["cmdline"]
25+
2126
[features]
27+
cmdline = ["anyhow", "clap"]
2228
default = []
2329
python = ["cpython"]
2430
wasm = ["wasm-bindgen"]
@@ -38,6 +44,14 @@ features = ["extension-module"]
3844
optional = true
3945
version = "0.5"
4046

47+
[dependencies.anyhow]
48+
optional = true
49+
version = "~1.0.31"
50+
51+
[dependencies.clap]
52+
optional = true
53+
version = "~2.33.1"
54+
4155
[dev-dependencies.reqwest]
4256
features = ["blocking"]
4357
version = "~0.10.6"

Diff for: README.md

+90-10
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,17 @@ languages. The table below describes current language support:
3232

3333
### Rust
3434

35-
Just add to your `Cargo.toml`:
35+
To use as a Rust library, add to your `Cargo.toml`:
3636

3737
``` toml
3838
[dependencies]
39-
jsonlogic-rs = "~0.1.1"
39+
jsonlogic-rs = "~0.1"
40+
```
41+
42+
If you just want to use the commandline `jsonlogic` binary:
43+
44+
``` sh
45+
cargo install jsonlogic-rs --features cmdline
4046
```
4147

4248
### Node/Browser
@@ -52,14 +58,15 @@ Note that the package is distributed as a node package, so you'll need to use
5258

5359
### Python
5460

55-
Wheels are distributed for many platforms, so you should often be able to just
56-
run:
61+
Supports Python 3.6+.
62+
63+
Wheels are distributed for many platforms, so you can often just run:
5764

5865
``` sh
5966
pip install jsonlogic-rs
6067
```
6168

62-
If a wheel does _not_ exist for your system, this will attempt to build the
69+
If a wheel does _not_ exist for your system, this will attempt to build the
6370
package. In order for the package to build successfully, you MUST have Rust
6471
installed on your local system, and `cargo` MUST be present in your `PATH`.
6572

@@ -71,15 +78,15 @@ See [Building](#Building) below for more details.
7178

7279
```rust
7380
use jsonlogic_rs;
74-
use serde_json::json;
81+
use serde_json::{json, from_str, Value};
7582

76-
// You can pass JSON values deserialized with serde straight
77-
// into apply().
83+
// You can pass JSON values deserialized with serde straight into apply().
7884
fn main() {
85+
let data: Value = from_str(r#"{"a": 7}"#)
7986
assert_eq!(
8087
jsonlogic_rs::apply(
8188
json!({"===": [{"var": "a"}, 7]}),
82-
json!({"a": 7}),
89+
data,
8390
),
8491
json!(true)
8592
);
@@ -109,14 +116,87 @@ res = jsonlogic_rs.apply(
109116

110117
assert res == True
111118

112-
# If You have serialized JsonLogic and data, the `apply_serialized` method can
119+
# If You have serialized JsonLogic and data, the `apply_serialized` method can
113120
# be used instead
114121
res = jsonlogic_rs.apply_serialized(
115122
'{"===": [{"var": "a"}, 7]}',
116123
'{"a": 7}'
117124
)
118125
```
119126

127+
### Commandline
128+
129+
``` raw
130+
Parse JSON data with a JsonLogic rule.
131+
132+
When no <data> or <data> is -, read from stdin.
133+
134+
The result is written to stdout as JSON, so multiple calls
135+
can be chained together if desired.
136+
137+
USAGE:
138+
jsonlogic <logic> [data]
139+
140+
FLAGS:
141+
-h, --help Prints help information
142+
-V, --version Prints version information
143+
144+
ARGS:
145+
<logic> A JSON logic string
146+
<data> A string of JSON data to parse. May be provided as stdin.
147+
148+
EXAMPLES:
149+
jsonlogic '{"===": [{"var": "a"}, "foo"]}' '{"a": "foo"}'
150+
jsonlogic '{"===": [1, 1]}' null
151+
echo '{"a": "foo"}' | jsonlogic '{"===": [{"var": "a"}, "foo"]}'
152+
153+
Inspired by and conformant with the original JsonLogic (jsonlogic.com).
154+
```
155+
156+
Run `jsonlogic --help` the most up-to-date usage.
157+
158+
An example of chaining multiple results:
159+
160+
``` sh
161+
$ echo '{"a": "a"}' \
162+
| jsonlogic '{"if": [{"===": [{"var": "a"}, "a"]}, {"result": true}, {"result": false}]}' \
163+
| jsonlogic '{"if": [{"!!": {"var": "result"}}, "result was true", "result was false"]}'
164+
165+
"result was true"
166+
```
167+
168+
Using `jsonlogic` on the cmdline to explore an API:
169+
170+
``` sh
171+
> curl -s "https://catfact.ninja/facts?limit=5"
172+
173+
{"current_page":1,"data":[{"fact":"The Egyptian Mau is probably the oldest breed of cat. In fact, the breed is so ancient that its name is the Egyptian word for \u201ccat.\u201d","length":132},{"fact":"Julius Ceasar, Henri II, Charles XI, and Napoleon were all afraid of cats.","length":74},{"fact":"Unlike humans, cats cannot detect sweetness which likely explains why they are not drawn to it at all.","length":102},{"fact":"Cats can be taught to walk on a leash, but a lot of time and patience is required to teach them. The younger the cat is, the easier it will be for them to learn.","length":161},{"fact":"Researchers believe the word \u201ctabby\u201d comes from Attabiyah, a neighborhood in Baghdad, Iraq. Tabbies got their name because their striped coats resembled the famous wavy patterns in the silk produced in this city.","length":212}],"first_page_url":"https:\/\/catfact.ninja\/facts?page=1","from":1,"last_page":67,"last_page_url":"https:\/\/catfact.ninja\/facts?page=67","next_page_url":"https:\/\/catfact.ninja\/facts?page=2","path":"https:\/\/catfact.ninja\/facts","per_page":"5","prev_page_url":null,"to":5,"total":332}
174+
175+
> curl -s "https://catfact.ninja/facts?limit=5" | jsonlogic '{"var": "data"}'
176+
177+
[{"fact":"A cat's appetite is the barometer of its health. Any cat that does not eat or drink for more than two days should be taken to a vet.","length":132},{"fact":"Some notable people who disliked cats: Napoleon Bonaparte, Dwight D. Eisenhower, Hitler.","length":89},{"fact":"During the time of the Spanish Inquisition, Pope Innocent VIII condemned cats as evil and thousands of cats were burned. Unfortunately, the widespread killing of cats led to an explosion of the rat population, which exacerbated the effects of the Black Death.","length":259},{"fact":"A cat has approximately 60 to 80 million olfactory cells (a human has between 5 and 20 million).","length":96},{"fact":"In just seven years, a single pair of cats and their offspring could produce a staggering total of 420,000 kittens.","length":115}]
178+
179+
> curl -s "https://catfact.ninja/facts?limit=5" | jsonlogic '{"var": "data.0"}'
180+
181+
{"fact":"A tiger's stripes are like fingerprints","length":39}
182+
183+
> curl -s "https://catfact.ninja/facts?limit=5" | jsonlogic '{"var": "data.0.fact"}'
184+
"Neutering a male cat will, in almost all cases, stop him from spraying (territorial marking), fighting with other males (at least over females), as well as lengthen his life and improve its quality."
185+
186+
> curl -s "https://catfact.ninja/facts?limit=5" \
187+
| jsonlogic '{"var": "data.0.fact"}' \
188+
| jsonlogic '{"in": ["cat", {"var": ""}]}'
189+
190+
true
191+
192+
> curl -s "https://catfact.ninja/facts?limit=5" \
193+
| jsonlogic '{"var": "data.0.fact"}' \
194+
| jsonlogic '{"in": ["cat", {"var": ""}]}' \
195+
| jsonlogic '{"if": [{"var": ""}, "fact contained cat", "fact did not contain cat"]}'
196+
197+
"fact contained cat"
198+
```
199+
120200
## Building
121201

122202
### Prerequisites

Diff for: src/bin.rs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use std::io;
2+
use std::io::Read;
3+
4+
use anyhow::{Context, Result};
5+
use clap::{App, Arg};
6+
use serde_json;
7+
use serde_json::Value;
8+
9+
use jsonlogic_rs;
10+
11+
fn configure_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
12+
app.version(env!("CARGO_PKG_VERSION"))
13+
.author("Matthew Planchard <[email protected]>")
14+
.about(
15+
"Parse JSON data with a JsonLogic rule.\n\
16+
\n\
17+
When no <data> or <data> is -, read from stdin.
18+
\n\
19+
The result is written to stdout as JSON, so multiple calls \n\
20+
can be chained together if desired.",
21+
)
22+
.arg(
23+
Arg::with_name("logic")
24+
.help("A JSON logic string")
25+
.required(true)
26+
.takes_value(true),
27+
)
28+
.arg(
29+
Arg::with_name("data")
30+
.help("A string of JSON data to parse. May be provided as stdin.")
31+
.required(false)
32+
.takes_value(true),
33+
)
34+
.after_help(
35+
r#"EXAMPLES:
36+
jsonlogic '{"===": [{"var": "a"}, "foo"]}' '{"a": "foo"}'
37+
jsonlogic '{"===": [1, 1]}' null
38+
echo '{"a": "foo"}' | jsonlogic '{"===": [{"var": "a"}, "foo"]}'
39+
40+
Inspired by and conformant with the original JsonLogic (jsonlogic.com).
41+
42+
Report bugs to github.com/Bestowinc/json-logic-rs."#,
43+
)
44+
}
45+
46+
fn main() -> Result<()> {
47+
let app = configure_args(App::new("jsonlogic"));
48+
let matches = app.get_matches();
49+
50+
let logic = matches.value_of("logic").expect("logic arg expected");
51+
let json_logic: Value =
52+
serde_json::from_str(logic).context("Could not parse logic as JSON")?;
53+
54+
// let mut data: String;
55+
let data_arg = matches.value_of("data").unwrap_or("-");
56+
57+
let mut data: String;
58+
if data_arg != "-" {
59+
data = data_arg.to_string();
60+
} else {
61+
data = String::new();
62+
io::stdin().lock().read_to_string(&mut data)?;
63+
}
64+
let json_data: Value =
65+
serde_json::from_str(&data).context("Could not parse data as JSON")?;
66+
67+
let result = jsonlogic_rs::apply(&json_logic, &json_data)
68+
.context("Could not execute logic")?;
69+
70+
println!("{}", result.to_string());
71+
72+
Ok(())
73+
}

0 commit comments

Comments
 (0)