///|
priv enum InputSource {
Stdin
File(String)
}
///|
priv enum QuerySource {
Inline(String)
File(String)
}
///|
priv struct CliOptions {
query : QuerySource
inputs : Array[InputSource]
logs : Bool
raw_output : Bool
compact_output : Bool
null_input : Bool
}
///|
priv enum CliParseResult {
Error(String)
Run(CliOptions)
}
///|
fn jq_command() -> @argparse.Command {
Command(
"moonjq",
about="Run jq filters over JSON input.",
flags=[
FlagArg(
"compact-output",
short='c',
about="Print compact JSON instead of pretty JSON.",
),
FlagArg(
"logs",
short='l',
about="Treat input as newline-delimited JSON logs.",
conflicts_with=["null-input"],
),
FlagArg(
"null-input",
short='n',
about="Run the filter once with null input.",
conflicts_with=["logs"],
),
FlagArg(
"raw-output",
short='r',
about="Print strings without JSON quotes.",
),
],
options=[
OptionArg(
"from-file",
short='f',
about="Read the filter from FILE.",
action=Append,
),
],
positionals=[
PositionArg(
"args",
about=" [file...]",
num_args=ValueRange(lower=0),
),
],
disable_help_subcommand=true,
)
}
///|
fn parse_cli(args : ArrayView[String]) -> CliParseResult {
let command = jq_command()
let matches = command.parse(argv=args, env=Map([])) catch {
err => return Error(err.to_string())
}
let positionals = matches.values.get("args").unwrap_or([])
let filter_files = matches.values.get("from-file").unwrap_or([])
let query = match filter_files.length() {
0 => {
if positionals.is_empty() {
return Error("error: missing filter\n\n" + command.render_help())
}
QuerySource::Inline(positionals[0])
}
1 => File(filter_files[0])
_ =>
return Error(
"error: multiple filters provided\n\n" + command.render_help(),
)
}
let input_start = if filter_files.is_empty() { 1 } else { 0 }
let inputs : Array[InputSource] = []
for index in input_start.. String raise {
match source {
Inline(text) => text
File(path) => @fs.read_file_to_string(path)
}
}
///|
async fn read_input(source : InputSource) -> String {
match source {
Stdin => @stdio.stdin.read_all().text()
File(path) => @fs.read_file_to_string(path)
}
}
///|
fn run_query(
query : @moonjq.Query,
input : String,
logs : Bool,
) -> Iter[Json] raise {
if logs {
query.eval_logs(input)
} else {
let json = @json.parse(input)
query.eval(json)
}
}
///|
fn render_result(
value : Json,
raw_output : Bool,
compact_output : Bool,
) -> String {
if raw_output {
match value {
String(text) => text
_ => value.stringify(indent=if compact_output { 0 } else { 2 })
}
} else {
value.stringify(indent=if compact_output { 0 } else { 2 })
}
}
///|
async fn write_results(
results : Iter[Json],
raw_output : Bool,
compact_output : Bool,
) -> Unit {
for value in results {
@stdio.stdout.write(render_result(value, raw_output, compact_output))
@stdio.stdout.write("\n")
}
}
///|
async fn main {
let args = @env.args()[1:]
match parse_cli(args) {
Error(msg) => {
@stdio.stderr.write(msg)
if !msg.has_suffix("\n") {
@stdio.stderr.write("\n")
}
@sys.exit(2)
return
}
Run(options) => {
let query_text = read_query(options.query) catch {
err => {
@stdio.stderr.write("moonjq: \{err}\n")
@sys.exit(2)
return
}
}
let query = @moonjq.parse(query_text) catch {
err => {
@stdio.stderr.write("moonjq: \{err}\n")
@sys.exit(3)
return
}
}
try {
if options.null_input {
write_results(
query.eval(null),
options.raw_output,
options.compact_output,
)
} else {
for input_source in options.inputs {
let input = read_input(input_source)
write_results(
run_query(query, input, options.logs),
options.raw_output,
options.compact_output,
)
}
}
} catch {
err => {
@stdio.stderr.write("moonjq: \{err}\n")
@sys.exit(5)
return
}
}
}
}
}