mirror of https://github.com/sharkdp/fd.git
Initial implementation
This commit is contained in:
parent
82aa17f9fb
commit
e5760f09d5
|
@ -110,6 +110,7 @@ _fd() {
|
|||
'*'{-t+,--type=}"[filter search by type]:type:(($fd_types))"
|
||||
'*'{-e+,--extension=}'[filter search by file extension]:extension'
|
||||
'*'{-E+,--exclude=}'[exclude files/directories that match the given glob pattern]:glob pattern'
|
||||
'*--exclude-absolute=[exclude files/directories whose absolute path match the given glob pattern]:glob pattern'
|
||||
'*'{-S+,--size=}'[limit search by file size]:size limit:->size'
|
||||
'(-o --owner)'{-o+,--owner=}'[filter by owning user and/or group]:owner and/or group:->owner'
|
||||
|
||||
|
|
19
src/cli.rs
19
src/cli.rs
|
@ -46,7 +46,7 @@ pub struct Opts {
|
|||
no_hidden: (),
|
||||
|
||||
/// Show search results from files and directories that would otherwise be
|
||||
/// ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file.
|
||||
/// ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore files.
|
||||
/// The flag can be overridden with --ignore.
|
||||
#[arg(
|
||||
long,
|
||||
|
@ -106,7 +106,7 @@ pub struct Opts {
|
|||
)]
|
||||
pub no_ignore_parent: bool,
|
||||
|
||||
/// Do not respect the global ignore file
|
||||
/// Do not respect the global ignore files
|
||||
#[arg(long, hide = true)]
|
||||
pub no_global_ignore_file: bool,
|
||||
|
||||
|
@ -300,6 +300,21 @@ pub struct Opts {
|
|||
)]
|
||||
pub exclude: Vec<String>,
|
||||
|
||||
/// Exclude files/directories whose absolute path match the given glob pattern.
|
||||
/// This filter is applied on top of all other ignore logic. Multiple exclude patterns
|
||||
/// can be specified.
|
||||
///
|
||||
/// Note that using this filter causes fd to perform an extra canonicalization
|
||||
/// for every path traversed, which incurs a non-trivial performance penalty.
|
||||
/// Use at your own discretion.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "pattern",
|
||||
help = "Exclude entries whose absolute path match the given glob pattern",
|
||||
long_help
|
||||
)]
|
||||
pub exclude_absolute: Vec<String>,
|
||||
|
||||
/// Do not traverse into directories that match the search criteria. If
|
||||
/// you want to exclude specific directories, use the '--exclude=…' option.
|
||||
#[arg(long, hide_short_help = true, conflicts_with_all(&["size", "exact_depth"]),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use globset::GlobMatcher;
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::RegexSet;
|
||||
|
||||
|
@ -95,6 +96,9 @@ pub struct Config {
|
|||
/// A list of glob patterns that should be excluded from the search.
|
||||
pub exclude_patterns: Vec<String>,
|
||||
|
||||
/// A list of glob matchers that should exclude matched entries by their absolute paths.
|
||||
pub exclude_absolute_matchers: Vec<GlobMatcher>,
|
||||
|
||||
/// A list of custom ignore files.
|
||||
pub ignore_files: Vec<PathBuf>,
|
||||
|
||||
|
@ -130,3 +134,19 @@ impl Config {
|
|||
self.command.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the platform-specific config directory for fd.
|
||||
pub fn get_fd_config_dir() -> Option<PathBuf> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut dir = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs_next::home_dir().map(|d| d.join(".config")))?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let mut dir = dirs_next::config_dir()?;
|
||||
|
||||
dir.push("fd");
|
||||
|
||||
Some(dir)
|
||||
}
|
||||
|
|
74
src/main.rs
74
src/main.rs
|
@ -19,12 +19,13 @@ use std::time;
|
|||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use atty::Stream;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use globset::GlobBuilder;
|
||||
use globset::{Glob, GlobBuilder, GlobMatcher};
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::{Regex, RegexBuilder, RegexSetBuilder};
|
||||
|
||||
use crate::cli::{ColorWhen, Opts};
|
||||
use crate::config::Config;
|
||||
use crate::config::{get_fd_config_dir, Config};
|
||||
use crate::error::print_error;
|
||||
use crate::exec::CommandSet;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filetypes::FileTypes;
|
||||
|
@ -233,6 +234,29 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
|
|||
let command = extract_command(&mut opts, colored_output)?;
|
||||
let has_command = command.is_some();
|
||||
|
||||
let read_global_ignore =
|
||||
!(opts.no_ignore || opts.rg_alias_ignore() || opts.no_global_ignore_file);
|
||||
let exclude_absolute_matchers = {
|
||||
let mut matchers = vec![];
|
||||
|
||||
// absolute excludes from CLI
|
||||
for glob_str in opts.exclude_absolute.iter() {
|
||||
// invalid globs from CLI are hard errors
|
||||
let m = Glob::new(glob_str)?.compile_matcher();
|
||||
matchers.push(m);
|
||||
}
|
||||
// absolute excludes from global `ignore-absolute` file
|
||||
if read_global_ignore {
|
||||
match read_and_build_global_exclude_absolute_matchers() {
|
||||
Ok(mut v) => matchers.append(&mut v),
|
||||
Err(err) => print_error(format!("Cannot read global ignore-absolute file. {err}.")),
|
||||
}
|
||||
}
|
||||
|
||||
dbg!(&matchers);
|
||||
matchers
|
||||
};
|
||||
|
||||
Ok(Config {
|
||||
case_sensitive,
|
||||
search_full_path: opts.full_path,
|
||||
|
@ -241,9 +265,7 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
|
|||
read_vcsignore: !(opts.no_ignore || opts.rg_alias_ignore() || opts.no_ignore_vcs),
|
||||
require_git_to_read_vcsignore: !opts.no_require_git,
|
||||
read_parent_ignore: !opts.no_ignore_parent,
|
||||
read_global_ignore: !(opts.no_ignore
|
||||
|| opts.rg_alias_ignore()
|
||||
|| opts.no_global_ignore_file),
|
||||
read_global_ignore,
|
||||
follow_links: opts.follow,
|
||||
one_file_system: opts.one_file_system,
|
||||
null_separator: opts.null_separator,
|
||||
|
@ -297,6 +319,7 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
|
|||
command: command.map(Arc::new),
|
||||
batch_size: opts.batch_size,
|
||||
exclude_patterns: opts.exclude.iter().map(|p| String::from("!") + p).collect(),
|
||||
exclude_absolute_matchers,
|
||||
ignore_files: std::mem::take(&mut opts.ignore_file),
|
||||
size_constraints: size_limits,
|
||||
time_constraints,
|
||||
|
@ -468,3 +491,44 @@ fn build_regex(pattern_regex: String, config: &Config) -> Result<regex::bytes::R
|
|||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn read_and_build_global_exclude_absolute_matchers() -> Result<Vec<GlobMatcher>> {
|
||||
let file_content = match get_fd_config_dir()
|
||||
.map(|p| p.join("ignore-absolute"))
|
||||
.filter(|p| p.is_file())
|
||||
{
|
||||
Some(path) => std::fs::read_to_string(path)?,
|
||||
// not an error if the file doesn't exist
|
||||
None => return Ok(vec![]),
|
||||
};
|
||||
|
||||
let matchers = file_content
|
||||
.lines()
|
||||
// trim trailing spaces, unless escaped with backslash (`\`)
|
||||
.map(|raw| {
|
||||
let naive_trimmed = raw.trim_end_matches(' ');
|
||||
if raw.len() == naive_trimmed.len() {
|
||||
raw
|
||||
} else if naive_trimmed.ends_with('\\') {
|
||||
&raw[..naive_trimmed.len() + 1]
|
||||
} else {
|
||||
naive_trimmed
|
||||
}
|
||||
})
|
||||
// skip empty lines and comments
|
||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
||||
// build matchers
|
||||
.filter_map(|line| match Glob::new(line) {
|
||||
Ok(glob) => Some(glob.compile_matcher()),
|
||||
// invalid globs from config file are warnings
|
||||
Err(err) => {
|
||||
print_error(format!(
|
||||
"Malformed pattern in global ignore-absolute file. {err}."
|
||||
));
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(matchers)
|
||||
}
|
||||
|
|
37
src/walk.rs
37
src/walk.rs
|
@ -14,6 +14,7 @@ use ignore::overrides::OverrideBuilder;
|
|||
use ignore::{self, WalkBuilder};
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use crate::config::get_fd_config_dir;
|
||||
use crate::config::Config;
|
||||
use crate::dir_entry::DirEntry;
|
||||
use crate::error::print_error;
|
||||
|
@ -89,17 +90,8 @@ pub fn scan(paths: &[PathBuf], patterns: Arc<Vec<Regex>>, config: Arc<Config>) -
|
|||
}
|
||||
|
||||
if config.read_global_ignore {
|
||||
#[cfg(target_os = "macos")]
|
||||
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let config_dir_op = dirs_next::config_dir();
|
||||
|
||||
if let Some(global_ignore_file) = config_dir_op
|
||||
.map(|p| p.join("fd").join("ignore"))
|
||||
if let Some(global_ignore_file) = get_fd_config_dir()
|
||||
.map(|p| p.join("ignore"))
|
||||
.filter(|p| p.is_file())
|
||||
{
|
||||
let result = walker.add_ignore(global_ignore_file);
|
||||
|
@ -534,6 +526,29 @@ fn spawn_senders(
|
|||
}
|
||||
}
|
||||
|
||||
// Exclude by absolute path
|
||||
// `ignore` crate does not intend to support this, so it's implemented here independently
|
||||
// see https://github.com/BurntSushi/ripgrep/issues/2366
|
||||
// This is done last because canonicalisation has non-trivial cost
|
||||
if !config.exclude_absolute_matchers.is_empty() {
|
||||
match entry_path.canonicalize() {
|
||||
Ok(path) => {
|
||||
if config
|
||||
.exclude_absolute_matchers
|
||||
.iter()
|
||||
.any(|glob| glob.is_match(&path))
|
||||
{
|
||||
// emulate Git's behavior of skipping any matched directory entirely
|
||||
// see https://git-scm.com/docs/gitignore#_pattern_format
|
||||
return ignore::WalkState::Skip;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
print_error(format!("Cannot canonicalize {entry_path:?}. {err}."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.is_printing() {
|
||||
if let Some(ls_colors) = &config.ls_colors {
|
||||
// Compute colors in parallel
|
||||
|
|
Loading…
Reference in New Issue