Add file_meta and file_size filters

This commit is contained in:
Félix Saparelli 2023-04-08 21:03:19 +12:00
parent 309b7583bd
commit 67ceab9eca
No known key found for this signature in database
2 changed files with 124 additions and 6 deletions

View file

@ -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.
///

View file

@ -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<Definitions> {
@ -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<SystemTime>) -> Option<u64> {
time.ok()
.and_then(|time| time.duration_since(UNIX_EPOCH).ok())
.map(|dur| dur.as_secs())
}