-
Notifications
You must be signed in to change notification settings - Fork 578
Description
So I just did a little experiment by adding two functons to my own just repo: json_stringify()
and jaq()
(rust implementation of jq)
They worked fabulously. I would like to propose incorporating them into the official just
.
These alone can fill a huge portion of the gap that is needed to make a no-shell option viable (in fact, that's what I used them for), such as #1570 and #2458, as well as related use cases discussed in #528, #537, #2379, #2080, and probably many others.
jq_filter := 'split(" ") | .[] | <whatever else I want to do here>'
jq_input := json_stringify(my_input_str)
jq_output := jaq(jq_input, jq_filter)
recipe:
blah {{ jq_output }} blah
Granted, using jq is a particular approach that may or may suit the user and for any given case might not be the perfect choice compared with potential external alternatives-- but consider:
- it is extremely versatile and does not require a shell
- in may cases is provides a built-in solution where no other built-in solution exists and there is no assurance that an external solution exists
- its implementation is clean and unintrusive (merely add a couple functions totalling 73 LOC in src/function.rs)
- it is does not conflict with any future alternative other solution
- it does not introduce any platform dependencies or conflicts (the added code compiled out-of-the-box even to wasm)
- it incorporates a well-established and widely-used library (treating jq and jaq as one and the same for this purpose)
Thoughts?
FYI, the changes in function.rs were merely as follows (note: this is a super quick-and-dirty whip-up for illustrative purposes). Happy to submit a PR:
diff --git a/src/function.rs b/src/function.rs
index 66e7c6e..82c40fc 100644
--- a/src/function.rs
+++ b/src/function.rs
@@ -71,6 +71,8 @@ pub(crate) fn get(name: &str) -> Option<Function> {
"invocation_directory_native" => Nullary(invocation_directory_native),
"is_dependency" => Nullary(is_dependency),
"join" => BinaryPlus(join),
+ "jaq" => Binary(jaq),
+ "json_stringify" => UnaryPlus(json),
"just_executable" => Nullary(just_executable),
"just_pid" => Nullary(just_pid),
"justfile" => Nullary(justfile),
@@ -369,6 +371,60 @@ fn prepend(_context: Context, prefix: &str, s: &str) -> FunctionResult {
)
}
+ // invalid_date is a helper function for jaq, probably should be located elsewhere
+fn invalid_data(e: impl std::error::Error + Send + Sync + 'static) -> std::io::Error {
+ use std::io;
+ io::Error::new(io::ErrorKind::InvalidData, e)
+}
+
+fn jaq(_context: Context, input_str: &str, filter_str: &str) -> FunctionResult {
+ use jaq_core::{load, Compiler, Ctx, RcIter};
+ use jaq_json::Val;
+// println!("input: {}", input_str);
+// println!("filter: {}", filter_str);
+
+ let json = |s: String| {
+ use hifijson::token::Lex;
+ hifijson::SliceLexer::new(s.as_bytes())
+ .exactly_one(Val::parse)
+ .map_err(invalid_data)
+ };
+
+ let input = json(input_str.to_string());
+ let program = File { code: filter_str, path: () };
+
+ use load::{Arena, File, Loader};
+
+ let loader = Loader::new(jaq_std::defs().chain(jaq_json::defs()));
+ let arena = Arena::default();
+
+ // parse the filter
+ let modules = loader.load(&arena, program).unwrap();
+
+ // compile the filter
+ let filter = Compiler::default()
+ .with_funs(jaq_std::funs().chain(jaq_json::funs()))
+ .compile(modules)
+ .unwrap();
+
+ let inputs = RcIter::new(core::iter::empty());
+
+ // iterator over the output values
+ let mut out = filter.run((Ctx::new([], &inputs), input.unwrap()));
+
+ // collect result values, each on a separate line
+ let mut output_str = String::new();
+ while let Some(value) = out.next() {
+ output_str.push_str(value.unwrap().to_string().as_str());
+ output_str.push('\n');
+ }
+ if output_str.ends_with('\n') {
+ output_str.pop();
+ }
+
+ Ok(output_str)
+}
+
fn join(_context: Context, base: &str, with: &str, and: &[String]) -> FunctionResult {
let mut result = Utf8Path::new(base).join(with);
for arg in and {
@@ -434,6 +490,22 @@ fn justfile_directory(context: Context) -> FunctionResult {
})
}
+
+fn json_stringify(_context: Context, first_arg: &str, more_args: &[String]) -> FunctionResult {
+ use serde_json::json;
+ let result = if more_args.is_empty() {
+ // If no additional arguments, return JSON stringified version of the first argument
+ json!(first_arg).to_string()
+ } else {
+ // If additional arguments exist, create a JSON array with the first argument followed by the additional arguments
+ let mut args = vec![first_arg.to_string()];
+ args.extend_from_slice(more_args);
+ json!(args).to_string()
+ };
+
+ Ok(result)
+}
+
fn kebabcase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_kebab_case())
}