mirror of
https://github.com/BurntSushi/walkdir.git
synced 2025-09-27 05:30:27 +00:00
313 lines
9.0 KiB
Rust
313 lines
9.0 KiB
Rust
// 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<T> = result::Result<T, Box<dyn Error>>;
|
|
|
|
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<W1, W2>(
|
|
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<W1, W2>(
|
|
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<W1, W2>(
|
|
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<W1, W2>(
|
|
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<PathBuf>,
|
|
follow_links: bool,
|
|
min_depth: Option<usize>,
|
|
max_depth: Option<usize>,
|
|
max_open: Option<usize>,
|
|
tree: bool,
|
|
ignore_errors: bool,
|
|
sort: bool,
|
|
depth_first: bool,
|
|
same_file_system: bool,
|
|
timeit: bool,
|
|
count: bool,
|
|
}
|
|
|
|
impl Args {
|
|
fn parse() -> Result<Args> {
|
|
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<Option<usize>> {
|
|
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<W: io::Write>(wtr: W, path: &Path) -> io::Result<()> {
|
|
write_os_str(wtr, path.as_os_str())
|
|
}
|
|
|
|
fn write_os_str<W: io::Write>(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())
|
|
}
|