diff --git a/doc_src/cmds/path.rst b/doc_src/cmds/path.rst index fb000194d..461204398 100644 --- a/doc_src/cmds/path.rst +++ b/doc_src/cmds/path.rst @@ -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 `. 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 diff --git a/src/builtins/path.rs b/src/builtins/path.rs index a4b3fd13b..14fd29125 100644 --- a/src/builtins/path.rs +++ b/src/builtins/path.rs @@ -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 diff --git a/tests/checks/path.fish b/tests/checks/path.fish index 62812571a..4d051dc64 100644 --- a/tests/checks/path.fish +++ b/tests/checks/path.fish @@ -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