From 67ceab9eca2d73994f3cd0e8413a72918f3a2b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Sat, 8 Apr 2023 21:03:19 +1200 Subject: [PATCH] Add file_meta and file_size filters --- crates/cli/src/args.rs | 6 +- crates/cli/src/filterer/proglib.rs | 124 ++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 6 deletions(-) diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 88c1813..6a16295 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -800,11 +800,11 @@ pub struct Args { /// /// In addition to the jaq stdlib, watchexec adds some custom filter definitions: /// - /// - 'path | metadata' returns file metadata or null if the file does not exist. + /// - 'path | file_meta' returns file metadata or null if the file does not exist. /// - /// - 'path | filesize' returns the size of the file at path, or null if it does not exist. + /// - 'path | file_size' 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. + /// - 'path | file_read(bytes)' returns a string with 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. /// diff --git a/crates/cli/src/filterer/proglib.rs b/crates/cli/src/filterer/proglib.rs index 5c72158..cc86347 100644 --- a/crates/cli/src/filterer/proglib.rs +++ b/crates/cli/src/filterer/proglib.rs @@ -1,13 +1,15 @@ use std::{ - fs::File, + fs::{metadata, File, FileType, Metadata}, io::{BufReader, Read}, iter::once, sync::Arc, + time::{SystemTime, UNIX_EPOCH}, }; use dashmap::DashMap; use jaq_core::{CustomFilter, Definitions, Error, Val}; use miette::miette; +use serde_json::{json, Value}; use tracing::{debug, error, info, trace, warn}; pub fn load_std_defs() -> miette::Result { @@ -186,9 +188,9 @@ pub fn load_watchexec_defs(defs: &mut Definitions) -> miette::Result<()> { }), ); - trace!("jaq: add read filter"); + trace!("jaq: add file_read filter"); defs.insert_custom( - "read", + "file_read", CustomFilter::new(1, { move |args, (ctx, val)| { let path = match &val { @@ -226,5 +228,121 @@ pub fn load_watchexec_defs(defs: &mut Definitions) -> miette::Result<()> { }), ); + trace!("jaq: add file_meta filter"); + defs.insert_custom( + "file_meta", + CustomFilter::new(0, { + move |_, (_, val)| { + let path = match &val { + Val::Str(v) => v.to_string(), + _ => return_err!(custom_err("expected string (path) but got {val:?}")), + }; + + Box::new(once(Ok(match metadata(&path) { + Ok(meta) => Val::from(json_meta(meta)), + Err(err) => { + error!("jaq: failed to open {path:?}: {err:?}"); + Val::Null + } + }))) + } + }), + ); + + trace!("jaq: add file_size filter"); + defs.insert_custom( + "file_size", + CustomFilter::new(0, { + move |_, (_, val)| { + let path = match &val { + Val::Str(v) => v.to_string(), + _ => return_err!(custom_err("expected string (path) but got {val:?}")), + }; + + Box::new(once(Ok(match metadata(&path) { + Ok(meta) => Val::Int(meta.len() as _), + Err(err) => { + error!("jaq: failed to open {path:?}: {err:?}"); + Val::Null + } + }))) + } + }), + ); + Ok(()) } + +fn json_meta(meta: Metadata) -> Value { + let perms = meta.permissions(); + let mut val = json!({ + "type": filetype_str(meta.file_type()), + "size": meta.len(), + "modified": fs_time(meta.modified()), + "accessed": fs_time(meta.accessed()), + "created": fs_time(meta.created()), + "dir": meta.is_dir(), + "file": meta.is_file(), + "symlink": meta.is_symlink(), + "readonly": perms.readonly(), + }); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let map = val.as_object_mut().unwrap(); + map.insert( + "mode".to_string(), + Value::String(format!("{:o}", perms.mode())), + ); + map.insert("mode_byte".to_string(), Value::from(perms.mode())); + map.insert( + "executable".to_string(), + Value::Bool(perms.mode() & 0o111 != 0), + ); + } + + val +} + +fn filetype_str(filetype: FileType) -> &'static str { + #[cfg(unix)] + { + use std::os::unix::fs::FileTypeExt; + if filetype.is_char_device() { + return "char"; + } else if filetype.is_block_device() { + return "block"; + } else if filetype.is_fifo() { + return "fifo"; + } else if filetype.is_socket() { + return "socket"; + } + } + + #[cfg(windows)] + { + use std::os::windows::fs::FileTypeExt; + if filetype.is_symlink_dir() { + return "symdir"; + } else if filetype.is_symlink_file() { + return "symfile"; + } + } + + if filetype.is_dir() { + "dir" + } else if filetype.is_file() { + "file" + } else if filetype.is_symlink() { + "symlink" + } else { + "unknown" + } +} + +fn fs_time(time: std::io::Result) -> Option { + time.ok() + .and_then(|time| time.duration_since(UNIX_EPOCH).ok()) + .map(|dur| dur.as_secs()) +}