"Fossies" - the Fresh Open Source Software Archive

Member "fd-8.1.1/src/main.rs" (25 May 2020, 15173 Bytes) of package /linux/privat/fd-8.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Rust source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "main.rs": 8.1.0_vs_8.1.1.

    1 mod app;
    2 mod error;
    3 mod exec;
    4 mod exit_codes;
    5 mod filesystem;
    6 mod filetypes;
    7 mod filter;
    8 mod options;
    9 mod output;
   10 mod regex_helper;
   11 mod walk;
   12 
   13 use std::env;
   14 use std::path::{Path, PathBuf};
   15 use std::process;
   16 use std::sync::Arc;
   17 use std::time;
   18 
   19 use anyhow::{anyhow, Context, Result};
   20 use atty::Stream;
   21 use globset::GlobBuilder;
   22 use lscolors::LsColors;
   23 use regex::bytes::{RegexBuilder, RegexSetBuilder};
   24 
   25 use crate::error::print_error;
   26 use crate::exec::CommandTemplate;
   27 use crate::exit_codes::ExitCode;
   28 use crate::filetypes::FileTypes;
   29 #[cfg(unix)]
   30 use crate::filter::OwnerFilter;
   31 use crate::filter::{SizeFilter, TimeFilter};
   32 use crate::options::Options;
   33 use crate::regex_helper::pattern_has_uppercase_char;
   34 
   35 // We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
   36 // FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
   37 #[cfg(all(not(windows), not(target_os = "macos"), not(target_env = "musl")))]
   38 #[global_allocator]
   39 static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
   40 
   41 fn run() -> Result<ExitCode> {
   42     let matches = app::build_app().get_matches_from(env::args_os());
   43 
   44     // Set the current working directory of the process
   45     if let Some(base_directory) = matches.value_of_os("base-directory") {
   46         let base_directory = Path::new(base_directory);
   47         if !filesystem::is_dir(base_directory) {
   48             return Err(anyhow!(
   49                 "The '--base-directory' path '{}' is not a directory.",
   50                 base_directory.to_string_lossy()
   51             ));
   52         }
   53         env::set_current_dir(base_directory).with_context(|| {
   54             format!(
   55                 "Could not set '{}' as the current working directory.",
   56                 base_directory.to_string_lossy()
   57             )
   58         })?;
   59     }
   60 
   61     let current_directory = Path::new(".");
   62     if !filesystem::is_dir(current_directory) {
   63         return Err(anyhow!(
   64             "Could not retrieve current directory (has it been deleted?)."
   65         ));
   66     }
   67 
   68     // Get the search pattern
   69     let pattern = matches
   70         .value_of_os("pattern")
   71         .map(|p| {
   72             p.to_str()
   73                 .ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
   74         })
   75         .transpose()?
   76         .unwrap_or("");
   77 
   78     // Get one or more root directories to search.
   79     let passed_arguments = matches
   80         .values_of_os("path")
   81         .or_else(|| matches.values_of_os("search-path"));
   82 
   83     let mut search_paths = if let Some(paths) = passed_arguments {
   84         let mut directories = vec![];
   85         for path in paths {
   86             let path_buffer = PathBuf::from(path);
   87             if filesystem::is_dir(&path_buffer) {
   88                 directories.push(path_buffer);
   89             } else {
   90                 print_error(format!(
   91                     "Search path '{}' is not a directory.",
   92                     path_buffer.to_string_lossy()
   93                 ));
   94             }
   95         }
   96 
   97         directories
   98     } else {
   99         vec![current_directory.to_path_buf()]
  100     };
  101 
  102     // Check if we have no valid search paths.
  103     if search_paths.is_empty() {
  104         return Err(anyhow!("No valid search paths given."));
  105     }
  106 
  107     if matches.is_present("absolute-path") {
  108         search_paths = search_paths
  109             .iter()
  110             .map(|path_buffer| {
  111                 path_buffer
  112                     .canonicalize()
  113                     .and_then(|pb| filesystem::absolute_path(pb.as_path()))
  114                     .unwrap()
  115             })
  116             .collect();
  117     }
  118 
  119     // Detect if the user accidentally supplied a path instead of a search pattern
  120     if !matches.is_present("full-path")
  121         && pattern.contains(std::path::MAIN_SEPARATOR)
  122         && filesystem::is_dir(Path::new(pattern))
  123     {
  124         return Err(anyhow!(
  125             "The search pattern '{pattern}' contains a path-separation character ('{sep}') \
  126              and will not lead to any search results.\n\n\
  127              If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n  \
  128              fd . '{pattern}'\n\n\
  129              Instead, if you want your pattern to match the full file path, use:\n\n  \
  130              fd --full-path '{pattern}'",
  131             pattern = pattern,
  132             sep = std::path::MAIN_SEPARATOR,
  133         ));
  134     }
  135 
  136     let pattern_regex = if matches.is_present("glob") && !pattern.is_empty() {
  137         let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
  138         glob.regex().to_owned()
  139     } else if matches.is_present("fixed-strings") {
  140         // Treat pattern as literal string if '--fixed-strings' is used
  141         regex::escape(pattern)
  142     } else {
  143         String::from(pattern)
  144     };
  145 
  146     // The search will be case-sensitive if the command line flag is set or
  147     // if the pattern has an uppercase character (smart case).
  148     let case_sensitive = !matches.is_present("ignore-case")
  149         && (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
  150 
  151     #[cfg(windows)]
  152     let ansi_colors_support =
  153         ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
  154 
  155     #[cfg(not(windows))]
  156     let ansi_colors_support = true;
  157 
  158     let interactive_terminal = atty::is(Stream::Stdout);
  159     let colored_output = match matches.value_of("color") {
  160         Some("always") => true,
  161         Some("never") => false,
  162         _ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
  163     };
  164 
  165     let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
  166 
  167     let ls_colors = if colored_output {
  168         Some(LsColors::from_env().unwrap_or_default())
  169     } else {
  170         None
  171     };
  172 
  173     let command = if let Some(args) = matches.values_of("exec") {
  174         Some(CommandTemplate::new(args))
  175     } else if let Some(args) = matches.values_of("exec-batch") {
  176         Some(CommandTemplate::new_batch(args)?)
  177     } else if matches.is_present("list-details") {
  178         let color = matches.value_of("color").unwrap_or("auto");
  179         let color_arg = ["--color=", color].concat();
  180 
  181         #[allow(unused)]
  182         let gnu_ls = |command_name| {
  183             vec![
  184                 command_name,
  185                 "-l",               // long listing format
  186                 "--human-readable", // human readable file sizes
  187                 "--directory",      // list directories themselves, not their contents
  188                 &color_arg,
  189             ]
  190         };
  191 
  192         let cmd: Vec<&str> = if cfg!(unix) {
  193             if !cfg!(any(
  194                 target_os = "macos",
  195                 target_os = "dragonfly",
  196                 target_os = "freebsd",
  197                 target_os = "netbsd",
  198                 target_os = "openbsd"
  199             )) {
  200                 // Assume ls is GNU ls
  201                 gnu_ls("ls")
  202             } else {
  203                 // MacOS, DragonFlyBSD, FreeBSD
  204                 use std::process::{Command, Stdio};
  205 
  206                 // Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
  207                 let gnu_ls_exists = Command::new("gls")
  208                     .arg("--version")
  209                     .stdout(Stdio::null())
  210                     .stderr(Stdio::null())
  211                     .status()
  212                     .is_ok();
  213 
  214                 if gnu_ls_exists {
  215                     gnu_ls("gls")
  216                 } else {
  217                     let mut cmd = vec![
  218                         "ls", // BSD version of ls
  219                         "-l", // long listing format
  220                         "-h", // '--human-readable' is not available, '-h' is
  221                         "-d", // '--directory' is not available, but '-d' is
  222                     ];
  223 
  224                     if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
  225                         // -G is not available in NetBSD's and OpenBSD's ls
  226                         cmd.push("-G");
  227                     }
  228 
  229                     cmd
  230                 }
  231             }
  232         } else if cfg!(windows) {
  233             use std::process::{Command, Stdio};
  234 
  235             // Use GNU ls, if available
  236             let gnu_ls_exists = Command::new("ls")
  237                 .arg("--version")
  238                 .stdout(Stdio::null())
  239                 .stderr(Stdio::null())
  240                 .status()
  241                 .is_ok();
  242 
  243             if gnu_ls_exists {
  244                 gnu_ls("ls")
  245             } else {
  246                 return Err(anyhow!(
  247                     "'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
  248                 ));
  249             }
  250         } else {
  251             return Err(anyhow!(
  252                 "'fd --list-details' is not supported on this platform."
  253             ));
  254         };
  255 
  256         Some(CommandTemplate::new_batch(&cmd).unwrap())
  257     } else {
  258         None
  259     };
  260 
  261     let size_limits = if let Some(vs) = matches.values_of("size") {
  262         vs.map(|sf| {
  263             SizeFilter::from_string(sf)
  264                 .ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
  265         })
  266         .collect::<Result<Vec<_>>>()?
  267     } else {
  268         vec![]
  269     };
  270 
  271     let now = time::SystemTime::now();
  272     let mut time_constraints: Vec<TimeFilter> = Vec::new();
  273     if let Some(t) = matches.value_of("changed-within") {
  274         if let Some(f) = TimeFilter::after(&now, t) {
  275             time_constraints.push(f);
  276         } else {
  277             return Err(anyhow!(
  278                 "'{}' is not a valid date or duration. See 'fd --help'.",
  279                 t
  280             ));
  281         }
  282     }
  283     if let Some(t) = matches.value_of("changed-before") {
  284         if let Some(f) = TimeFilter::before(&now, t) {
  285             time_constraints.push(f);
  286         } else {
  287             return Err(anyhow!(
  288                 "'{}' is not a valid date or duration. See 'fd --help'.",
  289                 t
  290             ));
  291         }
  292     }
  293 
  294     #[cfg(unix)]
  295     let owner_constraint = if let Some(s) = matches.value_of("owner") {
  296         OwnerFilter::from_string(s)?
  297     } else {
  298         None
  299     };
  300 
  301     let config = Options {
  302         case_sensitive,
  303         search_full_path: matches.is_present("full-path"),
  304         ignore_hidden: !(matches.is_present("hidden")
  305             || matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
  306         read_fdignore: !(matches.is_present("no-ignore")
  307             || matches.is_present("rg-alias-hidden-ignore")),
  308         read_vcsignore: !(matches.is_present("no-ignore")
  309             || matches.is_present("rg-alias-hidden-ignore")
  310             || matches.is_present("no-ignore-vcs")),
  311         read_global_ignore: !(matches.is_present("no-ignore")
  312             || matches.is_present("rg-alias-hidden-ignore")
  313             || matches.is_present("no-global-ignore-file")),
  314         follow_links: matches.is_present("follow"),
  315         one_file_system: matches.is_present("one-file-system"),
  316         null_separator: matches.is_present("null_separator"),
  317         max_depth: matches
  318             .value_of("max-depth")
  319             .or_else(|| matches.value_of("rg-depth"))
  320             .or_else(|| matches.value_of("exact-depth"))
  321             .and_then(|n| usize::from_str_radix(n, 10).ok()),
  322         min_depth: matches
  323             .value_of("min-depth")
  324             .or_else(|| matches.value_of("exact-depth"))
  325             .and_then(|n| usize::from_str_radix(n, 10).ok()),
  326         threads: std::cmp::max(
  327             matches
  328                 .value_of("threads")
  329                 .and_then(|n| usize::from_str_radix(n, 10).ok())
  330                 .unwrap_or_else(num_cpus::get),
  331             1,
  332         ),
  333         max_buffer_time: matches
  334             .value_of("max-buffer-time")
  335             .and_then(|n| u64::from_str_radix(n, 10).ok())
  336             .map(time::Duration::from_millis),
  337         ls_colors,
  338         interactive_terminal,
  339         file_types: matches.values_of("file-type").map(|values| {
  340             let mut file_types = FileTypes::default();
  341             for value in values {
  342                 match value {
  343                     "f" | "file" => file_types.files = true,
  344                     "d" | "directory" => file_types.directories = true,
  345                     "l" | "symlink" => file_types.symlinks = true,
  346                     "x" | "executable" => {
  347                         file_types.executables_only = true;
  348                         file_types.files = true;
  349                     }
  350                     "e" | "empty" => file_types.empty_only = true,
  351                     "s" | "socket" => file_types.sockets = true,
  352                     "p" | "pipe" => file_types.pipes = true,
  353                     _ => unreachable!(),
  354                 }
  355             }
  356 
  357             // If only 'empty' was specified, search for both files and directories:
  358             if file_types.empty_only && !(file_types.files || file_types.directories) {
  359                 file_types.files = true;
  360                 file_types.directories = true;
  361             }
  362 
  363             file_types
  364         }),
  365         extensions: matches
  366             .values_of("extension")
  367             .map(|exts| {
  368                 let patterns = exts
  369                     .map(|e| e.trim_start_matches('.'))
  370                     .map(|e| format!(r".\.{}$", regex::escape(e)));
  371                 RegexSetBuilder::new(patterns)
  372                     .case_insensitive(true)
  373                     .build()
  374             })
  375             .transpose()?,
  376         command: command.map(Arc::new),
  377         exclude_patterns: matches
  378             .values_of("exclude")
  379             .map(|v| v.map(|p| String::from("!") + p).collect())
  380             .unwrap_or_else(|| vec![]),
  381         ignore_files: matches
  382             .values_of("ignore-file")
  383             .map(|vs| vs.map(PathBuf::from).collect())
  384             .unwrap_or_else(|| vec![]),
  385         size_constraints: size_limits,
  386         time_constraints,
  387         #[cfg(unix)]
  388         owner_constraint,
  389         show_filesystem_errors: matches.is_present("show-errors"),
  390         path_separator,
  391         max_results: matches
  392             .value_of("max-results")
  393             .and_then(|n| usize::from_str_radix(n, 10).ok())
  394             .filter(|&n| n != 0)
  395             .or_else(|| {
  396                 if matches.is_present("max-one-result") {
  397                     Some(1)
  398                 } else {
  399                     None
  400                 }
  401             }),
  402     };
  403 
  404     let re = RegexBuilder::new(&pattern_regex)
  405         .case_insensitive(!config.case_sensitive)
  406         .dot_matches_new_line(true)
  407         .build()
  408         .map_err(|e| {
  409             anyhow!(
  410                 "{}\n\nNote: You can use the '--fixed-strings' option to search for a \
  411                  literal string instead of a regular expression. Alternatively, you can \
  412                  also use the '--glob' option to match on a glob pattern.",
  413                 e.to_string()
  414             )
  415         })?;
  416 
  417     walk::scan(&search_paths, Arc::new(re), Arc::new(config))
  418 }
  419 
  420 fn main() {
  421     let result = run();
  422     match result {
  423         Ok(exit_code) => {
  424             process::exit(exit_code.into());
  425         }
  426         Err(err) => {
  427             eprintln!("[fd error]: {}", err);
  428             process::exit(ExitCode::GeneralError.into());
  429         }
  430     }
  431 }