// This program isn't necessarily meant to serve as an example of how to use // walkdir, but rather, is a good example of how a basic `find` utility can be // written using walkdir in a way that is both correct and as fast as possible. // This includes doing things like block buffering when not printing to a tty, // and correctly writing file paths to stdout without allocating on Unix. // // Additionally, this program is useful for demonstrating all of walkdir's // features. That is, when new functionality is added, some demonstration of // it should be added to this program. // // Finally, this can be useful for ad hoc benchmarking. e.g., See the --timeit // and --count flags. use std::error::Error; use std::ffi::OsStr; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process; use std::result; use std::time::Instant; use bstr::BString; use walkdir::WalkDir; type Result = result::Result>; macro_rules! err { ($($tt:tt)*) => { Err(From::from(format!($($tt)*))) } } fn main() { if let Err(err) = try_main() { eprintln!("{}", err); process::exit(1); } } fn try_main() -> Result<()> { let args = Args::parse()?; let mut stderr = io::stderr(); let start = Instant::now(); if args.count { print_count(&args, io::stdout(), &mut stderr)?; } else if atty::is(atty::Stream::Stdout) { print_paths(&args, io::stdout(), &mut stderr)?; } else { print_paths(&args, io::BufWriter::new(io::stdout()), &mut stderr)?; } if args.timeit { let since = Instant::now().duration_since(start); writeln!(stderr, "duration: {:?}", since)?; } Ok(()) } fn print_count( args: &Args, mut stdout: W1, mut stderr: W2, ) -> Result<()> where W1: io::Write, W2: io::Write, { let mut count: u64 = 0; for dir in &args.dirs { for result in args.walkdir(dir) { match result { Ok(_) => count += 1, Err(err) => { if !args.ignore_errors { writeln!(stderr, "ERROR: {}", err)?; } } } } } writeln!(stdout, "{}", count)?; Ok(()) } fn print_paths( args: &Args, mut stdout: W1, mut stderr: W2, ) -> Result<()> where W1: io::Write, W2: io::Write, { for dir in &args.dirs { if args.tree { print_paths_tree(&args, &mut stdout, &mut stderr, dir)?; } else { print_paths_flat(&args, &mut stdout, &mut stderr, dir)?; } } Ok(()) } fn print_paths_flat( args: &Args, mut stdout: W1, mut stderr: W2, dir: &Path, ) -> Result<()> where W1: io::Write, W2: io::Write, { for result in args.walkdir(dir) { let dent = match result { Ok(dent) => dent, Err(err) => { if !args.ignore_errors { writeln!(stderr, "ERROR: {}", err)?; } continue; } }; write_path(&mut stdout, dent.path())?; stdout.write_all(b"\n")?; } Ok(()) } fn print_paths_tree( args: &Args, mut stdout: W1, mut stderr: W2, dir: &Path, ) -> Result<()> where W1: io::Write, W2: io::Write, { for result in args.walkdir(dir) { let dent = match result { Ok(dent) => dent, Err(err) => { if !args.ignore_errors { writeln!(stderr, "ERROR: {}", err)?; } continue; } }; stdout.write_all(" ".repeat(dent.depth()).as_bytes())?; write_os_str(&mut stdout, dent.file_name())?; stdout.write_all(b"\n")?; } Ok(()) } #[derive(Debug)] struct Args { dirs: Vec, follow_links: bool, min_depth: Option, max_depth: Option, max_open: Option, tree: bool, ignore_errors: bool, sort: bool, depth_first: bool, same_file_system: bool, timeit: bool, count: bool, } impl Args { fn parse() -> Result { use clap::{crate_authors, crate_version, App, Arg}; let parsed = App::new("List files using walkdir") .author(crate_authors!()) .version(crate_version!()) .max_term_width(100) .arg(Arg::with_name("dirs").multiple(true)) .arg( Arg::with_name("follow-links") .long("follow-links") .short("L") .help("Follow symbolic links."), ) .arg( Arg::with_name("min-depth") .long("min-depth") .takes_value(true) .help("Only show entries at or above this depth."), ) .arg( Arg::with_name("max-depth") .long("max-depth") .takes_value(true) .help("Only show entries at or below this depth."), ) .arg( Arg::with_name("max-open") .long("max-open") .takes_value(true) .default_value("10") .help("Use at most this many open file descriptors."), ) .arg( Arg::with_name("tree") .long("tree") .help("Show file paths in a tree."), ) .arg( Arg::with_name("ignore-errors") .long("ignore-errors") .short("q") .help("Don't print error messages."), ) .arg( Arg::with_name("sort") .long("sort") .help("Sort file paths lexicographically."), ) .arg( Arg::with_name("depth-first").long("depth-first").help( "Show directory contents before the directory path.", ), ) .arg( Arg::with_name("same-file-system") .long("same-file-system") .short("x") .help( "Only show paths on the same file system as the root.", ), ) .arg( Arg::with_name("timeit") .long("timeit") .short("t") .help("Print timing info."), ) .arg( Arg::with_name("count") .long("count") .short("c") .help("Print only a total count of all file paths."), ) .get_matches(); let dirs = match parsed.values_of_os("dirs") { None => vec![PathBuf::from("./")], Some(dirs) => dirs.map(PathBuf::from).collect(), }; Ok(Args { dirs: dirs, follow_links: parsed.is_present("follow-links"), min_depth: parse_usize(&parsed, "min-depth")?, max_depth: parse_usize(&parsed, "max-depth")?, max_open: parse_usize(&parsed, "max-open")?, tree: parsed.is_present("tree"), ignore_errors: parsed.is_present("ignore-errors"), sort: parsed.is_present("sort"), depth_first: parsed.is_present("depth-first"), same_file_system: parsed.is_present("same-file-system"), timeit: parsed.is_present("timeit"), count: parsed.is_present("count"), }) } fn walkdir(&self, path: &Path) -> WalkDir { let mut walkdir = WalkDir::new(path) .follow_links(self.follow_links) .contents_first(self.depth_first) .same_file_system(self.same_file_system); if let Some(x) = self.min_depth { walkdir = walkdir.min_depth(x); } if let Some(x) = self.max_depth { walkdir = walkdir.max_depth(x); } if let Some(x) = self.max_open { walkdir = walkdir.max_open(x); } if self.sort { walkdir = walkdir.sort_by(|a, b| a.file_name().cmp(b.file_name())); } walkdir } } fn parse_usize( parsed: &clap::ArgMatches, flag: &str, ) -> Result> { match parsed.value_of_lossy(flag) { None => Ok(None), Some(x) => x.parse().map(Some).or_else(|e| { err!("failed to parse --{} as a number: {}", flag, e) }), } } fn write_path(wtr: W, path: &Path) -> io::Result<()> { write_os_str(wtr, path.as_os_str()) } fn write_os_str(mut wtr: W, os: &OsStr) -> io::Result<()> { // On Unix, this is a no-op, and correctly prints raw paths. On Windows, // this lossily converts paths that originally contained invalid UTF-16 // to paths that will ultimately contain only valid UTF-16. This doesn't // correctly print all possible paths, but on Windows, one can't print // invalid UTF-16 to a console anyway. wtr.write_all(BString::from_os_str_lossy(os).as_bytes()) }