2022-06-17 10:08:24 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
#[cfg(feature = "completions")]
|
|
|
|
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;
|
|
|
|
|
|
|
|
// Type for options that don't have any values, but are used to negate
|
|
|
|
// earlier options
|
|
|
|
struct Negations;
|
|
|
|
|
|
|
|
impl clap::FromArgMatches for Negations {
|
2022-10-09 07:51:45 +02:00
|
|
|
fn from_arg_matches(_: &ArgMatches) -> clap::error::Result<Self> {
|
2022-06-17 10:08:24 +02:00
|
|
|
Ok(Negations)
|
|
|
|
}
|
|
|
|
|
2022-10-09 07:51:45 +02:00
|
|
|
fn update_from_arg_matches(&mut self, _: &ArgMatches) -> clap::error::Result<()> {
|
2022-06-17 10:08:24 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl clap::Args for Negations {
|
2022-10-09 07:51:45 +02:00
|
|
|
fn augment_args(cmd: Command) -> Command {
|
2022-06-17 10:08:24 +02:00
|
|
|
Self::augment_args_for_update(cmd)
|
|
|
|
}
|
|
|
|
|
2022-10-09 07:51:45 +02:00
|
|
|
fn augment_args_for_update(cmd: Command) -> Command {
|
2022-06-17 10:08:24 +02:00
|
|
|
cmd.arg(
|
2022-10-09 07:51:45 +02:00
|
|
|
Arg::new("no_hidden")
|
|
|
|
.action(ArgAction::Count)
|
2022-06-17 10:08:24 +02:00
|
|
|
.long("no-hidden")
|
|
|
|
.overrides_with("hidden")
|
|
|
|
.hide(true)
|
|
|
|
.long_help("Overrides --hidden."),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::new("ignore")
|
2022-10-09 07:51:45 +02:00
|
|
|
.action(ArgAction::Count)
|
2022-06-17 10:08:24 +02:00
|
|
|
.long("ignore")
|
2022-10-09 07:51:45 +02:00
|
|
|
.overrides_with("no_ignore")
|
2022-06-17 10:08:24 +02:00
|
|
|
.hide(true)
|
|
|
|
.long_help("Overrides --no-ignore."),
|
|
|
|
)
|
|
|
|
.arg(
|
2022-10-09 07:51:45 +02:00
|
|
|
Arg::new("ignore_vcs")
|
|
|
|
.action(ArgAction::Count)
|
2022-06-17 10:08:24 +02:00
|
|
|
.long("ignore-vcs")
|
2022-10-09 07:51:45 +02:00
|
|
|
.overrides_with("no_ignore_vcs")
|
2022-06-17 10:08:24 +02:00
|
|
|
.hide(true)
|
|
|
|
.long_help("Overrides --no-ignore-vcs."),
|
|
|
|
)
|
|
|
|
.arg(
|
2022-10-09 07:51:45 +02:00
|
|
|
Arg::new("relative_path")
|
|
|
|
.action(ArgAction::Count)
|
2022-06-17 10:08:24 +02:00
|
|
|
.long("relative-path")
|
2022-10-09 07:51:45 +02:00
|
|
|
.overrides_with("absolute_path")
|
2022-06-17 10:08:24 +02:00
|
|
|
.hide(true)
|
|
|
|
.long_help("Overrides --absolute-path."),
|
|
|
|
)
|
|
|
|
.arg(
|
2022-10-09 07:51:45 +02:00
|
|
|
Arg::new("no_follow")
|
|
|
|
.action(ArgAction::Count)
|
2022-06-17 10:08:24 +02:00
|
|
|
.long("no-follow")
|
|
|
|
.overrides_with("follow")
|
|
|
|
.hide(true)
|
|
|
|
.long_help("Overrides --follow."),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Parser)]
|
2022-10-12 06:27:57 +02:00
|
|
|
#[command(
|
2022-06-17 10:08:24 +02:00
|
|
|
version,
|
|
|
|
after_help = "Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
|
|
|
|
details.",
|
2022-10-09 09:00:48 +02:00
|
|
|
after_long_help = "Bugs can be reported on GitHub: https://github.com/sharkdp/fd/issues",
|
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(&[
|
|
|
|
"max_results", "has_results", "count"])),
|
2022-06-17 10:08:24 +02:00
|
|
|
)]
|
|
|
|
pub struct Opts {
|
|
|
|
/// Search hidden files and directories
|
|
|
|
///
|
|
|
|
/// 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).
|
2022-07-18 10:19:11 +02:00
|
|
|
/// The flag can be overridden with --no-hidden.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'H')]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub hidden: bool,
|
|
|
|
/// Do not respect .(git|fd)ignore files
|
|
|
|
///
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'I')]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub no_ignore: bool,
|
|
|
|
/// Do not respect .gitignore files
|
|
|
|
///
|
|
|
|
///Show search results from files and directories that would otherwise be
|
|
|
|
///ignored by '.gitignore' files. The flag can be overridden with --ignore-vcs.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub no_ignore_vcs: bool,
|
|
|
|
/// Do not respect .(git|fd)ignore files in parent directories
|
|
|
|
///
|
|
|
|
/// Show search results from files and directories that would otherwise be
|
|
|
|
/// ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub no_ignore_parent: bool,
|
|
|
|
/// 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,
|
|
|
|
///Perform an unrestricted search, including ignored and hidden files. This is
|
|
|
|
///an alias for '--no-ignore --hidden'.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long = "unrestricted", short = 'u', overrides_with_all(&["ignore", "no_hidden"]), action(ArgAction::Count), hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
rg_alias_hidden_ignore: u8,
|
|
|
|
/// Case-sensitive search (default: smart case)
|
|
|
|
///
|
|
|
|
///Perform a case-sensitive search. By default, fd uses case-insensitive
|
|
|
|
///searches, unless the pattern contains an uppercase character (smart case).
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 's', overrides_with("ignore_case"))]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub case_sensitive: bool,
|
|
|
|
/// Case-insensitive search (default: smart case)
|
|
|
|
///
|
|
|
|
/// Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless
|
|
|
|
/// the pattern contains an uppercase character (smart case).
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'i', overrides_with("case_sensitive"))]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub ignore_case: bool,
|
|
|
|
/// Glob-based search (default: regular expression)
|
|
|
|
///
|
|
|
|
/// Perform a glob-based search instead of a regular expression search.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'g', conflicts_with("fixed_strings"))]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub glob: bool,
|
2022-10-20 08:34:52 +02:00
|
|
|
/// Perform a regular-expression based search (default). This can be used to override --glob.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, overrides_with("glob"), hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub regex: bool,
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'F', alias = "literal", hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub fixed_strings: bool,
|
|
|
|
/// Show absolute instead of relative paths
|
|
|
|
///
|
|
|
|
/// Shows the full path starting with the root as opposed to relative paths.
|
|
|
|
/// The flag can be overridden with --relative-path.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'a')]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub absolute_path: bool,
|
|
|
|
/// Use a long listing format with file metadata
|
|
|
|
///
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'l', conflicts_with("absolute_path"))]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub list_details: bool,
|
|
|
|
/// Follow symbolic links
|
|
|
|
///
|
|
|
|
/// By default, fd does not descend into symlinked directories. Using this
|
|
|
|
/// flag, symbolic links are also traversed.
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Flag can be overridden with --no-follow.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'L', alias = "dereference")]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub follow: bool,
|
|
|
|
/// Search full abs. path (default: filename only)
|
|
|
|
///
|
|
|
|
/// By default, the search pattern is only matched against the filename (or
|
|
|
|
/// directory name). Using this flag, the pattern is matched against the full
|
2022-07-18 10:19:11 +02:00
|
|
|
/// (absolute) path.
|
|
|
|
/// Example:
|
2022-06-17 10:08:24 +02:00
|
|
|
/// fd --glob -p '**/.git/config'
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'p', verbatim_doc_comment)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub full_path: bool,
|
|
|
|
/// 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-06-17 10:08:24 +02:00
|
|
|
hide_short_help = true
|
|
|
|
)]
|
|
|
|
pub null_separator: bool,
|
|
|
|
/// Set maximum search depth (default: none)
|
|
|
|
///
|
|
|
|
/// Limit the directory traversal to a given depth. By default, there is no
|
|
|
|
/// limit on the search depth.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'd', value_name = "depth", alias("maxdepth"))]
|
2022-06-17 10:08:24 +02:00
|
|
|
max_depth: Option<usize>,
|
|
|
|
/// Only show search results starting at the given depth.
|
|
|
|
/// See also: '--max-depth' and '--exact-depth'
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, value_name = "depth", hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
min_depth: Option<usize>,
|
|
|
|
/// Only show search results at the exact given depth. This is an alias for
|
|
|
|
/// '--min-depth <depth> --max-depth <depth>'.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, value_name = "depth", hide_short_help = true, conflicts_with_all(&["max_depth", "min_depth"]))]
|
2022-06-17 10:08:24 +02:00
|
|
|
exact_depth: Option<usize>,
|
|
|
|
/// Do not traverse into directories that match the search criteria. If
|
|
|
|
/// you want to exclude specific directories, use the '--exclude=…' option.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, hide_short_help = true, conflicts_with_all(&["size", "exact_depth"]))]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub prune: bool,
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Filter by type: file (f), directory (d), symlink (l),
|
|
|
|
/// executable (x), empty (e), socket (s), pipe (p)
|
2022-06-17 10:08:24 +02:00
|
|
|
///
|
|
|
|
/// Filter the search by type:
|
|
|
|
///
|
|
|
|
/// 'f' or 'file': regular files
|
|
|
|
/// 'd' or 'directory': directories
|
|
|
|
/// 'l' or 'symlink': symbolic links
|
|
|
|
/// 's' or 'socket': socket
|
|
|
|
/// 'p' or 'pipe': named pipe (FIFO)
|
|
|
|
///
|
|
|
|
/// 'x' or 'executable': executables
|
|
|
|
/// '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:
|
|
|
|
///
|
|
|
|
/// - Only search for files:
|
|
|
|
/// fd --type file …
|
|
|
|
/// fd -tf …
|
|
|
|
/// - Find both files and symlinks
|
|
|
|
/// fd --type file --type symlink …
|
|
|
|
/// fd -tf -tl …
|
|
|
|
/// - Find executable files:
|
|
|
|
/// fd --type executable
|
|
|
|
/// fd -tx
|
|
|
|
/// - Find empty files:
|
|
|
|
/// fd --type empty --type file
|
|
|
|
/// fd -te -tf
|
|
|
|
/// - Find empty directories:
|
|
|
|
/// fd --type empty --type directory
|
|
|
|
/// 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,
|
|
|
|
verbatim_doc_comment
|
|
|
|
)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub filetype: Option<Vec<FileType>>,
|
|
|
|
/// Filter by file extension
|
|
|
|
///
|
|
|
|
/// (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-10-12 06:27:57 +02:00
|
|
|
#[arg(long = "extension", short = 'e', value_name = "ext")]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub extensions: Option<Vec<String>>,
|
|
|
|
|
2022-10-12 06:27:57 +02:00
|
|
|
#[command(flatten)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub exec: Exec,
|
|
|
|
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
#[arg(
|
2022-06-17 10:08:24 +02:00
|
|
|
long,
|
|
|
|
value_name = "size",
|
|
|
|
hide_short_help = true,
|
2022-10-09 07:51:45 +02:00
|
|
|
requires("exec_batch"),
|
2022-06-17 10:08:24 +02:00
|
|
|
value_parser = value_parser!(usize),
|
|
|
|
default_value_t
|
|
|
|
)]
|
|
|
|
pub batch_size: usize,
|
|
|
|
/// Exclude entries that match the given glob pattern
|
|
|
|
///
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Exclude files/directories that match the given glob pattern. This overrides any other
|
|
|
|
/// ignore logic. Multiple exclude patterns can be specified.
|
2022-06-17 10:08:24 +02:00
|
|
|
///
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Examples:
|
|
|
|
/// --exclude '*.pyc'
|
|
|
|
/// --exclude node_modules
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'E', value_name = "pattern", verbatim_doc_comment)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub exclude: Vec<String>,
|
|
|
|
/// Add a custom ignore-file in '.gitignore' format. These files have a low
|
|
|
|
/// precedence.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, value_name = "path", hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub ignore_file: Vec<PathBuf>,
|
|
|
|
/// When to use colors
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(
|
2022-06-17 10:08:24 +02:00
|
|
|
long,
|
|
|
|
short = 'c',
|
2022-09-08 09:18:04 +02:00
|
|
|
value_enum,
|
|
|
|
default_value_t = ColorWhen::Auto,
|
2022-07-18 10:19:11 +02:00
|
|
|
value_name = "when",
|
|
|
|
verbatim_doc_comment
|
2022-06-17 10:08:24 +02:00
|
|
|
)]
|
|
|
|
pub color: ColorWhen,
|
|
|
|
/// Set number of threads to use for searching & executing (default: number
|
|
|
|
/// of available CPU cores)
|
2022-10-12 07:19:17 +02:00
|
|
|
#[arg(long, short = 'j', value_name = "num", hide_short_help = true, value_parser = 1..)]
|
|
|
|
pub threads: Option<u32>,
|
2022-06-17 10:08:24 +02:00
|
|
|
/// Limit results based on the size of files
|
|
|
|
///
|
|
|
|
/// 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-10-20 08:34:52 +02:00
|
|
|
#[arg(long, short = 'S', value_parser = SizeFilter::from_string, allow_hyphen_values = true, verbatim_doc_comment, value_name = "size")]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub size: Vec<SizeFilter>,
|
|
|
|
/// Milliseconds to buffer before streaming search results to console
|
|
|
|
///
|
|
|
|
/// Amount of time in milliseconds to buffer, before streaming the search
|
|
|
|
/// results to the console.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, hide = true, value_parser = parse_millis)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub max_buffer_time: Option<Duration>,
|
|
|
|
/// Filter by file modification time (newer than)
|
|
|
|
///
|
2022-07-18 10:19:11 +02:00
|
|
|
/// The argument can be provided as a specific point in time (YYYY-MM-DD HH:MM:SS)
|
|
|
|
/// or as a duration (10h, 1d, 35min).
|
2022-06-17 10:08:24 +02:00
|
|
|
/// If the time is not specified, it defaults to 00:00:00.
|
|
|
|
/// '--change-newer-than' or '--newer' can be used as aliases.
|
|
|
|
/// Examples:
|
|
|
|
/// --changed-within 2weeks
|
|
|
|
/// --change-newer-than '2018-10-27 10:00:00'
|
|
|
|
/// --newer 2018-10-27
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(
|
2022-06-17 10:08:24 +02:00
|
|
|
long,
|
|
|
|
alias("change-newer-than"),
|
|
|
|
alias("newer"),
|
|
|
|
value_name = "date|dur",
|
2022-10-12 06:27:57 +02:00
|
|
|
verbatim_doc_comment
|
2022-06-17 10:08:24 +02:00
|
|
|
)]
|
|
|
|
pub changed_within: Option<String>,
|
|
|
|
/// Filter by file modification time (older than)
|
|
|
|
///
|
2022-07-18 10:19:11 +02:00
|
|
|
/// The argument can be provided as a specific point in time (YYYY-MM-DD HH:MM:SS)
|
|
|
|
/// or as a duration (10h, 1d, 35min).
|
2022-06-17 10:08:24 +02:00
|
|
|
/// '--change-older-than' or '--older' can be used as aliases.
|
|
|
|
///
|
|
|
|
/// Examples:
|
|
|
|
/// --changed-before '2018-10-27 10:00:00'
|
|
|
|
/// --change-older-than 2weeks
|
|
|
|
/// --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-10-12 06:27:57 +02:00
|
|
|
verbatim_doc_comment
|
2022-06-17 10:08:24 +02:00
|
|
|
)]
|
|
|
|
pub changed_before: Option<String>,
|
|
|
|
/// Limit number of search results
|
|
|
|
///
|
|
|
|
/// Limit the number of search results to 'count' and quit immediately.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, value_name = "count", hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
max_results: Option<usize>,
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Limit search to a single result and quit immediately
|
2022-06-17 10:08:24 +02:00
|
|
|
///
|
|
|
|
/// This is an alias for '--max-results=1'.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(short = '1', hide_short_help = true, overrides_with("max_results"))]
|
2022-06-17 10:08:24 +02:00
|
|
|
max_one_result: bool,
|
|
|
|
/// Print nothing, exit code 0 if match found, 1 otherwise
|
|
|
|
///
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
conflicts_with("max_results")
|
2022-07-18 10:19:11 +02:00
|
|
|
)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub quiet: bool,
|
|
|
|
/// Show filesystem errors
|
|
|
|
///
|
|
|
|
///Enable the display of filesystem errors for situations such as
|
|
|
|
///insufficient permissions or dead symlinks.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub show_errors: bool,
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
#[arg(long, value_name = "path", hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub base_directory: Option<PathBuf>,
|
|
|
|
/// the search pattern (a regular expression, unless '--glob' is used; optional)
|
|
|
|
///
|
|
|
|
/// 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-10-12 06:27:57 +02:00
|
|
|
#[arg(default_value = "", hide_default_value = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub pattern: String,
|
|
|
|
/// Set path separator when printing file paths
|
|
|
|
/// Set the path separator to use when printing file paths. The default is
|
|
|
|
/// the OS-specific separator ('/' on Unix, '\\' on Windows).
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, value_name = "separator", hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub path_separator: Option<String>,
|
|
|
|
/// the root directories for the filesystem search (optional)
|
|
|
|
///
|
2022-07-18 10:19:11 +02:00
|
|
|
/// The directories where the filesystem search is rooted.
|
2022-06-17 10:08:24 +02:00
|
|
|
/// If omitted, search the current working directory.
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(action = ArgAction::Append)]
|
2022-06-17 10:08:24 +02:00
|
|
|
path: Vec<PathBuf>,
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Provides paths to search as an alternative to the positional <path> argument
|
2022-06-17 10:08:24 +02:00
|
|
|
///
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Changes the usage to `fd [OPTIONS] --search-path <path> --search-path <path2> [<pattern>]`
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, conflicts_with("path"), hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
search_path: Vec<PathBuf>,
|
2022-10-09 09:00:48 +02:00
|
|
|
/// strip './' prefix from -0/--print-0 output
|
2022-06-17 10:08:24 +02: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-10-12 06:27:57 +02:00
|
|
|
#[arg(long, conflicts_with_all(&["path", "search_path"]), hide_short_help = true)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub strip_cwd_prefix: bool,
|
|
|
|
/// Filter by owning user and/or group
|
|
|
|
///
|
2022-10-20 08:34:52 +02:00
|
|
|
/// Format: [(user|uid)][:(group|gid)].
|
2022-07-18 10:19:11 +02:00
|
|
|
/// Either side is optional. Precede either side with a '!' to exclude files instead.
|
2022-06-17 10:08:24 +02:00
|
|
|
///
|
|
|
|
/// Examples:
|
|
|
|
/// --owner john
|
|
|
|
/// --owner :students
|
|
|
|
/// --owner '!john:students'
|
|
|
|
#[cfg(unix)]
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, short = 'o', value_parser = OwnerFilter::from_string, value_name = "user:group", verbatim_doc_comment)]
|
2022-06-17 10:08:24 +02:00
|
|
|
pub owner: Option<OwnerFilter>,
|
|
|
|
/// Do not descend into a different file system
|
|
|
|
///
|
|
|
|
/// 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).
|
|
|
|
#[cfg(any(unix, windows))]
|
2022-10-12 06:27:57 +02:00
|
|
|
#[arg(long, aliases(&["mount", "xdev"]), hide_short_help = true)]
|
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>>,
|
|
|
|
|
|
|
|
#[clap(flatten)]
|
|
|
|
_negations: Negations,
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
.or_else(|| self.max_one_result.then(|| 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),
|
|
|
|
None => guess_shell(),
|
|
|
|
})
|
|
|
|
.transpose()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: windows?
|
|
|
|
#[cfg(feature = "completions")]
|
|
|
|
fn guess_shell() -> anyhow::Result<Shell> {
|
|
|
|
let env_shell = std::env::var_os("SHELL").map(PathBuf::from);
|
2022-10-09 08:02:28 +02:00
|
|
|
if let Some(shell) = env_shell
|
2022-07-18 10:19:11 +02:00
|
|
|
.as_ref()
|
2022-06-17 10:08:24 +02:00
|
|
|
.and_then(|s| s.file_name())
|
|
|
|
.and_then(|s| s.to_str())
|
2022-10-09 08:02:28 +02:00
|
|
|
{
|
|
|
|
shell
|
|
|
|
.parse::<Shell>()
|
|
|
|
.map_err(|_| anyhow!("Unknown shell {}", shell))
|
|
|
|
} else {
|
|
|
|
// Assume powershell on windows
|
|
|
|
#[cfg(windows)]
|
2022-10-09 09:05:27 +02:00
|
|
|
return Ok(Shell::PowerShell);
|
2022-10-09 08:02:28 +02:00
|
|
|
#[cfg(not(windows))]
|
|
|
|
return Err(anyhow!("Unable to get shell from environment"));
|
|
|
|
}
|
2022-06-17 10:08:24 +02:00
|
|
|
}
|
|
|
|
|
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,
|
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",
|
|
|
|
Always => "always",
|
|
|
|
Never => "never",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
.grouped_values_of("exec")
|
|
|
|
.map(CommandSet::new)
|
|
|
|
.or_else(|| {
|
|
|
|
matches
|
2022-10-09 07:51:45 +02:00
|
|
|
.grouped_values_of("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 \
|
|
|
|
'{/.}': basename without file extension\n\n\
|
|
|
|
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-10-09 07:51:45 +02: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 \
|
|
|
|
'{/.}': basename without file extension\n\n\
|
|
|
|
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?)."
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|