Add --all option to path

- Add --all option to path
- Add tests
- Add doc
This commit is contained in:
Dennis Huang
2025-06-02 08:41:33 +08:00
committed by Johannes Altmanninger
parent 8c5de9acfb
commit 7fe92be405
3 changed files with 57 additions and 13 deletions

View File

@@ -13,7 +13,7 @@ Synopsis
path extension GENERAL_OPTIONS [PATH ...]
path filter GENERAL_OPTIONS [-v | --invert]
[-d] [-f] [-l] [-r] [-w] [-x]
[(-t | --type) TYPE] [(-p | --perm) PERMISSION] [PATH ...]
[(-t | --type) TYPE] [(-p | --perm) PERMISSION] [--all] [PATH ...]
path is GENERAL_OPTIONS [(-v | --invert)] [(-t | --type) TYPE]
[-d] [-f] [-l] [-r] [-w] [-x]
[(-p | --perm) PERMISSION] [PATH ...]
@@ -22,7 +22,7 @@ Synopsis
path resolve GENERAL_OPTIONS [PATH ...]
path change-extension GENERAL_OPTIONS EXTENSION [PATH ...]
path sort GENERAL_OPTIONS [-r | --reverse]
[-u | --unique] [--key=basename|dirname|path] [PATH ...]
[-u | --unique] [--key=(basename | dirname | path)] [PATH ...]
GENERAL_OPTIONS
[-z | --null-in] [-Z | --null-out] [-q | --quiet]
@@ -148,7 +148,7 @@ Examples
> echo $path$extension
# reconstructs the original path again.
./foo.mp4
.. _cmd-path-filter:
"filter" subcommand
@@ -158,7 +158,7 @@ Examples
path filter [-z | --null-in] [-Z | --null-out] [-q | --quiet] \
[-d] [-f] [-l] [-r] [-w] [-x] \
[-v | --invert] [(-t | --type) TYPE] [(-p | --perm) PERMISSION] [PATH ...]
[-v | --invert] [(-t | --type) TYPE] [(-p | --perm) PERMISSION] [--all] [PATH ...]
``path filter`` returns all of the given paths that match the given checks. In all cases, the paths need to exist, nonexistent paths are always filtered.
@@ -180,6 +180,10 @@ When a path starts with ``-``, ``path filter`` will prepend ``./`` to avoid it b
It returns 0 if at least one path passed the filter.
With ``--all``, return status 0 (true) if all paths pass the filter, and status 1 (false) if any path fails. This is equivalent to ``not path filter -v``. It produces no output, only a status.
When ``--all`` combined with ``--invert``, it returns status 0 (true) if all paths fail the filter and status 1 (false) if any path passes.
``path is`` is shorthand for ``path filter -q``, i.e. just checking without producing output, see :ref:`The is subcommand <cmd-path-is>`.
Examples
@@ -211,6 +215,9 @@ Examples
>_ path filter -fx $PATH/*
# Prints all possible commands - the first entry of each name is what fish would execute!
>_ path filter --all /usr/bin /usr/argagagji
# This returns 1 (false) because not all paths pass the filter.
.. _cmd-path-is:
"is" subcommand

View File

@@ -154,6 +154,9 @@ struct Options<'args> {
no_ext_valid: bool,
no_ext: bool,
all_valid: bool,
all: bool,
}
#[inline]
@@ -201,14 +204,13 @@ fn construct_short_opts(opts: &Options) -> WString {
if opts.no_ext_valid {
short_opts.push('E');
}
short_opts
}
/// Note that several long flags share the same short flag. That is okay. The caller is expected
/// to indicate that a max of one of the long flags sharing a short flag is valid.
/// Remember: adjust the completions in share/completions/ when options change
const LONG_OPTIONS: [WOption<'static>; 11] = [
const LONG_OPTIONS: [WOption<'static>; 12] = [
wopt(L!("quiet"), NoArgument, 'q'),
wopt(L!("null-in"), NoArgument, 'z'),
wopt(L!("null-out"), NoArgument, 'Z'),
@@ -220,6 +222,7 @@ fn construct_short_opts(opts: &Options) -> WString {
wopt(L!("unique"), NoArgument, 'u'),
wopt(L!("key"), RequiredArgument, NON_OPTION_CHAR),
wopt(L!("no-extension"), NoArgument, 'E'),
wopt(L!("all"), NoArgument, '\x02'),
];
fn parse_opts<'args>(
@@ -339,6 +342,11 @@ fn parse_opts<'args>(
opts.key = w.woptarg;
continue;
}
'\x02' if opts.all_valid => {
opts.all = true;
continue;
}
_ => {
path_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
return Err(STATUS_INVALID_ARGS);
@@ -846,6 +854,7 @@ fn path_filter_maybe_is(
opts.types_valid = true;
opts.perms_valid = true;
opts.invert_valid = true;
opts.all_valid = true;
let mut optind = 0;
parse_opts(&mut opts, &mut optind, 0, args, parser, streams)?;
@@ -873,7 +882,10 @@ fn path_filter_maybe_is(
None
};
for (arg, _) in arguments.filter(|(f, _)| {
// Collect arguments into a Vec so we can use .len()
let arguments_vec: Vec<_> = arguments.collect();
for (arg, _) in arguments_vec.iter().cloned().filter(|(f, _)| {
(opts.perms.is_none() && opts.types.is_none())
|| (filter_path(&opts, f, uid, gid) != opts.invert)
}) {
@@ -881,10 +893,20 @@ fn path_filter_maybe_is(
if opts.perms.is_none() && opts.types.is_none() {
let ok = waccess(&arg, F_OK) == 0;
if ok == opts.invert {
// For --all, fail early if any path does not match the filter.
if opts.all {
return Err(STATUS_CMD_ERROR);
}
continue;
}
}
n_transformed += 1;
if opts.all {
// For --all, do not output paths, just check all must match.
continue;
}
// We *know* this is a filename,
// and so if it starts with a `-` we *know* it is relative
// to $PWD. So we can add `./`.
@@ -895,12 +917,16 @@ fn path_filter_maybe_is(
} else {
path_out(streams, &opts, arg);
}
n_transformed += 1;
if opts.quiet {
return Ok(SUCCESS);
};
}
if opts.all && n_transformed != arguments_vec.len() {
// We have a filter and some paths didn't match.
// Return Err if we have a filter and some paths didn't match.
return Err(STATUS_CMD_ERROR);
}
if n_transformed > 0 {
Ok(SUCCESS)
} else {
@@ -922,7 +948,6 @@ pub fn path(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
return Err(STATUS_INVALID_ARGS);
};
let argc = args.len();
if argc <= 1 {
streams
.err

View File

@@ -105,6 +105,21 @@ path filter -vf bin argagagji
# CHECK: bin
# CHECK: argagagji
# With --all, return true if all paths are passed.
path filter --all bin bin/bash
echo $status
# CHECK: 0
path filter --all bin argagagji
echo $status
# CHECK: 1
# With --all and --invert, return true if none of paths is passed.
path filter --all --invert bin bin/bash
echo $status
# CHECK: 1
path filter --all --invert argagagji argagagji2
echo $status
# CHECK: 0
path filter --type file bin bin/fish
# Only fish is a file
# CHECK: bin/fish
@@ -273,8 +288,6 @@ path sort --unique --key=basename {def,abc}/{456,123,789} def/{abc,def,0} abc/{f
# CHECK: def/def
# CHECK: abc/foo
# Symlink loop.
# It goes brrr.
ln -s target link
@@ -288,13 +301,12 @@ test (path resolve link) = (pwd -P)/link
and echo link resolves to link
# CHECK: link resolves to link
# path mtime
# These tests deal with *time*, so we have to account
# for slow systems (like CI).
# So we should only test with a lot of slack.
echo bananana >> foo
echo bananana >>foo
test (math abs (date +%s) - (path mtime foo)) -lt 20
or echo MTIME IS BOGUS