fd/src/app.rs

510 lines
20 KiB
Rust
Raw Normal View History

2017-10-21 10:16:03 +02:00
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashMap;
use clap::{crate_version, App, AppSettings, Arg};
2017-10-04 14:31:08 +02:00
struct Help {
short: &'static str,
long: &'static str,
}
macro_rules! doc {
2018-05-14 18:39:47 +02:00
($map:expr, $name:expr, $short:expr) => {
doc!($map, $name, $short, $short)
};
2018-05-14 18:39:47 +02:00
($map:expr, $name:expr, $short:expr, $long:expr) => {
2018-03-12 17:46:13 +01:00
$map.insert(
$name,
Help {
short: $short,
long: concat!($long, "\n "),
},
);
};
}
2017-10-04 14:31:08 +02:00
pub fn build_app() -> App<'static, 'static> {
let helps = usage();
let arg = |name| {
2018-01-01 12:16:43 +01:00
Arg::with_name(name)
.help(helps[name].short)
.long_help(helps[name].long)
};
2020-01-01 11:17:16 +01:00
let mut app = App::new("fd")
2017-10-05 21:29:29 +02:00
.version(crate_version!())
2017-12-10 06:40:13 +01:00
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
2017-10-05 21:29:29 +02:00
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
2019-01-01 17:40:53 +01:00
.after_help(
"Note: `fd -h` prints a short and concise overview while `fd --help` \
gives all details.",
)
.arg(
arg("hidden")
.long("hidden")
.short("H")
2019-10-09 12:33:27 +02:00
.overrides_with("hidden"),
)
.arg(
arg("no-ignore")
.long("no-ignore")
.short("I")
.overrides_with("no-ignore"),
)
.arg(
arg("no-ignore-vcs")
.long("no-ignore-vcs")
.overrides_with("no-ignore-vcs"),
)
2017-10-12 08:01:51 +02:00
.arg(
arg("rg-alias-hidden-ignore")
.short("u")
2019-09-17 21:57:10 +02:00
.long("unrestricted")
2017-10-12 08:01:51 +02:00
.multiple(true)
.hidden_short_help(true),
2018-09-27 23:01:38 +02:00
)
.arg(
2017-10-12 08:01:51 +02:00
arg("case-sensitive")
.long("case-sensitive")
.short("s")
.overrides_with_all(&["ignore-case", "case-sensitive"]),
2018-09-27 23:01:38 +02:00
)
.arg(
2017-10-12 08:01:51 +02:00
arg("ignore-case")
.long("ignore-case")
.short("i")
.overrides_with_all(&["case-sensitive", "ignore-case"]),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("glob")
.long("glob")
.short("g")
.conflicts_with("fixed-strings")
.overrides_with("glob"),
)
2019-09-15 15:48:34 +02:00
.arg(
arg("regex")
.long("regex")
.overrides_with_all(&["glob", "regex"])
2019-09-15 15:48:34 +02:00
.hidden_short_help(true),
)
2018-09-27 23:01:38 +02:00
.arg(
2018-02-10 15:19:53 +01:00
arg("fixed-strings")
.long("fixed-strings")
.short("F")
.alias("literal")
.overrides_with("fixed-strings"),
2018-09-27 23:01:38 +02:00
)
2019-10-09 12:33:27 +02:00
.arg(
arg("absolute-path")
.long("absolute-path")
.short("a")
.overrides_with("absolute-path"),
)
.arg(
arg("list")
.long("list")
.short("l")
.conflicts_with("absolute-path"),
)
2019-10-09 12:33:27 +02:00
.arg(
arg("follow")
.long("follow")
.short("L")
.alias("dereference")
.overrides_with("follow"),
)
.arg(
arg("full-path")
.long("full-path")
.short("p")
.overrides_with("full-path"),
)
.arg(
arg("null_separator")
.long("print0")
.short("0")
.overrides_with("print0")
.conflicts_with("list"),
2019-10-09 12:33:27 +02:00
)
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
// support --maxdepth as well, for compatibility with rg
2017-10-05 21:29:29 +02:00
.arg(
arg("rg-depth")
.long("maxdepth")
.hidden(true)
.takes_value(true),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("file-type")
2017-10-04 14:31:08 +02:00
.long("type")
.short("t")
.multiple(true)
.number_of_values(1)
2017-10-04 14:31:08 +02:00
.takes_value(true)
2017-10-05 21:35:22 +02:00
.value_name("filetype")
2018-03-25 16:36:37 +02:00
.possible_values(&[
"f",
"file",
"d",
"directory",
"l",
"symlink",
"x",
"executable",
"e",
"empty",
2018-09-27 23:01:38 +02:00
])
.hide_possible_values(true),
)
.arg(
arg("extension")
2017-10-04 14:31:08 +02:00
.long("extension")
.short("e")
.multiple(true)
.number_of_values(1)
2017-10-04 14:31:08 +02:00
.takes_value(true)
.value_name("ext"),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("exec")
.long("exec")
.short("x")
.min_values(1)
2017-11-03 01:39:03 +01:00
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with("list"),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("exec-batch")
.long("exec-batch")
.short("X")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with_all(&["exec", "list"]),
)
2018-09-27 23:01:38 +02:00
.arg(
arg("exclude")
.long("exclude")
.short("E")
.takes_value(true)
.value_name("pattern")
.number_of_values(1)
.multiple(true),
2018-09-27 23:01:38 +02:00
)
.arg(
2018-03-26 00:15:01 +02:00
arg("ignore-file")
.long("ignore-file")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.multiple(true)
.hidden_short_help(true),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("color")
2017-10-04 14:31:08 +02:00
.long("color")
.short("c")
.takes_value(true)
2017-10-05 21:35:22 +02:00
.value_name("when")
2017-10-04 14:31:08 +02:00
.possible_values(&["never", "auto", "always"])
.hide_possible_values(true),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("threads")
2017-10-04 14:31:08 +02:00
.long("threads")
.short("j")
.takes_value(true)
.value_name("num")
.hidden_short_help(true),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("size")
.long("size")
.short("S")
.takes_value(true)
.number_of_values(1)
.allow_hyphen_values(true)
.multiple(true),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("max-buffer-time")
2017-10-04 14:31:08 +02:00
.long("max-buffer-time")
.takes_value(true)
.hidden(true),
2018-09-27 23:01:38 +02:00
)
.arg(
arg("changed-within")
.long("changed-within")
2018-10-27 15:34:10 +02:00
.alias("change-newer-than")
.takes_value(true)
2018-10-27 15:34:10 +02:00
.value_name("date|dur")
.number_of_values(1),
)
.arg(
arg("changed-before")
.long("changed-before")
2018-10-27 15:34:10 +02:00
.alias("change-older-than")
.takes_value(true)
2018-10-27 15:34:10 +02:00
.value_name("date|dur")
.number_of_values(1),
)
.arg(
arg("max-results")
.long("max-results")
.takes_value(true)
.value_name("count")
.conflicts_with_all(&["exec", "exec-batch"])
.hidden_short_help(true),
)
.arg(
arg("show-errors")
.long("show-errors")
.hidden_short_help(true)
.overrides_with("show-errors"),
)
.arg(
arg("base-directory")
.long("base-directory")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.hidden_short_help(true),
)
2018-09-27 23:01:38 +02:00
.arg(arg("pattern"))
.arg(
arg("path-separator")
.takes_value(true)
.value_name("separator")
.long("path-separator")
.hidden_short_help(true),
)
Add multiple path support (#182) * Adding support for multiple paths. (panic) - Started adding multiple file support - fd panics with multiple files right now * Moved the ctrlc handler to main. - Moved the ctrlc handler to main so we can search multiple files * Tests now allow custom directory setup - TestEnv::new() now takes two arguments, the directories to create and the files to create inside those directories. * rust-fmt changes * rust-fmt changes * Moving code around, no need to do everything in one big loop - PathDisplay was never actually used for anything, removed it during refactor of main - Removed redundant logic for absolute paths - Moved code placed needlessly inside a loop in the last commit outside of that loop. * Moving code around, no need to do everything in one big loop - PathDisplay was never actually used for anything, removed it during refactor of main - Removed redundant logic for absolute paths - Moved code placed needlessly inside a loop in the last commit outside of that loop. * Removed commented code in testenv * Refactored walk::scan to accept the path buffer vector. Using the ParallelWalker allows for multithreaded searching of multiple directories * Moved ctrlc handler back into walker, it is only called once from main now. * Moved the colored output check back to it's original place * Removing shell-escape, not sure how it got added... * Added test for `fd 'a.foo' test1` to show that a.foo is only found in the test1 and not the test2 direcotry * Removing side effect from walk::scan, `dir_vec` is no longer a mutable reference and an iterator is being used instead. * Running rustfmt to format code correctly
2017-12-06 23:52:23 +01:00
.arg(arg("path").multiple(true))
.arg(
arg("search-path")
.long("search-path")
.takes_value(true)
.conflicts_with("path")
.multiple(true)
.hidden_short_help(true)
.number_of_values(1),
);
// Make `--one-file-system` available only on Unix and Windows platforms, as per the
// restrictions on the corresponding option in the `ignore` crate.
// Provide aliases `mount` and `xdev` for people coming from `find`.
2019-12-30 22:27:45 +01:00
if cfg!(any(unix, windows)) {
2020-01-01 11:17:16 +01:00
app = app.arg(
arg("one-file-system")
.long("one-file-system")
.aliases(&["mount", "xdev"])
.hidden_short_help(true),
2020-01-01 11:17:16 +01:00
);
}
2020-01-01 11:17:16 +01:00
app
}
2019-01-26 02:13:16 +01:00
#[rustfmt::skip]
fn usage() -> HashMap<&'static str, Help> {
let mut h = HashMap::new();
doc!(h, "hidden"
, "Search hidden files and directories"
2017-10-07 15:15:30 +02: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).");
doc!(h, "no-ignore"
2018-03-26 10:25:33 +02:00
, "Do not respect .(git|fd)ignore files"
2017-10-07 15:15:30 +02:00
, "Show search results from files and directories that would otherwise be ignored by \
2018-10-27 16:44:24 +02:00
'.gitignore', '.ignore' or '.fdignore' files.");
doc!(h, "no-ignore-vcs"
, "Do not respect .gitignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.gitignore' files.");
doc!(h, "case-sensitive"
, "Case-sensitive search (default: smart case)"
2017-10-07 15:15:30 +02:00
, "Perform a case-sensitive search. By default, fd uses case-insensitive searches, \
unless the pattern contains an uppercase character (smart case).");
2017-10-12 01:21:44 +02:00
doc!(h, "ignore-case"
, "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).");
doc!(h, "glob"
, "Glob-based search (default: regular expression)"
, "Perform a glob-based search instead of a regular expression search.");
2019-09-15 15:48:34 +02:00
doc!(h, "regex"
, "Perform a regex-based search"
, "Perform a regular-expression based search (default). This can be used to override --glob.");
2018-02-10 15:19:53 +01:00
doc!(h, "fixed-strings"
, "Treat the pattern as a literal string"
, "Treat the pattern as a literal string instead of a regular expression.");
doc!(h, "absolute-path"
, "Show absolute instead of relative paths"
2017-10-07 15:15:30 +02:00
, "Shows the full path starting from the root as opposed to relative paths.");
doc!(h, "list"
, "Use a detailed listing format"
, "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, to achieve a deterministic \
sort order and to avoid duplicate search results when using multiple search paths.");
doc!(h, "path-separator"
, "Set the path separator to use when printing file paths."
2019-09-15 10:36:10 +02:00
, "Set the path separator to use when printing file paths. The default is the OS-specific \
separator ('/' on Unix, '\\' on Windows).");
doc!(h, "follow"
, "Follow symbolic links"
, "By default, fd does not descend into symlinked directories. Using this flag, symbolic \
2017-10-07 15:15:30 +02:00
links are also traversed.");
doc!(h, "full-path"
, "Search full path (default: file-/dirname only)"
2017-10-07 15:15:30 +02: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 path.");
doc!(h, "null_separator"
, "Separate results by the null character"
2017-10-07 15:15:30 +02:00
, "Separate search results by the null character (instead of newlines). Useful for \
piping results to 'xargs'.");
doc!(h, "depth"
, "Set maximum search depth (default: none)"
2017-10-07 15:15:30 +02:00
, "Limit the directory traversal to a given depth. By default, there is no limit \
on the search depth.");
doc!(h, "rg-depth"
, "See --max-depth"
, "See --max-depth");
doc!(h, "file-type"
, "Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), empty (e)"
, "Filter the search by type (multiple allowable filetypes can be specified):\n \
2017-10-07 15:15:30 +02:00
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links\n \
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories");
doc!(h, "extension"
, "Filter by file extension"
, "(Additionally) filter search results by their file extension. Multiple allowable file \
extensions can be specified.");
2017-10-14 18:04:11 +02:00
doc!(h, "exec"
, "Execute a command for each search result"
, "Execute a command for each search result.\n\
2017-11-15 22:07:04 +01:00
All arguments following --exec are taken to be arguments to the command until the \
argument ';' is encountered.\n\
Each occurrence of the following placeholders is substituted by a path derived from the \
current search result before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension");
doc!(h, "exec-batch"
, "Execute a command with all search results at once"
, "Execute a command with all search results at once.\n\
All arguments following --exec-batch are taken to be arguments to the command until the \
argument ';' is encountered.\n\
A single occurrence of the following placeholders is authorized and substituted by the paths derived from the \
search results before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension");
doc!(h, "exclude"
2018-01-03 10:28:34 +01:00
, "Exclude entries that match the given glob pattern"
, "Exclude files/directories that match the given glob pattern. This overrides any \
other ignore logic. Multiple exclude patterns can be specified.");
2018-03-26 00:15:01 +02:00
doc!(h, "ignore-file"
, "Add a custom ignore-file in .gitignore format"
2018-03-26 10:25:33 +02:00
, "Add a custom ignore-file in '.gitignore' format. These files have a low precedence.");
doc!(h, "color"
2017-10-07 15:15:30 +02:00
, "When to use colors: never, *auto*, always"
, "Declare when to use color for the pattern match output:\n \
'auto': show colors if the output goes to an interactive console (default)\n \
'never': do not use colorized output\n \
'always': always use colorized output");
doc!(h, "threads"
2017-10-14 18:04:11 +02:00
, "Set number of threads to use for searching & executing"
2017-10-14 20:04:04 +02:00
, "Set number of threads to use for searching & executing (default: number of available \
CPU cores)");
doc!(h, "max-buffer-time"
, "the time (in ms) to buffer, before streaming to the console"
, "Amount of time in milliseconds to buffer, before streaming the search results to \
2017-10-07 15:15:30 +02:00
the console.");
doc!(h, "pattern"
, "the search pattern - a regular expression unless '--glob' is used (optional)");
doc!(h, "path"
, "the root directory for the filesystem search (optional)"
2017-10-07 15:15:30 +02:00
, "The directory where the filesystem search is rooted (optional). \
If omitted, search the current working directory.");
doc!(h, "rg-alias-hidden-ignore"
2019-09-17 21:57:10 +02:00
, ""
, "Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.");
doc!(h, "size"
, "Limit results based on the size of files."
2018-04-24 00:13:21 +02:00
, "Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
'+': file size must be greater than or equal to this\n \
'-': file size must be less than or equal to this\n \
'NUM': The numeric size (e.g. 500)\n \
'UNIT': The units for NUM. They are not case-sensitive.\n\
Allowed unit values:\n \
2018-04-24 00:13:21 +02:00
'b': bytes\n \
'k': kilobytes\n \
'm': megabytes\n \
'g': gigabytes\n \
't': terabytes\n \
'ki': kibibytes\n \
'mi': mebibytes\n \
'gi': gibibytes\n \
'ti': tebibytes");
doc!(h, "changed-within"
2018-10-27 15:34:10 +02:00
, "Filter by file modification time (newer than)"
, "Filter results based on the file modification time. 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-newer-than' can be used as an alias.\n\
Examples:\n \
--changed-within 2weeks\n \
--change-newer-than '2018-10-27 10:00:00'");
doc!(h, "changed-before"
, "Filter by file modification time (older than)"
, "Filter results based on the file modification time. 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' can be used as an alias.\n\
Examples:\n \
--changed-before '2018-10-27 10:00:00'\n \
--change-older-than 2weeks");
doc!(h, "max-results"
, "(hidden)"
, "Limit the number of search results to 'count' and quit immediately.");
2018-10-22 14:20:08 +02:00
doc!(h, "show-errors"
, "Enable display of filesystem errors"
, "Enable the display of filesystem errors for situations such as insufficient permissions \
or dead symlinks.");
doc!(h, "search-path"
, "(hidden)"
, "Provide paths to search as an alternative to the positional <path> argument. \
Changes the usage to `fd [FLAGS/OPTIONS] --search-path <path> --search-path <path2> [<pattern>]`");
doc!(h, "base-directory"
, "(hidden)"
2019-12-23 16:07:38 +01:00
, "Change the current working directory of fd to the provided path. The 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.");
2019-12-30 22:27:45 +01:00
if cfg!(any(unix, windows)) {
doc!(h, "one-file-system"
, "Don't cross file system boundaries"
, "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).");
}
h
2017-10-05 21:29:29 +02:00
}