Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 83 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,90 @@ let parsed: Vec<i64> = serde_json::from_str(&output).unwrap();

assert_eq!(vec![2009, 2012, 2014, 2016, 2019], parsed);
```
## Options

Barely any of the options or flags available from the [jq] cli are exposed
currently.
Literally all that is provided is the ability to execute a _jq program_ on a blob
of json.
Please pardon my dust as I sort out the details.
Jq has flags to alter the way in which data is input or output, some of these flags are supported.
The supported flags are available throught the _advanced varients of the run functions.

```rust
use jq_rs;
use serde_json::{self, json};

let data = json!({ "title": "Coraline", "year": 2009 });
let query = ".title";

// program output as a raw string, without quotes
let output = jq_rs::run_advanced(query, &data.to_string(), jq_rs::JqOptions::default().with_raw_output(true));

let output_raw = jq_rs::run_advanced(query, &data.to_string());

assert_eq!("\"Coraline\"", output);
```

### Raw input and raw output

jq-rs supports the `-R, --raw-input` and `-r, --raw-output` flags through the following options:

```rust
use jq_rs;
let options = jq_rs::JqOptions::default()
.with_raw_output(true)
.with_raw_input(true);
```

These are disabled by default.

### Compact output

jq-rs supports the `-c, --compact-output`, `--tabs` and `--indent n` flags through the following options:

```rust
use jq_rs;
let compact = jq_rs::JqOptions::default()
.with_indentation(jq_rs::JqIndentation::Compact);

let tabs = jq_rs::JqOptions::default()
.with_indentation(jq_rs::JqIndentation::Tabs);

let spaces_2 = jq_rs::JqOptions::default()
.with_indentation(jq_rs::JqIndentation::Spaces(2));
```

Compact is the default for this option.

### Sorting

jq-rs supports the `-S, --sort-keys` flag using the following option:

```rust
use jq_rs;
let sorted = jq_rs::JqOptions::default()
.with_sort_keys(true);
```

Sorting is disabled by default.

### Colorization

jq-rs supports the `-C, --color-output` and `-M, --monochrome-output` flags.
jq-rs also supports custom colors, which are normally available through the `JQ_COLORS` environment variable.

```rust
use jq_rs;

let monochrome = jq_rs::JqOptions::default()
.with_colorization(jq_rs::JqColorization::Monochrome);

let colorize = jq_rs::JqOptions::default()
.with_colorization(jq_rs::JqColorization::Colorize),

let all_blue = jq_rs::JqOptions::default()
.with_colorization(jq_rs::JqColorization::Custom(
"0;34:0;34:0;34:0;34:0;34:0;34:0;34:0;34",
));
```

The default option is monochrome, refer to the [jq documentation](https://jqlang.github.io/jq/manual/#colors) for using custom colors.

## Linking to libjq

Expand Down
8 changes: 7 additions & 1 deletion examples/simple-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ fn main() {

let program = args.next().expect("jq program");
let input = args.next().expect("data input");
match jq_rs::run(&program, &input) {
match jq_rs::run_advanced(
&program,
&input,
jq_rs::JqOptions::default()
.with_colorization(jq_rs::JqColorization::Colorize)
.with_indentation(jq_rs::JqIndentation::Spaces(2)),
) {
Ok(s) => print!("{}", s), // The output will include a trailing newline
Err(e) => eprintln!("{}", e),
}
Expand Down
132 changes: 109 additions & 23 deletions src/jq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
//! These are building blocks and not intended for use from the public API.

use crate::errors::{Error, Result};
use crate::options::{JqColorization, JqIndentation};
use crate::JqOptions;
use jq_sys::{
jq_compile, jq_format_error, jq_get_exit_code, jq_halted, jq_init, jq_next, jq_set_error_cb,
jq_start, jq_state, jq_teardown, jv, jv_copy, jv_dump_string, jv_free, jv_get_kind,
jv_invalid_get_msg, jv_invalid_has_msg, jv_kind_JV_KIND_INVALID, jv_kind_JV_KIND_NUMBER,
jv_kind_JV_KIND_STRING, jv_number_value, jv_parser, jv_parser_free, jv_parser_new,
jv_parser_next, jv_parser_set_buf, jv_string_value,
jq_compile, jq_format_error, jq_get_exit_code, jq_halted, jq_init, jq_next, jq_set_colors,
jq_set_error_cb, jq_start, jq_state, jq_teardown, jv, jv_copy, jv_dump_string, jv_free,
jv_get_kind, jv_invalid_get_msg, jv_invalid_has_msg, jv_kind_JV_KIND_INVALID,
jv_kind_JV_KIND_NUMBER, jv_kind_JV_KIND_STRING, jv_number_value, jv_parser, jv_parser_free,
jv_parser_new, jv_parser_next, jv_parser_set_buf, jv_print_flags_JV_PRINT_COLOR,
jv_print_flags_JV_PRINT_PRETTY, jv_print_flags_JV_PRINT_SORTED, jv_print_flags_JV_PRINT_TAB,
jv_string, jv_string_value,
};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::sync::{Mutex, MutexGuard};

pub struct Jq {
state: *mut jq_state,
Expand Down Expand Up @@ -84,20 +89,23 @@ impl Jq {
}
}

/// Run the jq program against an input.
pub fn execute(&mut self, input: CString) -> Result<String> {
/// Run the jq program against an input, using options.
pub fn execute_advanced(&mut self, input: CString, options: &JqOptions) -> Result<String> {
let mut parser = Parser::new();
self.process(parser.parse(input)?)
self.process(parser.parse(input, options)?, options)
}

/// Unwind the parser and return the rendered result.
///
/// When this results in `Err`, the String value should contain a message about
/// what failed.
fn process(&mut self, initial_value: JV) -> Result<String> {
fn process(&mut self, initial_value: JV, options: &JqOptions) -> Result<String> {
let mut buf = String::new();

unsafe {
static LOCK: Mutex<()> = Mutex::new(());
let _guard: MutexGuard<()> = LOCK.lock().unwrap();

// `jq_start` seems to be a consuming call.
// In order to avoid a double-free, when `initial_value` is dropped,
// we have to use `jv_copy` on the inner `jv`.
Expand All @@ -106,7 +114,7 @@ impl Jq {
// it is no longer needed.
drop(initial_value);

dump(self, &mut buf)?;
dump(self, &mut buf, options)?;
}

Ok(buf)
Expand All @@ -123,13 +131,33 @@ struct JV {
ptr: jv,
}

fn jv_print_indent_flags(n: u32) -> u32 {
if n > 7 {
return jv_print_flags_JV_PRINT_TAB | jv_print_flags_JV_PRINT_PRETTY;
}

if n == 0 {
return 0;
}

n << 8 | jv_print_flags_JV_PRINT_PRETTY
}

impl JV {
/// Convert the current `JV` into the "dump string" rendering of itself.
pub fn as_dump_string(&self) -> Result<String> {
pub fn as_dump_string(&self, flags: u32) -> Result<String> {
let dump = JV {
ptr: unsafe { jv_dump_string(jv_copy(self.ptr), 0) },
ptr: unsafe { jv_dump_string(jv_copy(self.ptr), flags as i32) },
};
dump.as_string()
}

/// Convert the current `JV` into the raw rendering of itself.
pub fn as_raw_string(&self) -> Result<String> {
let copied = JV {
ptr: unsafe { jv_copy(self.ptr) },
};
unsafe { get_string_value(jv_string_value(dump.ptr)) }
copied.as_string()
}

/// Attempts to extract feedback from jq if the JV is invalid.
Expand Down Expand Up @@ -168,15 +196,17 @@ impl JV {
}

pub fn as_string(&self) -> Result<String> {
unsafe {
if jv_get_kind(self.ptr) == jv_kind_JV_KIND_STRING {
get_string_value(jv_string_value(self.ptr))
} else {
Err(Error::Unknown)
}
if self.is_string() {
unsafe { get_string_value(jv_string_value(self.ptr)) }
} else {
Err(Error::Unknown)
}
}

pub fn is_string(&self) -> bool {
unsafe { jv_get_kind(self.ptr) == jv_kind_JV_KIND_STRING }
}

pub fn is_valid(&self) -> bool {
unsafe { jv_get_kind(self.ptr) != jv_kind_JV_KIND_INVALID }
}
Expand All @@ -203,7 +233,7 @@ impl Parser {
}
}

pub fn parse(&mut self, input: CString) -> Result<JV> {
pub fn parse(&mut self, input: CString, options: &JqOptions) -> Result<JV> {
// For a single run, we could set this to `1` (aka `true`) but this will
// break the repeated `JqProgram` usage.
// It may be worth exposing this to the caller so they can set it for each
Expand All @@ -225,7 +255,11 @@ impl Parser {
};

let value = JV {
ptr: unsafe { jv_parser_next(self.ptr) },
ptr: if !options.raw_input {
unsafe { jv_parser_next(self.ptr) }
} else {
unsafe { jv_string(input.as_ptr()) }
},
};
if value.is_valid() {
Ok(value)
Expand Down Expand Up @@ -256,15 +290,54 @@ unsafe fn get_string_value(value: *const c_char) -> Result<String> {
}

/// Renders the data from the parser and pushes it into the buffer.
unsafe fn dump(jq: &Jq, buf: &mut String) -> Result<()> {
unsafe fn dump(jq: &Jq, buf: &mut String, options: &JqOptions) -> Result<()> {
// Looks a lot like an iterator...

let mut value = JV {
ptr: jq_next(jq.state),
};

let mut dumpoptions = jv_print_indent_flags(2);
match options.indentation {
JqIndentation::Compact => {
dumpoptions &= !(jv_print_flags_JV_PRINT_TAB | jv_print_indent_flags(7));
}
JqIndentation::Tabs => {
dumpoptions &= !jv_print_indent_flags(7);
dumpoptions |= jv_print_flags_JV_PRINT_TAB | jv_print_flags_JV_PRINT_PRETTY;
}
JqIndentation::Spaces(indent) => {
dumpoptions &= !(jv_print_flags_JV_PRINT_TAB | jv_print_indent_flags(7));
dumpoptions |= jv_print_indent_flags(indent as u32);
}
};

// Reset colors
try_set_colors("")?;
match options.colorization {
JqColorization::Custom(colors) => {
try_set_colors(colors)?;
dumpoptions |= jv_print_flags_JV_PRINT_COLOR;
}
JqColorization::Colorize => {
// This is the default, but we need to restore it as it might have been overwritten by a previous run.
dumpoptions |= jv_print_flags_JV_PRINT_COLOR;
}
JqColorization::Monochrome => {
dumpoptions &= !jv_print_flags_JV_PRINT_COLOR;
}
}

if options.sort_keys {
dumpoptions |= jv_print_flags_JV_PRINT_SORTED;
}

while value.is_valid() {
let s = value.as_dump_string()?;
let s: String = if options.raw_output && value.is_string() {
value.as_raw_string()?
} else {
value.as_dump_string(dumpoptions)?
};
buf.push_str(&s);
buf.push('\n');

Expand Down Expand Up @@ -302,6 +375,19 @@ unsafe fn dump(jq: &Jq, buf: &mut String) -> Result<()> {
}
}

unsafe fn try_set_colors(colors: &str) -> Result<()> {
let c_colors = CString::new(colors)?;
let val = jq_set_colors(c_colors.as_ptr());

if val != 1 {
return Err(Error::System {
reason: Some("Error while setting colors".to_string()),
});
}

Ok(())
}

/// Various exit codes jq checks for during the `if (jq_halted(jq))` branch of
/// their processing loop.
///
Expand Down
Loading