Skip to content

Commit 4f6a313

Browse files
committed
add -q for JMESPath query on input data
1 parent 4fa85d9 commit 4f6a313

8 files changed

Lines changed: 93 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ exitcode = "1.1.2"
2121
failure = "0.1.6"
2222
handlebars = "2.0.2"
2323
indexmap = "1.3.0"
24+
jmespath = "0.2.0"
2425
log = "0.4.0"
2526
rayon = "1.2.0"
2627
serde = "1.0.102"

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ Homer Simpson <homer@example.com>
5353
5454
```
5555

56-
#### Ping a list of EC2 instances returned from the AWS CLI.
56+
#### Query input data
5757

58-
Since _each_ reads from stdin by default it plays nicely with other CLI tools including _jq_.
58+
You can use arbitrary [JMESPath](http://jmespath.org/) queries to extract rows from your input data:
5959

6060
```sh
61-
aws ec2 describe-instances | \
62-
jq '.Instances' | \
63-
each ping -c 1 {{PublicIpAddress}}
61+
curl https://cat-fact.herokuapp.com/facts |
62+
each -f json -q 'all[?upvotes > `4`]' -- \
63+
echo {{user.name.first}}: {{text}}
6464
```
6565

6666
#### Supply stdin to each command

src/formats/csv.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl Format for Csv {
126126
Ok(false)
127127
}
128128

129-
fn parse(&self, input: &mut dyn Read) -> Result<Vec<serde_json::Value>, Error> {
129+
fn parse(&self, input: &mut dyn Read) -> Result<serde_json::Value, Error> {
130130
let mut reader = self.reader_builder().from_reader(input);
131131
let mut it = reader.records();
132132

@@ -161,7 +161,7 @@ impl Format for Csv {
161161
values.push(map.into());
162162
}
163163

164-
Ok(values)
164+
Ok(values.into())
165165
}
166166

167167
fn write(&self, values: Vec<serde_json::Value>) -> Result<(), Error> {

src/formats/json.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl Format for Json {
2626
Ok(header[0] as char == '[')
2727
}
2828

29-
fn parse(&self, input: &mut dyn Read) -> Result<Vec<serde_json::Value>, Error> {
29+
fn parse(&self, input: &mut dyn Read) -> Result<serde_json::Value, Error> {
3030
// read to string first - see https://github.com/serde-rs/json/issues/160
3131
let mut buffer = String::new();
3232
input.read_to_string(&mut buffer)?;

src/formats/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub trait Format {
1616
fn set_arguments(&mut self, matches: &clap::ArgMatches) -> Result<(), Error>;
1717
fn get_extensions(&self) -> &'static [&'static str];
1818
fn is_valid_header(&self, header: &[u8]) -> Result<bool, Error>;
19-
fn parse(&self, input: &mut dyn Read) -> Result<Vec<serde_json::Value>, Error>;
19+
fn parse(&self, input: &mut dyn Read) -> Result<serde_json::Value, Error>;
2020
fn write(&self, values: Vec<serde_json::Value>) -> Result<(), Error>;
2121
}
2222

src/main.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ fn main() {
4545
.takes_value(true)
4646
.possible_values(format_ids.as_slice()),
4747
)
48+
.arg(
49+
Arg::with_name("query")
50+
.short("q")
51+
.long("query")
52+
.value_name("QUERY")
53+
.help("JMES query to apply to each input file")
54+
.takes_value(true),
55+
)
4856
.arg(
4957
Arg::with_name("output-format")
5058
.short("F")
@@ -233,7 +241,7 @@ fn run(
233241
},
234242
};
235243

236-
let values = match format.parse(reader) {
244+
let mut values = match format.parse(reader) {
237245
Ok(values) => values,
238246
Err(e) => {
239247
return Err(EachError::Data {
@@ -242,9 +250,27 @@ fn run(
242250
}
243251
};
244252

253+
if let Some(query_str) = arg_matches.value_of("query") {
254+
let query = jmespath::compile(&query_str).map_err(|e| EachError::Usage {
255+
message: format!("Invalid JMES query: {}", e),
256+
})?;
257+
258+
let query_result = query.search(values).map_err(|e| EachError::Data {
259+
message: format!("Error evaluating JMES query: {}", e),
260+
})?;
261+
262+
values = serde_json::to_value(query_result).map_err(|e| EachError::Data {
263+
message: format!("Error converting query result to JSON value: {}", e),
264+
})?;
265+
}
266+
267+
let vec_values = values.as_array().ok_or_else(|| EachError::Data {
268+
message: "Input values are not an array".to_string(),
269+
})?;
270+
245271
match action {
246-
Some(ref action) => process(&values, &action)?,
247-
None => output_values.extend_from_slice(&values),
272+
Some(ref action) => process(&vec_values, &action)?,
273+
None => output_values.extend_from_slice(&vec_values),
248274
}
249275
}
250276

src/tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,24 @@ Homer Simpson <homer@example.com>
153153
)
154154
.unwrap();
155155
}
156+
157+
#[test]
158+
fn jmes_query() {
159+
Assert::main_binary()
160+
.with_args(&[
161+
"-i",
162+
PEOPLE_JSON_PATH,
163+
"-q",
164+
r#"[?starts_with(name, `"Bart"`)]"#,
165+
"--",
166+
"echo",
167+
"-n",
168+
"{{email}}",
169+
])
170+
.succeeds()
171+
.and()
172+
.stdout()
173+
.is("bart@example.com")
174+
.unwrap();
175+
}
156176
}

0 commit comments

Comments
 (0)