2022-06-17 10:08:24 +02:00
use std ::path ::{ Path , PathBuf } ;
use std ::time ::Duration ;
use anyhow ::anyhow ;
use clap ::{
2022-10-15 07:14:03 +02:00
error ::ErrorKind , value_parser , Arg , ArgAction , ArgGroup , ArgMatches , Command , Parser ,
ValueEnum ,
2022-06-17 10:08:24 +02:00
} ;
#[ cfg(feature = " completions " ) ]
use clap_complete ::Shell ;
use normpath ::PathExt ;
use crate ::error ::print_error ;
use crate ::exec ::CommandSet ;
use crate ::filesystem ;
#[ cfg(unix) ]
use crate ::filter ::OwnerFilter ;
use crate ::filter ::SizeFilter ;
#[ derive(Parser) ]
2022-10-12 06:27:57 +02:00
#[ command(
2022-11-01 21:00:32 +01:00
name = " fd " ,
2022-06-17 10:08:24 +02:00
version ,
2022-11-01 21:02:37 +01:00
about = " A program to find entries in your filesystem " ,
2022-10-09 09:00:48 +02:00
after_long_help = " Bugs can be reported on GitHub: https://github.com/sharkdp/fd/issues " ,
2022-11-01 21:14:16 +01:00
max_term_width = 98 ,
2022-10-09 07:51:45 +02:00
args_override_self = true ,
group ( ArgGroup ::new ( " execs " ) . args ( & [ " exec " , " exec_batch " , " list_details " ] ) . conflicts_with_all ( & [
2023-10-18 07:00:56 +02:00
" max_results " , " quiet " , " max_one_result " ] ) ) ,
2022-06-17 10:08:24 +02:00
) ]
pub struct Opts {
2022-11-08 08:43:42 +01:00
/// Include hidden directories and files in the search results (default:
/// hidden files and directories are skipped). Files and directories are
/// considered to be hidden if their name starts with a `.` sign (dot).
/// The flag can be overridden with --no-hidden.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'H' ,
2022-11-08 08:43:42 +01:00
help = " Search hidden files and directories " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub hidden : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:03:55 +01:00
/// Overrides --hidden
#[ arg(long, overrides_with = " hidden " , hide = true, action = ArgAction::SetTrue) ]
no_hidden : ( ) ,
2022-11-08 08:43:42 +01:00
/// Show search results from files and directories that would otherwise be
/// ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file.
/// The flag can be overridden with --ignore.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'I' ,
2022-11-08 08:43:42 +01:00
help = " Do not respect .(git|fd)ignore files " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub no_ignore : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:03:55 +01:00
/// Overrides --no-ignore
#[ arg(long, overrides_with = " no_ignore " , hide = true, action = ArgAction::SetTrue) ]
ignore : ( ) ,
2022-11-08 08:43:42 +01:00
///Show search results from files and directories that would otherwise be
/// ignored by '.gitignore' files. The flag can be overridden with --ignore-vcs.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Do not respect .gitignore files " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub no_ignore_vcs : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:03:55 +01:00
/// Overrides --no-ignore-vcs
#[ arg(long, overrides_with = " no_ignore_vcs " , hide = true, action = ArgAction::SetTrue) ]
ignore_vcs : ( ) ,
2023-01-13 22:52:05 +01:00
/// Do not require a git repository to respect gitignores.
/// By default, fd will only respect global gitignore rules, .gitignore rules,
/// and local exclude rules if fd detects that you are searching inside a
/// git repository. This flag allows you to relax this restriction such that
/// fd will respect all git related ignore rules regardless of whether you're
/// searching in a git repository or not.
///
///
/// This flag can be disabled with --require-git.
#[ arg(
long ,
overrides_with = " require_git " ,
hide_short_help = true ,
// same description as ripgrep's flag: ripgrep/crates/core/app.rs
long_help
) ]
pub no_require_git : bool ,
/// Overrides --no-require-git
#[ arg(long, overrides_with = " no_require_git " , hide = true, action = ArgAction::SetTrue) ]
require_git : ( ) ,
2022-11-08 08:43:42 +01:00
/// Show search results from files and directories that would otherwise be
/// ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Do not respect .(git|fd)ignore files in parent directories " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub no_ignore_parent : bool ,
2022-11-01 20:18:17 +01:00
2022-06-17 10:08:24 +02:00
/// Do not respect the global ignore file
2022-10-12 06:27:57 +02:00
#[ arg(long, hide = true) ]
2022-06-17 10:08:24 +02:00
pub no_global_ignore_file : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Perform an unrestricted search, including ignored and hidden files. This is
/// an alias for '--no-ignore --hidden'.
2022-11-01 09:30:22 +01:00
#[ arg(long = " unrestricted " , short = 'u', overrides_with_all(& [ " ignore " , " no_hidden " ] ), action(ArgAction::Count), hide_short_help = true,
2022-11-08 08:43:42 +01:00
help = " Unrestricted search, alias for '--no-ignore --hidden' " ,
long_help ,
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
rg_alias_hidden_ignore : u8 ,
2022-11-01 20:18:17 +01:00
2022-06-17 10:08:24 +02:00
/// Case-sensitive search (default: smart case)
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 's' ,
overrides_with ( " ignore_case " ) ,
long_help = " Perform a case-sensitive search. By default, fd uses case-insensitive \
searches , unless the pattern contains an uppercase character ( smart \
case ) . "
) ]
2022-06-17 10:08:24 +02:00
pub case_sensitive : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Perform a case-insensitive search. By default, fd uses case-insensitive
/// searches, unless the pattern contains an uppercase character (smart
/// case).
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'i' ,
overrides_with ( " case_sensitive " ) ,
2022-11-08 08:43:42 +01:00
help = " Case-insensitive search (default: smart case) " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub ignore_case : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Perform a glob-based search instead of a regular expression search.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'g' ,
conflicts_with ( " fixed_strings " ) ,
2022-11-08 08:43:42 +01:00
help = " Glob-based search (default: regular expression) " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub glob : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Perform a regular-expression based search (default). This can be used to
/// override --glob.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
overrides_with ( " glob " ) ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Regular-expression based search (default) " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub regex : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Treat the pattern as a literal string instead of a regular expression. Note
/// that this also performs substring comparison. If you want to match on an
/// exact filename, consider using '--glob'.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'F' ,
alias = " literal " ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Treat pattern as literal string stead of regex " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub fixed_strings : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Add additional required search patterns, all of which must be matched. Multiple
/// additional patterns can be specified. The patterns are regular
/// expressions, unless '--glob' or '--fixed-strings' is used.
2022-11-21 20:53:24 +01:00
#[ arg(
long = " and " ,
value_name = " pattern " ,
2022-11-08 08:43:42 +01:00
help = " Additional search patterns that need to be matched " ,
long_help ,
2022-11-21 21:02:36 +01:00
hide_short_help = true ,
allow_hyphen_values = true
2022-11-21 20:53:24 +01:00
) ]
pub exprs : Option < Vec < String > > ,
2022-11-08 08:43:42 +01:00
/// Shows the full path starting from the root as opposed to relative paths.
/// The flag can be overridden with --relative-path.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'a' ,
2022-11-08 08:43:42 +01:00
help = " Show absolute instead of relative paths " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub absolute_path : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:03:55 +01:00
/// Overrides --absolute-path
#[ arg(long, overrides_with = " absolute_path " , hide = true, action = ArgAction::SetTrue) ]
relative_path : ( ) ,
2022-11-08 08:43:42 +01:00
/// Use a detailed listing format like 'ls -l'. This is basically an alias
/// for '--exec-batch ls -l' with some additional 'ls' options. This can be
/// used to see more metadata, to show symlink targets and to achieve a
/// deterministic sort order.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'l' ,
conflicts_with ( " absolute_path " ) ,
2022-11-08 08:43:42 +01:00
help = " Use a long listing format with file metadata " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub list_details : bool ,
2022-11-01 20:18:17 +01:00
2022-06-17 10:08:24 +02:00
/// Follow symbolic links
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'L' ,
alias = " dereference " ,
long_help = " By default, fd does not descend into symlinked directories. Using this \
flag , symbolic links are also traversed . \
Flag can be overriden with - - no - follow . "
) ]
2022-06-17 10:08:24 +02:00
pub follow : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:03:55 +01:00
/// Overrides --follow
#[ arg(long, overrides_with = " follow " , hide = true, action = ArgAction::SetTrue) ]
no_follow : ( ) ,
2022-11-08 08:43:42 +01:00
/// By default, the search pattern is only matched against the filename (or directory name). Using this flag, the pattern is matched against the full (absolute) path. Example:
/// fd --glob -p '**/.git/config'
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'p' ,
2022-11-08 08:43:42 +01:00
help = " Search full abs. path (default: filename only) " ,
long_help ,
verbatim_doc_comment
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub full_path : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Separate search results by the null character (instead of newlines).
/// Useful for piping results to 'xargs'.
2022-10-12 06:27:57 +02:00
#[ arg(
2022-06-17 10:08:24 +02:00
long = " print0 " ,
short = '0' ,
2022-10-09 07:51:45 +02:00
conflicts_with ( " list_details " ) ,
2022-11-01 09:30:22 +01:00
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Separate search results by the null character " ,
long_help
2022-06-17 10:08:24 +02:00
) ]
pub null_separator : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Limit the directory traversal to a given depth. By default, there is no
/// limit on the search depth.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
short = 'd' ,
value_name = " depth " ,
alias ( " maxdepth " ) ,
2022-11-08 08:43:42 +01:00
help = " Set maximum search depth (default: none) " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
max_depth : Option < usize > ,
2022-11-01 20:18:17 +01:00
2022-06-17 10:08:24 +02:00
/// Only show search results starting at the given depth.
2022-11-08 08:43:42 +01:00
/// See also: '--max-depth' and '--exact-depth'
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
value_name = " depth " ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Only show search results starting at the given depth. " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
min_depth : Option < usize > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Only show search results at the exact given depth. This is an alias for
/// '--min-depth <depth> --max-depth <depth>'.
2022-11-01 09:30:22 +01:00
#[ arg(long, value_name = " depth " , hide_short_help = true, conflicts_with_all(& [ " max_depth " , " min_depth " ] ),
2022-11-08 08:43:42 +01:00
help = " Only show search results at the exact given depth " ,
long_help ,
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
exact_depth : Option < usize > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Exclude files/directories that match the given glob pattern. This
/// overrides any other ignore logic. Multiple exclude patterns can be
/// specified.
///
/// Examples:
/// {n} --exclude '*.pyc'
/// {n} --exclude node_modules
2022-11-01 21:17:48 +01:00
#[ arg(
long ,
short = 'E' ,
value_name = " pattern " ,
2022-11-08 08:43:42 +01:00
help = " Exclude entries that match the given glob pattern " ,
long_help
2022-11-01 21:17:48 +01:00
) ]
pub exclude : Vec < String > ,
2022-06-17 10:08:24 +02:00
/// Do not traverse into directories that match the search criteria. If
/// you want to exclude specific directories, use the '--exclude=…' option.
2022-11-01 09:30:22 +01:00
#[ arg(long, hide_short_help = true, conflicts_with_all(& [ " size " , " exact_depth " ] ),
2022-11-08 08:43:42 +01:00
long_help ,
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub prune : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Filter the search by type:
/// {n} 'f' or 'file': regular files
/// {n} 'd' or 'directory': directories
/// {n} 'l' or 'symlink': symbolic links
/// {n} 's' or 'socket': socket
/// {n} 'p' or 'pipe': named pipe (FIFO)
2023-06-14 15:28:24 +02:00
/// {n} 'b' or 'block-device': block device
/// {n} 'c' or 'char-device': character device
2022-11-08 08:43:42 +01:00
/// {n}{n} 'x' or 'executable': executables
/// {n} 'e' or 'empty': empty files or directories
///
/// This option can be specified more than once to include multiple file types.
/// Searching for '--type file --type symlink' will show both regular files as
/// well as symlinks. Note that the 'executable' and 'empty' filters work differently:
/// '--type executable' implies '--type file' by default. And '--type empty' searches
/// for empty files and directories, unless either '--type file' or '--type directory'
/// is specified in addition.
///
/// Examples:
/// {n} - Only search for files:
/// {n} fd --type file …
/// {n} fd -tf …
/// {n} - Find both files and symlinks
/// {n} fd --type file --type symlink …
/// {n} fd -tf -tl …
/// {n} - Find executable files:
/// {n} fd --type executable
/// {n} fd -tx
/// {n} - Find empty files:
/// {n} fd --type empty --type file
/// {n} fd -te -tf
/// {n} - Find empty directories:
/// {n} fd --type empty --type directory
/// {n} fd -te -td
2022-10-12 06:27:57 +02:00
#[ arg(
2022-09-08 09:18:04 +02:00
long = " type " ,
short = 't' ,
value_name = " filetype " ,
hide_possible_values = true ,
value_enum ,
2022-11-08 08:43:42 +01:00
help = " Filter by type: file (f), directory (d), symlink (l), \
2023-06-14 15:28:24 +02:00
executable ( x ) , empty ( e ) , socket ( s ) , pipe ( p ) , \
char - device ( c ) , block - device ( b ) " ,
2022-11-08 08:43:42 +01:00
long_help
2022-09-08 09:18:04 +02:00
) ]
2022-06-17 10:08:24 +02:00
pub filetype : Option < Vec < FileType > > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// (Additionally) filter search results by their file extension. Multiple
/// allowable file extensions can be specified.
///
/// If you want to search for files without extension,
/// you can use the regex '^[^.]+$' as a normal search pattern.
2022-11-01 09:30:22 +01:00
#[ arg(
long = " extension " ,
short = 'e' ,
value_name = " ext " ,
2022-11-08 08:43:42 +01:00
help = " Filter by file extension " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub extensions : Option < Vec < String > > ,
2022-11-08 08:43:42 +01:00
/// Limit results based on the size of files using the format <+-><NUM><UNIT>.
/// '+': file size must be greater than or equal to this
/// '-': file size must be less than or equal to this
///
/// If neither '+' nor '-' is specified, file size must be exactly equal to this.
/// 'NUM': The numeric size (e.g. 500)
/// 'UNIT': The units for NUM. They are not case-sensitive.
/// Allowed unit values:
/// 'b': bytes
/// 'k': kilobytes (base ten, 10^3 = 1000 bytes)
/// 'm': megabytes
/// 'g': gigabytes
/// 't': terabytes
/// 'ki': kibibytes (base two, 2^10 = 1024 bytes)
/// 'mi': mebibytes
/// 'gi': gibibytes
/// 'ti': tebibytes
2022-11-01 09:30:22 +01:00
#[ arg(long, short = 'S', value_parser = SizeFilter::from_string, allow_hyphen_values = true, verbatim_doc_comment, value_name = " size " ,
2022-11-08 08:43:42 +01:00
help = " Limit results based on the size of files " ,
long_help ,
verbatim_doc_comment ,
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub size : Vec < SizeFilter > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Filter results based on the file modification time. Files with modification times
/// greater than the argument are returned. The argument can be provided
/// as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min).
/// If the time is not specified, it defaults to 00:00:00.
2022-11-27 07:45:08 +01:00
/// '--change-newer-than', '--newer', or '--changed-after' can be used as aliases.
2022-11-08 08:43:42 +01:00
///
/// Examples:
/// {n} --changed-within 2weeks
/// {n} --change-newer-than '2018-10-27 10:00:00'
/// {n} --newer 2018-10-27
2022-11-27 07:45:08 +01:00
/// {n} --changed-after 1day
2022-10-12 06:27:57 +02:00
#[ arg(
2022-06-17 10:08:24 +02:00
long ,
alias ( " change-newer-than " ) ,
alias ( " newer " ) ,
2022-11-15 06:00:11 +01:00
alias ( " changed-after " ) ,
2022-06-17 10:08:24 +02:00
value_name = " date|dur " ,
2022-11-08 08:43:42 +01:00
help = " Filter by file modification time (newer than) " ,
long_help
2022-06-17 10:08:24 +02:00
) ]
pub changed_within : Option < String > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Filter results based on the file modification time. Files with modification times
/// less than the argument are returned. The argument can be provided
/// as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min).
/// '--change-older-than' or '--older' can be used as aliases.
///
/// Examples:
/// {n} --changed-before '2018-10-27 10:00:00'
/// {n} --change-older-than 2weeks
/// {n} --older 2018-10-27
2022-10-12 06:27:57 +02:00
#[ arg(
2022-06-17 10:08:24 +02:00
long ,
alias ( " change-older-than " ) ,
alias ( " older " ) ,
value_name = " date|dur " ,
2022-11-08 08:43:42 +01:00
help = " Filter by file modification time (older than) " ,
long_help
2022-06-17 10:08:24 +02:00
) ]
pub changed_before : Option < String > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Filter files by their user and/or group.
/// Format: [(user|uid)][:(group|gid)]. Either side is optional.
/// Precede either side with a '!' to exclude files instead.
///
/// Examples:
/// {n} --owner john
/// {n} --owner :students
/// {n} --owner '!john:students'
2022-11-01 21:17:48 +01:00
#[ cfg(unix) ]
#[ arg(long, short = 'o', value_parser = OwnerFilter::from_string, value_name = " user:group " ,
2022-11-08 08:43:42 +01:00
help = " Filter by owning user and/or group " ,
long_help ,
2022-11-01 21:17:48 +01:00
) ]
pub owner : Option < OwnerFilter > ,
#[ command(flatten) ]
pub exec : Exec ,
2022-11-08 08:43:42 +01:00
/// Maximum number of arguments to pass to the command given with -X.
/// If the number of results is greater than the given size,
/// the command given with -X is run again with remaining arguments.
/// A batch size of zero means there is no limit (default), but note
/// that batching might still happen due to OS restrictions on the
/// maximum length of command lines.
2022-11-01 21:17:48 +01:00
#[ arg(
long ,
value_name = " size " ,
hide_short_help = true ,
requires ( " exec_batch " ) ,
value_parser = value_parser! ( usize ) ,
default_value_t ,
2022-11-08 08:43:42 +01:00
help = " Max number of arguments to run as a batch size with -X " ,
long_help ,
2022-11-01 21:17:48 +01:00
) ]
pub batch_size : usize ,
2022-11-08 08:43:42 +01:00
/// Add a custom ignore-file in '.gitignore' format. These files have a low precedence.
2022-11-01 21:17:48 +01:00
#[ arg(
long ,
value_name = " path " ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Add a custom ignore-file in '.gitignore' format " ,
long_help
2022-11-01 21:17:48 +01:00
) ]
pub ignore_file : Vec < PathBuf > ,
2022-11-08 08:43:42 +01:00
/// Declare when to use color for the pattern match output
2022-11-01 21:17:48 +01:00
#[ arg(
long ,
short = 'c' ,
value_enum ,
default_value_t = ColorWhen ::Auto ,
value_name = " when " ,
2022-11-08 08:43:42 +01:00
help = " When to use colors " ,
long_help ,
2022-11-01 21:17:48 +01:00
) ]
pub color : ColorWhen ,
/// Set number of threads to use for searching & executing (default: number
/// of available CPU cores)
2022-11-02 13:32:22 +01:00
#[ arg(long, short = 'j', value_name = " num " , hide_short_help = true, value_parser = clap::value_parser!(u32).range(1..)) ]
2022-11-01 21:17:48 +01:00
pub threads : Option < u32 > ,
/// Milliseconds to buffer before streaming search results to console
///
/// Amount of time in milliseconds to buffer, before streaming the search
/// results to the console.
#[ arg(long, hide = true, value_parser = parse_millis) ]
pub max_buffer_time : Option < Duration > ,
2022-11-08 08:43:42 +01:00
///Limit the number of search results to 'count' and quit immediately.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
value_name = " count " ,
hide_short_help = true ,
2023-10-05 14:10:37 +02:00
overrides_with ( " max_one_result " ) ,
2022-11-08 08:43:42 +01:00
help = " Limit the number of search results " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
max_results : Option < usize > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Limit the search to a single result and quit immediately.
/// This is an alias for '--max-results=1'.
2022-11-01 09:30:22 +01:00
#[ arg(
short = '1' ,
hide_short_help = true ,
overrides_with ( " max_results " ) ,
2022-11-08 08:43:42 +01:00
help = " Limit search to a single result " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
max_one_result : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// When the flag is present, the program does not print anything and will
/// return with an exit code of 0 if there is at least one match. Otherwise, the
/// exit code will be 1.
/// '--has-results' can be used as an alias.
2022-10-12 06:27:57 +02:00
#[ arg(
2022-07-18 10:19:11 +02:00
long ,
short = 'q' ,
alias = " has-results " ,
hide_short_help = true ,
2022-11-01 09:30:22 +01:00
conflicts_with ( " max_results " ) ,
2022-11-08 08:43:42 +01:00
help = " Print nothing, exit code 0 if match found, 1 otherwise " ,
long_help
2022-07-18 10:19:11 +02:00
) ]
2022-06-17 10:08:24 +02:00
pub quiet : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Enable the display of filesystem errors for situations such as
/// insufficient permissions or dead symlinks.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Show filesystem errors " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub show_errors : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Change the current working directory of fd to the provided path. This
/// means that search results will be shown with respect to the given base
/// path. Note that relative paths which are passed to fd via the positional
/// <path> argument or the '--search-path' option will also be resolved
/// relative to this directory.
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
value_name = " path " ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Change current working directory " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub base_directory : Option < PathBuf > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// the search pattern which is either a regular expression (default) or a glob
/// pattern (if --glob is used). If no pattern has been specified, every entry
/// is considered a match. If your pattern starts with a dash (-), make sure to
/// pass '--' first, or it will be considered as a flag (fd -- '-foo').
2022-11-01 09:30:22 +01:00
#[ arg(
default_value = " " ,
hide_default_value = true ,
value_name = " pattern " ,
2022-11-08 08:43:42 +01:00
help = " the search pattern (a regular expression, unless '--glob' is used; optional) " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub pattern : String ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Set the path separator to use when printing file paths. The default is
/// the OS-specific separator ('/' on Unix, '\' on Windows).
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
value_name = " separator " ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Set path separator when printing file paths " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
pub path_separator : Option < String > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// The directory where the filesystem search is rooted (optional). If
/// omitted, search the current working directory.
2022-11-01 09:30:22 +01:00
#[ arg(action = ArgAction::Append,
value_name = " path " ,
2022-11-08 08:43:42 +01:00
help = " the root directories for the filesystem search (optional) " ,
long_help ,
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
path : Vec < PathBuf > ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// Provide paths to search as an alternative to the positional <path>
/// argument. Changes the usage to `fd [OPTIONS] --search-path <path>
/// --search-path <path2> [<pattern>]`
2022-11-01 09:30:22 +01:00
#[ arg(
long ,
conflicts_with ( " path " ) ,
value_name = " search-path " ,
hide_short_help = true ,
2022-11-08 08:43:42 +01:00
help = " Provides paths to search as an alternative to the positional <path> argument " ,
long_help
2022-11-01 09:30:22 +01:00
) ]
2022-06-17 10:08:24 +02:00
search_path : Vec < PathBuf > ,
2022-11-01 20:18:17 +01:00
2022-10-09 09:00:48 +02:00
/// By default, relative paths are prefixed with './' when -x/--exec,
/// -X/--exec-batch, or -0/--print0 are given, to reduce the risk of a
/// path starting with '-' being treated as a command line option. Use
/// this flag to disable this behaviour.
2022-11-08 08:43:42 +01:00
#[ arg(long, conflicts_with_all(& [ " path " , " search_path " ] ), hide_short_help = true, long_help) ]
2022-06-17 10:08:24 +02:00
pub strip_cwd_prefix : bool ,
2022-11-01 20:18:17 +01:00
2022-11-08 08:43:42 +01:00
/// By default, fd will traverse the file system tree as far as other options
/// dictate. With this flag, fd ensures that it does not descend into a
/// different file system than the one it started in. Comparable to the -mount
/// or -xdev filters of find(1).
2022-06-17 10:08:24 +02:00
#[ cfg(any(unix, windows)) ]
2022-11-08 08:43:42 +01:00
#[ arg(long, aliases(& [ " mount " , " xdev " ] ), hide_short_help = true, long_help) ]
2022-06-17 10:08:24 +02:00
pub one_file_system : bool ,
#[ cfg(feature = " completions " ) ]
2022-10-12 06:27:57 +02:00
#[ arg(long, hide = true, exclusive = true) ]
2022-06-17 10:08:24 +02:00
gen_completions : Option < Option < Shell > > ,
}
impl Opts {
pub fn search_paths ( & self ) -> anyhow ::Result < Vec < PathBuf > > {
// would it make sense to concatenate these?
let paths = if ! self . path . is_empty ( ) {
& self . path
} else if ! self . search_path . is_empty ( ) {
& self . search_path
} else {
let current_directory = Path ::new ( " . " ) ;
ensure_current_directory_exists ( current_directory ) ? ;
return Ok ( vec! [ self . normalize_path ( current_directory ) ] ) ;
} ;
Ok ( paths
. iter ( )
. filter_map ( | path | {
2022-07-26 06:27:26 +02:00
if filesystem ::is_existing_directory ( path ) {
2022-06-17 10:08:24 +02:00
Some ( self . normalize_path ( path ) )
} else {
print_error ( format! (
" Search path '{}' is not a directory. " ,
path . to_string_lossy ( )
) ) ;
None
}
} )
. collect ( ) )
}
fn normalize_path ( & self , path : & Path ) -> PathBuf {
if self . absolute_path {
filesystem ::absolute_path ( path . normalize ( ) . unwrap ( ) . as_path ( ) ) . unwrap ( )
} else {
path . to_path_buf ( )
}
}
pub fn no_search_paths ( & self ) -> bool {
self . path . is_empty ( ) & & self . search_path . is_empty ( )
}
#[ inline ]
pub fn rg_alias_ignore ( & self ) -> bool {
self . rg_alias_hidden_ignore > 0
}
pub fn max_depth ( & self ) -> Option < usize > {
self . max_depth . or ( self . exact_depth )
}
pub fn min_depth ( & self ) -> Option < usize > {
self . min_depth . or ( self . exact_depth )
}
pub fn threads ( & self ) -> usize {
2022-10-12 07:19:17 +02:00
// This will panic if the number of threads passed in is more than usize::MAX in an environment
// where usize is less than 32 bits (for example 16-bit architectures). It's pretty
// unlikely fd will be running in such an environment, and even more unlikely someone would
// be trying to use that many threads on such an environment, so I think panicing is an
// appropriate way to handle that.
2022-10-15 07:14:03 +02:00
std ::cmp ::max (
self . threads
. map_or_else ( num_cpus ::get , | n | n . try_into ( ) . expect ( " too many threads " ) ) ,
1 ,
)
2022-06-17 10:08:24 +02:00
}
pub fn max_results ( & self ) -> Option < usize > {
2022-07-18 10:19:11 +02:00
self . max_results
. filter ( | & m | m > 0 )
2023-01-19 07:31:12 +01:00
. or_else ( | | self . max_one_result . then_some ( 1 ) )
2022-06-17 10:08:24 +02:00
}
#[ cfg(feature = " completions " ) ]
pub fn gen_completions ( & self ) -> anyhow ::Result < Option < Shell > > {
self . gen_completions
. map ( | maybe_shell | match maybe_shell {
Some ( sh ) = > Ok ( sh ) ,
2022-11-08 08:03:55 +01:00
None = > {
Shell ::from_env ( ) . ok_or_else ( | | anyhow! ( " Unable to get shell from environment " ) )
}
2022-06-17 10:08:24 +02:00
} )
. transpose ( )
}
}
2022-07-18 10:19:11 +02:00
#[ derive(Copy, Clone, PartialEq, Eq, ValueEnum) ]
2022-06-17 10:08:24 +02:00
pub enum FileType {
2022-10-12 06:27:57 +02:00
#[ value(alias = " f " ) ]
2022-06-17 10:08:24 +02:00
File ,
2022-10-12 06:27:57 +02:00
#[ value(alias = " d " ) ]
2022-06-17 10:08:24 +02:00
Directory ,
2022-10-12 06:27:57 +02:00
#[ value(alias = " l " ) ]
2022-06-17 10:08:24 +02:00
Symlink ,
2023-06-14 15:28:24 +02:00
#[ value(alias = " b " ) ]
BlockDevice ,
#[ value(alias = " c " ) ]
CharDevice ,
2022-11-11 14:36:16 +01:00
/// A file which is executable by the current effective user
2022-10-12 06:27:57 +02:00
#[ value(alias = " x " ) ]
2022-06-17 10:08:24 +02:00
Executable ,
2022-10-12 06:27:57 +02:00
#[ value(alias = " e " ) ]
2022-06-17 10:08:24 +02:00
Empty ,
2022-10-12 06:27:57 +02:00
#[ value(alias = " s " ) ]
2022-06-17 10:08:24 +02:00
Socket ,
2022-10-12 06:27:57 +02:00
#[ value(alias = " p " ) ]
2022-06-17 10:08:24 +02:00
Pipe ,
}
2022-07-18 10:19:11 +02:00
#[ derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum) ]
2022-06-17 10:08:24 +02:00
pub enum ColorWhen {
/// show colors if the output goes to an interactive console (default)
Auto ,
/// always use colorized output
Always ,
/// do not use colorized output
Never ,
}
2022-07-26 06:23:47 +02:00
impl ColorWhen {
pub fn as_str ( & self ) -> & 'static str {
use ColorWhen ::* ;
match * self {
Auto = > " auto " ,
Never = > " never " ,
2022-11-01 09:30:22 +01:00
Always = > " always " ,
2022-07-26 06:23:47 +02:00
}
}
}
2022-06-17 10:08:24 +02:00
// there isn't a derive api for getting grouped values yet,
// so we have to use hand-rolled parsing for exec and exec-batch
pub struct Exec {
pub command : Option < CommandSet > ,
}
impl clap ::FromArgMatches for Exec {
2022-10-09 07:51:45 +02:00
fn from_arg_matches ( matches : & ArgMatches ) -> clap ::error ::Result < Self > {
2022-06-17 10:08:24 +02:00
let command = matches
2023-01-17 08:17:50 +01:00
. get_occurrences ::< String > ( " exec " )
2022-06-17 10:08:24 +02:00
. map ( CommandSet ::new )
. or_else ( | | {
matches
2023-01-17 08:17:50 +01:00
. get_occurrences ::< String > ( " exec_batch " )
2022-06-17 10:08:24 +02:00
. map ( CommandSet ::new_batch )
} )
. transpose ( )
. map_err ( | e | clap ::Error ::raw ( ErrorKind ::InvalidValue , e ) ) ? ;
Ok ( Exec { command } )
}
2022-10-09 07:51:45 +02:00
fn update_from_arg_matches ( & mut self , matches : & ArgMatches ) -> clap ::error ::Result < ( ) > {
2022-06-17 10:08:24 +02:00
* self = Self ::from_arg_matches ( matches ) ? ;
Ok ( ( ) )
}
}
impl clap ::Args for Exec {
2022-10-09 07:51:45 +02:00
fn augment_args ( cmd : Command ) -> Command {
2022-06-17 10:08:24 +02:00
cmd . arg ( Arg ::new ( " exec " )
2022-10-09 07:51:45 +02:00
. action ( ArgAction ::Append )
2022-06-17 10:08:24 +02:00
. long ( " exec " )
. short ( 'x' )
2022-10-09 07:51:45 +02:00
. num_args ( 1 .. )
2022-06-17 10:08:24 +02:00
. allow_hyphen_values ( true )
. value_terminator ( " ; " )
. value_name ( " cmd " )
2022-10-09 07:51:45 +02:00
. conflicts_with ( " list_details " )
2022-06-17 10:08:24 +02:00
. help ( " Execute a command for each search result " )
. long_help (
" Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \
All positional arguments following - - exec are considered to be arguments to the command - not to fd . \
It is therefore recommended to place the ' - x '/' - - exec ' option last . \ n \
The following placeholders are substituted before the command is executed :\ n \
' { } ' : path ( of the current search result ) \ n \
' { / } ' : basename \ n \
' { //}': parent directory\n \
' { . } ' : path without file extension \ n \
2023-05-04 07:33:44 +02:00
' { / . } ' : basename without file extension \ n \
2023-07-18 08:16:01 +02:00
' { { ' : literal '{' ( for escaping ) \ n \
' } } ' : literal '}' ( for escaping ) \ n \ n \
2022-06-17 10:08:24 +02:00
If no placeholder is present , an implicit \ " {} \" at the end is assumed. \n \n \
Examples :\ n \ n \
- find all * . zip files and unzip them :\ n \ n \
fd - e zip - x unzip \ n \ n \
- find * . h and * . cpp files and run \ " clang-format -i .. \" for each of them: \n \n \
fd - e h - e cpp - x clang - format - i \ n \ n \
- Convert all * . jpg files to * . png files :\ n \ n \
fd - e jpg - x convert { } { . } . png \
" ,
) ,
)
. arg (
2022-10-09 07:51:45 +02:00
Arg ::new ( " exec_batch " )
. action ( ArgAction ::Append )
2022-06-17 10:08:24 +02:00
. long ( " exec-batch " )
. short ( 'X' )
2022-10-09 07:51:45 +02:00
. num_args ( 1 .. )
2022-06-17 10:08:24 +02:00
. allow_hyphen_values ( true )
. value_terminator ( " ; " )
. value_name ( " cmd " )
2022-11-08 09:09:06 +01:00
. conflicts_with_all ( [ " exec " , " list_details " ] )
2022-06-17 10:08:24 +02:00
. help ( " Execute a command with all search results at once " )
. long_help (
" Execute the given command once, with all search results as arguments. \n \
One of the following placeholders is substituted before the command is executed :\ n \
' { } ' : path ( of all search results ) \ n \
' { / } ' : basename \ n \
' { //}': parent directory\n \
' { . } ' : path without file extension \ n \
2023-05-04 07:33:44 +02:00
' { / . } ' : basename without file extension \ n \
2023-10-21 21:36:33 +02:00
' { { ' : literal '{' ( for escaping ) \ n \
' } } ' : literal '}' ( for escaping ) \ n \ n \
2022-06-17 10:08:24 +02:00
If no placeholder is present , an implicit \ " {} \" at the end is assumed. \n \n \
Examples :\ n \ n \
- Find all test_ * . py files and open them in your favorite editor :\ n \ n \
fd - g ' test_ * . py ' - X vim \ n \ n \
- Find all * . rs files and count the lines with \ " wc -l ... \" : \n \n \
fd - e rs - X wc - l \
"
) ,
)
}
2022-10-09 07:51:45 +02:00
fn augment_args_for_update ( cmd : Command ) -> Command {
2022-06-17 10:08:24 +02:00
Self ::augment_args ( cmd )
}
}
fn parse_millis ( arg : & str ) -> Result < Duration , std ::num ::ParseIntError > {
Ok ( Duration ::from_millis ( arg . parse ( ) ? ) )
}
fn ensure_current_directory_exists ( current_directory : & Path ) -> anyhow ::Result < ( ) > {
if filesystem ::is_existing_directory ( current_directory ) {
Ok ( ( ) )
} else {
Err ( anyhow! (
" Could not retrieve current directory (has it been deleted?). "
) )
}
}