From 309b7583bd84ce2b45109a61b96139054bf8a577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Sat, 8 Apr 2023 20:20:20 +1200 Subject: [PATCH] Add read filter --- crates/cli/src/args.rs | 7 ++-- crates/cli/src/filterer/proglib.rs | 58 +++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index bf15052c..88c18136 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -802,8 +802,11 @@ pub struct Args { /// /// - 'path | metadata' returns file metadata or null if the file does not exist. /// - /// - 'path | read' and 'path read(bytes)' return a string containing the file at path, or - /// the first n bytes of it. Obviously care should be taken not to read large files. + /// - 'path | filesize' returns the size of the file at path, or null if it does not exist. + /// + /// - 'path | read(bytes)' returns a string containing the first n bytes of the file at path. + /// If the file is smaller than n bytes, the whole file is returned. There is no filter to + /// read the whole file at once to encourage limiting the amount of data read and processed. /// /// - 'string | hash', and 'path | hashfile' return the hash of the string or file at path. /// No guarantee is made about the algorithm used: treat it as an opaque value. diff --git a/crates/cli/src/filterer/proglib.rs b/crates/cli/src/filterer/proglib.rs index 5a287753..5c721588 100644 --- a/crates/cli/src/filterer/proglib.rs +++ b/crates/cli/src/filterer/proglib.rs @@ -1,4 +1,9 @@ -use std::{iter::once, sync::Arc}; +use std::{ + fs::File, + io::{BufReader, Read}, + iter::once, + sync::Arc, +}; use dashmap::DashMap; use jaq_core::{CustomFilter, Definitions, Error, Val}; @@ -43,6 +48,17 @@ macro_rules! string_arg { }; } +macro_rules! int_arg { + ($args:expr, $n:expr, $ctx:expr, $val:expr) => { + match $args[$n].run(($ctx.clone(), $val.clone())).next() { + Some(Ok(Val::Int(v))) => Ok(v as _), + Some(Ok(val)) => custom_err(format!("expected int but got {val:?}")), + Some(Err(e)) => Err(e), + None => custom_err("value expected but none found"), + } + }; +} + macro_rules! log_action { ($level:expr, $val:expr) => { match $level.to_ascii_lowercase().as_str() { @@ -170,5 +186,45 @@ pub fn load_watchexec_defs(defs: &mut Definitions) -> miette::Result<()> { }), ); + trace!("jaq: add read filter"); + defs.insert_custom( + "read", + CustomFilter::new(1, { + move |args, (ctx, val)| { + let path = match &val { + Val::Str(v) => v.to_string(), + _ => return_err!(custom_err("expected string (path) but got {val:?}")), + }; + + let bytes = match int_arg!(args, 0, ctx, &val) { + Ok(v) => v, + Err(e) => return_err!(Err(e)), + }; + + Box::new(once(Ok(match File::open(&path) { + Ok(file) => { + let buf_reader = BufReader::new(file); + let mut limited = buf_reader.take(bytes); + let mut buffer = String::with_capacity(bytes as _); + match limited.read_to_string(&mut buffer) { + Ok(read) => { + debug!("jaq: read {read} bytes from {path:?}"); + Val::Str(buffer.into()) + } + Err(err) => { + error!("jaq: failed to read from {path:?}: {err:?}"); + Val::Null + } + } + } + Err(err) => { + error!("jaq: failed to open file {path:?}: {err:?}"); + Val::Null + } + }))) + } + }), + ); + Ok(()) }