├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── rustfmt.toml ├── src ├── tests │ ├── mod.rs │ ├── util.rs │ └── recursive.rs ├── util.rs ├── error.rs ├── dent.rs └── lib.rs ├── COPYING ├── .gitignore ├── compare ├── walk.py └── nftw.c ├── walkdir-list ├── Cargo.toml └── main.rs ├── Cargo.toml ├── LICENSE-MIT ├── UNLICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [BurntSushi] 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 79 2 | use_small_heuristics = "max" 3 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod util; 3 | 4 | mod recursive; 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This project is dual-licensed under the Unlicense and MIT licenses. 2 | 3 | You may use this code under the terms of either license. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | doc 3 | tags 4 | examples/ss10pusa.csv 5 | build 6 | target 7 | Cargo.lock 8 | scratch* 9 | bench_large/huge 10 | tmp 11 | -------------------------------------------------------------------------------- /compare/walk.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | import os 4 | import sys 5 | 6 | for dirpath, dirnames, filenames in os.walk(sys.argv[1]): 7 | for n in dirnames: 8 | print(os.path.join(dirpath, n)) 9 | for n in filenames: 10 | print(os.path.join(dirpath, n)) 11 | -------------------------------------------------------------------------------- /compare/nftw.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 500 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int 9 | display_info(const char *fpath, const struct stat *sb, 10 | int tflag, struct FTW *ftwbuf) 11 | { 12 | printf("%s\n", fpath); 13 | return 0; 14 | } 15 | 16 | int 17 | main(int argc, char *argv[]) 18 | { 19 | int flags = FTW_PHYS; 20 | if (nftw((argc < 2) ? "." : argv[1], display_info, 20, flags) == -1) { 21 | perror("nftw"); 22 | exit(EXIT_FAILURE); 23 | } 24 | exit(EXIT_SUCCESS); 25 | } 26 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::Path; 3 | 4 | #[cfg(unix)] 5 | pub fn device_num>(path: P) -> io::Result { 6 | use std::os::unix::fs::MetadataExt; 7 | 8 | path.as_ref().metadata().map(|md| md.dev()) 9 | } 10 | 11 | #[cfg(windows)] 12 | pub fn device_num>(path: P) -> io::Result { 13 | use winapi_util::{file, Handle}; 14 | 15 | let h = Handle::from_path_any(path)?; 16 | file::information(h).map(|info| info.volume_serial_number()) 17 | } 18 | 19 | #[cfg(not(any(unix, windows)))] 20 | pub fn device_num>(_: P) -> io::Result { 21 | Err(io::Error::new( 22 | io::ErrorKind::Other, 23 | "walkdir: same_file_system option not supported on this platform", 24 | )) 25 | } 26 | -------------------------------------------------------------------------------- /walkdir-list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "walkdir-bin" 4 | version = "0.0.0" 5 | authors = ["Andrew Gallant "] 6 | description = "A simple command line tool for playing with walkdir on the CLI." 7 | documentation = "https://docs.rs/walkdir" 8 | homepage = "https://github.com/BurntSushi/walkdir" 9 | repository = "https://github.com/BurntSushi/walkdir" 10 | keywords = ["walk", "directory", "recursive", "find"] 11 | license = "Unlicense OR MIT" 12 | categories = ["command-line-utilities"] 13 | edition = "2018" 14 | 15 | [[bin]] 16 | name = "walkdir-list" 17 | path = "main.rs" 18 | 19 | [dependencies] 20 | atty = "0.2.11" 21 | bstr = { version = "0.1.2", default-features = false, features = ["std"] } 22 | clap = { version = "2.33.0", default-features = false } 23 | walkdir = { version = "*", path = ".." } 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walkdir" 3 | version = "2.5.0" #:version 4 | authors = ["Andrew Gallant "] 5 | description = "Recursively walk a directory." 6 | documentation = "https://docs.rs/walkdir/" 7 | homepage = "https://github.com/BurntSushi/walkdir" 8 | repository = "https://github.com/BurntSushi/walkdir" 9 | readme = "README.md" 10 | keywords = ["directory", "recursive", "walk", "iterator"] 11 | categories = ["filesystem"] 12 | license = "Unlicense/MIT" 13 | exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml"] 14 | edition = "2018" 15 | 16 | [badges] 17 | travis-ci = { repository = "BurntSushi/walkdir" } 18 | appveyor = { repository = "BurntSushi/walkdir" } 19 | 20 | [workspace] 21 | members = ["walkdir-list"] 22 | 23 | [dependencies] 24 | same-file = "1.0.1" 25 | 26 | [target.'cfg(windows)'.dependencies.winapi-util] 27 | version = "0.1.1" 28 | 29 | [dev-dependencies] 30 | doc-comment = "0.3" 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Gallant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '00 01 * * *' 11 | 12 | # The section is needed to drop write-all permissions that are granted on 13 | # `schedule` event. By specifying any permission explicitly all others are set 14 | # to none. By using the principle of least privilege the damage a compromised 15 | # workflow can do (because of an injection or compromised third party tool or 16 | # action) is restricted. Currently the worklow doesn't need any additional 17 | # permission except for pulling the code. Adding labels to issues, commenting 18 | # on pull-requests, etc. may need additional permissions: 19 | # 20 | # Syntax for this section: 21 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions 22 | # 23 | # Reference for how to assign permissions on a job-by-job basis: 24 | # https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs 25 | # 26 | # Reference for available permissions that we can enable if needed: 27 | # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token 28 | permissions: 29 | # to fetch code (actions/checkout) 30 | contents: read 31 | 32 | jobs: 33 | test: 34 | name: test 35 | runs-on: ${{ matrix.os }} 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | include: 40 | - build: pinned 41 | os: ubuntu-latest 42 | rust: 1.60.0 43 | - build: pinned-win 44 | os: windows-latest 45 | rust: 1.60.0 46 | - build: stable 47 | os: ubuntu-latest 48 | rust: stable 49 | - build: beta 50 | os: ubuntu-latest 51 | rust: beta 52 | - build: nightly 53 | os: ubuntu-latest 54 | rust: nightly 55 | - build: macos 56 | os: macos-latest 57 | rust: stable 58 | - build: win-msvc 59 | os: windows-latest 60 | rust: stable 61 | - build: win-gnu 62 | os: windows-latest 63 | rust: stable-x86_64-gnu 64 | steps: 65 | - name: Checkout repository 66 | uses: actions/checkout@v4 67 | - name: Install Rust 68 | uses: dtolnay/rust-toolchain@master 69 | with: 70 | toolchain: ${{ matrix.rust }} 71 | - run: cargo build --verbose 72 | - if: startsWith(matrix.build, 'pinned-') == false 73 | run: cargo doc --verbose 74 | - if: startsWith(matrix.build, 'pinned-') == false 75 | run: cargo test --verbose 76 | - if: matrix.build == 'nightly' 77 | run: | 78 | set -x 79 | cargo generate-lockfile -Z minimal-versions 80 | cargo build --verbose 81 | cargo test --verbose 82 | 83 | rustfmt: 84 | runs-on: ubuntu-latest 85 | steps: 86 | - name: Checkout repository 87 | uses: actions/checkout@v4 88 | - name: Install Rust 89 | uses: dtolnay/rust-toolchain@master 90 | with: 91 | toolchain: stable 92 | components: rustfmt 93 | - name: Check formatting 94 | run: | 95 | cargo fmt --all -- --check 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | walkdir 2 | ======= 3 | A cross platform Rust library for efficiently walking a directory recursively. 4 | Comes with support for following symbolic links, controlling the number of 5 | open file descriptors and efficient mechanisms for pruning the entries in the 6 | directory tree. 7 | 8 | [![Build status](https://github.com/BurntSushi/walkdir/workflows/ci/badge.svg)](https://github.com/BurntSushi/walkdir/actions) 9 | [![](https://meritbadge.herokuapp.com/walkdir)](https://crates.io/crates/walkdir) 10 | 11 | Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/). 12 | 13 | ### Documentation 14 | 15 | [docs.rs/walkdir](https://docs.rs/walkdir/) 16 | 17 | ### Usage 18 | 19 | To use this crate, add `walkdir` as a dependency to your project's 20 | `Cargo.toml`: 21 | 22 | ```toml 23 | [dependencies] 24 | walkdir = "2" 25 | ``` 26 | 27 | ### Example 28 | 29 | The following code recursively iterates over the directory given and prints 30 | the path for each entry: 31 | 32 | ```rust,no_run 33 | use walkdir::WalkDir; 34 | 35 | for entry in WalkDir::new("foo") { 36 | let entry = entry.unwrap(); 37 | println!("{}", entry.path().display()); 38 | } 39 | ``` 40 | 41 | Or, if you'd like to iterate over all entries and ignore any errors that may 42 | arise, use `filter_map`. (e.g., This code below will silently skip directories 43 | that the owner of the running process does not have permission to access.) 44 | 45 | ```rust,no_run 46 | use walkdir::WalkDir; 47 | 48 | for entry in WalkDir::new("foo").into_iter().filter_map(|e| e.ok()) { 49 | println!("{}", entry.path().display()); 50 | } 51 | ``` 52 | 53 | ### Example: follow symbolic links 54 | 55 | The same code as above, except `follow_links` is enabled: 56 | 57 | ```rust,no_run 58 | use walkdir::WalkDir; 59 | 60 | for entry in WalkDir::new("foo").follow_links(true) { 61 | let entry = entry.unwrap(); 62 | println!("{}", entry.path().display()); 63 | } 64 | ``` 65 | 66 | ### Example: skip hidden files and directories efficiently on unix 67 | 68 | This uses the `filter_entry` iterator adapter to avoid yielding hidden files 69 | and directories efficiently: 70 | 71 | ```rust,no_run 72 | use walkdir::{DirEntry, WalkDir}; 73 | 74 | fn is_hidden(entry: &DirEntry) -> bool { 75 | entry.file_name() 76 | .to_str() 77 | .map(|s| s.starts_with(".")) 78 | .unwrap_or(false) 79 | } 80 | 81 | let walker = WalkDir::new("foo").into_iter(); 82 | for entry in walker.filter_entry(|e| !is_hidden(e)) { 83 | let entry = entry.unwrap(); 84 | println!("{}", entry.path().display()); 85 | } 86 | ``` 87 | 88 | ### Minimum Rust version policy 89 | 90 | This crate's minimum supported `rustc` version is `1.60.0`. 91 | 92 | The current policy is that the minimum Rust version required to use this crate 93 | can be increased in minor version updates. For example, if `crate 1.0` requires 94 | Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust 95 | 1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum 96 | version of Rust. 97 | 98 | In general, this crate will be conservative with respect to the minimum 99 | supported version of Rust. 100 | 101 | ### Performance 102 | 103 | The short story is that performance is comparable with `find` and glibc's 104 | `nftw` on both a warm and cold file cache. In fact, I cannot observe any 105 | performance difference after running `find /`, `walkdir /` and `nftw /` on my 106 | local file system (SSD, ~3 million entries). More precisely, I am reasonably 107 | confident that this crate makes as few system calls and close to as few 108 | allocations as possible. 109 | 110 | I haven't recorded any benchmarks, but here are some things you can try with a 111 | local checkout of `walkdir`: 112 | 113 | ```sh 114 | # The directory you want to recursively walk: 115 | DIR=$HOME 116 | 117 | # If you want to observe perf on a cold file cache, run this before *each* 118 | # command: 119 | sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 120 | 121 | # To warm the caches 122 | find $DIR 123 | 124 | # Test speed of `find` on warm cache: 125 | time find $DIR 126 | 127 | # Compile and test speed of `walkdir` crate: 128 | cargo build --release --example walkdir 129 | time ./target/release/examples/walkdir $DIR 130 | 131 | # Compile and test speed of glibc's `nftw`: 132 | gcc -O3 -o nftw ./compare/nftw.c 133 | time ./nftw $DIR 134 | 135 | # For shits and giggles, test speed of Python's (2 or 3) os.walk: 136 | time python ./compare/walk.py $DIR 137 | ``` 138 | 139 | On my system, the performance of `walkdir`, `find` and `nftw` is comparable. 140 | -------------------------------------------------------------------------------- /src/tests/util.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::error; 3 | use std::fs::{self, File}; 4 | use std::io; 5 | use std::path::{Path, PathBuf}; 6 | use std::result; 7 | 8 | use crate::{DirEntry, Error}; 9 | 10 | /// Create an error from a format!-like syntax. 11 | #[macro_export] 12 | macro_rules! err { 13 | ($($tt:tt)*) => { 14 | Box::::from(format!($($tt)*)) 15 | } 16 | } 17 | 18 | /// A convenient result type alias. 19 | pub type Result = result::Result>; 20 | 21 | /// The result of running a recursive directory iterator on a single directory. 22 | #[derive(Debug)] 23 | pub struct RecursiveResults { 24 | ents: Vec, 25 | errs: Vec, 26 | } 27 | 28 | impl RecursiveResults { 29 | /// Return all of the errors encountered during traversal. 30 | pub fn errs(&self) -> &[Error] { 31 | &self.errs 32 | } 33 | 34 | /// Assert that no errors have occurred. 35 | pub fn assert_no_errors(&self) { 36 | assert!( 37 | self.errs.is_empty(), 38 | "expected to find no errors, but found: {:?}", 39 | self.errs 40 | ); 41 | } 42 | 43 | /// Return all the successfully retrieved directory entries in the order 44 | /// in which they were retrieved. 45 | pub fn ents(&self) -> &[DirEntry] { 46 | &self.ents 47 | } 48 | 49 | /// Return all paths from all successfully retrieved directory entries. 50 | /// 51 | /// This does not include paths that correspond to an error. 52 | pub fn paths(&self) -> Vec { 53 | self.ents.iter().map(|d| d.path().to_path_buf()).collect() 54 | } 55 | 56 | /// Return all the successfully retrieved directory entries, sorted 57 | /// lexicographically by their full file path. 58 | pub fn sorted_ents(&self) -> Vec { 59 | let mut ents = self.ents.clone(); 60 | ents.sort_by(|e1, e2| e1.path().cmp(e2.path())); 61 | ents 62 | } 63 | 64 | /// Return all paths from all successfully retrieved directory entries, 65 | /// sorted lexicographically. 66 | /// 67 | /// This does not include paths that correspond to an error. 68 | pub fn sorted_paths(&self) -> Vec { 69 | self.sorted_ents().into_iter().map(|d| d.into_path()).collect() 70 | } 71 | } 72 | 73 | /// A helper for managing a directory in which to run tests. 74 | /// 75 | /// When manipulating paths within this directory, paths are interpreted 76 | /// relative to this directory. 77 | #[derive(Debug)] 78 | pub struct Dir { 79 | dir: TempDir, 80 | } 81 | 82 | impl Dir { 83 | /// Create a new empty temporary directory. 84 | pub fn tmp() -> Dir { 85 | let dir = TempDir::new().unwrap(); 86 | Dir { dir } 87 | } 88 | 89 | /// Return the path to this directory. 90 | pub fn path(&self) -> &Path { 91 | self.dir.path() 92 | } 93 | 94 | /// Return a path joined to the path to this directory. 95 | pub fn join>(&self, path: P) -> PathBuf { 96 | self.path().join(path) 97 | } 98 | 99 | /// Run the given iterator and return the result as a distinct collection 100 | /// of directory entries and errors. 101 | pub fn run_recursive(&self, it: I) -> RecursiveResults 102 | where 103 | I: IntoIterator>, 104 | { 105 | let mut results = RecursiveResults { ents: vec![], errs: vec![] }; 106 | for result in it { 107 | match result { 108 | Ok(ent) => results.ents.push(ent), 109 | Err(err) => results.errs.push(err), 110 | } 111 | } 112 | results 113 | } 114 | 115 | /// Create a directory at the given path, while creating all intermediate 116 | /// directories as needed. 117 | pub fn mkdirp>(&self, path: P) { 118 | let full = self.join(path); 119 | fs::create_dir_all(&full) 120 | .map_err(|e| { 121 | err!("failed to create directory {}: {}", full.display(), e) 122 | }) 123 | .unwrap(); 124 | } 125 | 126 | /// Create an empty file at the given path. All ancestor directories must 127 | /// already exists. 128 | pub fn touch>(&self, path: P) { 129 | let full = self.join(path); 130 | File::create(&full) 131 | .map_err(|e| { 132 | err!("failed to create file {}: {}", full.display(), e) 133 | }) 134 | .unwrap(); 135 | } 136 | 137 | /// Create empty files at the given paths. All ancestor directories must 138 | /// already exists. 139 | pub fn touch_all>(&self, paths: &[P]) { 140 | for p in paths { 141 | self.touch(p); 142 | } 143 | } 144 | 145 | /// Create a file symlink to the given src with the given link name. 146 | pub fn symlink_file, P2: AsRef>( 147 | &self, 148 | src: P1, 149 | link_name: P2, 150 | ) { 151 | #[cfg(windows)] 152 | fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 153 | use std::os::windows::fs::symlink_file; 154 | symlink_file(src, link_name) 155 | } 156 | 157 | #[cfg(unix)] 158 | fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 159 | use std::os::unix::fs::symlink; 160 | symlink(src, link_name) 161 | } 162 | 163 | let (src, link_name) = (self.join(src), self.join(link_name)); 164 | imp(&src, &link_name) 165 | .map_err(|e| { 166 | err!( 167 | "failed to symlink file {} with target {}: {}", 168 | src.display(), 169 | link_name.display(), 170 | e 171 | ) 172 | }) 173 | .unwrap() 174 | } 175 | 176 | /// Create a directory symlink to the given src with the given link name. 177 | pub fn symlink_dir, P2: AsRef>( 178 | &self, 179 | src: P1, 180 | link_name: P2, 181 | ) { 182 | #[cfg(windows)] 183 | fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 184 | use std::os::windows::fs::symlink_dir; 185 | symlink_dir(src, link_name) 186 | } 187 | 188 | #[cfg(unix)] 189 | fn imp(src: &Path, link_name: &Path) -> io::Result<()> { 190 | use std::os::unix::fs::symlink; 191 | symlink(src, link_name) 192 | } 193 | 194 | let (src, link_name) = (self.join(src), self.join(link_name)); 195 | imp(&src, &link_name) 196 | .map_err(|e| { 197 | err!( 198 | "failed to symlink directory {} with target {}: {}", 199 | src.display(), 200 | link_name.display(), 201 | e 202 | ) 203 | }) 204 | .unwrap() 205 | } 206 | } 207 | 208 | /// A simple wrapper for creating a temporary directory that is automatically 209 | /// deleted when it's dropped. 210 | /// 211 | /// We use this in lieu of tempfile because tempfile brings in too many 212 | /// dependencies. 213 | #[derive(Debug)] 214 | pub struct TempDir(PathBuf); 215 | 216 | impl Drop for TempDir { 217 | fn drop(&mut self) { 218 | fs::remove_dir_all(&self.0).unwrap(); 219 | } 220 | } 221 | 222 | impl TempDir { 223 | /// Create a new empty temporary directory under the system's configured 224 | /// temporary directory. 225 | pub fn new() -> Result { 226 | #[allow(deprecated)] 227 | use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; 228 | 229 | static TRIES: usize = 100; 230 | #[allow(deprecated)] 231 | static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; 232 | 233 | let tmpdir = env::temp_dir(); 234 | for _ in 0..TRIES { 235 | let count = COUNTER.fetch_add(1, Ordering::SeqCst); 236 | let path = tmpdir.join("rust-walkdir").join(count.to_string()); 237 | if path.is_dir() { 238 | continue; 239 | } 240 | fs::create_dir_all(&path).map_err(|e| { 241 | err!("failed to create {}: {}", path.display(), e) 242 | })?; 243 | return Ok(TempDir(path)); 244 | } 245 | Err(err!("failed to create temp dir after {} tries", TRIES)) 246 | } 247 | 248 | /// Return the underlying path to this temporary directory. 249 | pub fn path(&self) -> &Path { 250 | &self.0 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | use std::io; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use crate::DirEntry; 7 | 8 | /// An error produced by recursively walking a directory. 9 | /// 10 | /// This error type is a light wrapper around [`std::io::Error`]. In 11 | /// particular, it adds the following information: 12 | /// 13 | /// * The depth at which the error occurred in the file tree, relative to the 14 | /// root. 15 | /// * The path, if any, associated with the IO error. 16 | /// * An indication that a loop occurred when following symbolic links. In this 17 | /// case, there is no underlying IO error. 18 | /// 19 | /// To maintain good ergonomics, this type has a 20 | /// [`impl From for std::io::Error`][impl] defined which preserves the original context. 21 | /// This allows you to use an [`io::Result`] with methods in this crate if you don't care about 22 | /// accessing the underlying error data in a structured form. 23 | /// 24 | /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 25 | /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html 26 | /// [impl]: struct.Error.html#impl-From%3CError%3E 27 | #[derive(Debug)] 28 | pub struct Error { 29 | depth: usize, 30 | inner: ErrorInner, 31 | } 32 | 33 | #[derive(Debug)] 34 | enum ErrorInner { 35 | Io { path: Option, err: io::Error }, 36 | Loop { ancestor: PathBuf, child: PathBuf }, 37 | } 38 | 39 | impl Error { 40 | /// Returns the path associated with this error if one exists. 41 | /// 42 | /// For example, if an error occurred while opening a directory handle, 43 | /// the error will include the path passed to [`std::fs::read_dir`]. 44 | /// 45 | /// [`std::fs::read_dir`]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html 46 | pub fn path(&self) -> Option<&Path> { 47 | match self.inner { 48 | ErrorInner::Io { path: None, .. } => None, 49 | ErrorInner::Io { path: Some(ref path), .. } => Some(path), 50 | ErrorInner::Loop { ref child, .. } => Some(child), 51 | } 52 | } 53 | 54 | /// Returns the path at which a cycle was detected. 55 | /// 56 | /// If no cycle was detected, [`None`] is returned. 57 | /// 58 | /// A cycle is detected when a directory entry is equivalent to one of 59 | /// its ancestors. 60 | /// 61 | /// To get the path to the child directory entry in the cycle, use the 62 | /// [`path`] method. 63 | /// 64 | /// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None 65 | /// [`path`]: struct.Error.html#path 66 | pub fn loop_ancestor(&self) -> Option<&Path> { 67 | match self.inner { 68 | ErrorInner::Loop { ref ancestor, .. } => Some(ancestor), 69 | _ => None, 70 | } 71 | } 72 | 73 | /// Returns the depth at which this error occurred relative to the root. 74 | /// 75 | /// The smallest depth is `0` and always corresponds to the path given to 76 | /// the [`new`] function on [`WalkDir`]. Its direct descendents have depth 77 | /// `1`, and their descendents have depth `2`, and so on. 78 | /// 79 | /// [`new`]: struct.WalkDir.html#method.new 80 | /// [`WalkDir`]: struct.WalkDir.html 81 | pub fn depth(&self) -> usize { 82 | self.depth 83 | } 84 | 85 | /// Inspect the original [`io::Error`] if there is one. 86 | /// 87 | /// [`None`] is returned if the [`Error`] doesn't correspond to an 88 | /// [`io::Error`]. This might happen, for example, when the error was 89 | /// produced because a cycle was found in the directory tree while 90 | /// following symbolic links. 91 | /// 92 | /// This method returns a borrowed value that is bound to the lifetime of the [`Error`]. To 93 | /// obtain an owned value, the [`into_io_error`] can be used instead. 94 | /// 95 | /// > This is the original [`io::Error`] and is _not_ the same as 96 | /// > [`impl From for std::io::Error`][impl] which contains additional context about the 97 | /// error. 98 | /// 99 | /// # Example 100 | /// 101 | /// ```rust,no_run 102 | /// use std::io; 103 | /// use std::path::Path; 104 | /// 105 | /// use walkdir::WalkDir; 106 | /// 107 | /// for entry in WalkDir::new("foo") { 108 | /// match entry { 109 | /// Ok(entry) => println!("{}", entry.path().display()), 110 | /// Err(err) => { 111 | /// let path = err.path().unwrap_or(Path::new("")).display(); 112 | /// println!("failed to access entry {}", path); 113 | /// if let Some(inner) = err.io_error() { 114 | /// match inner.kind() { 115 | /// io::ErrorKind::InvalidData => { 116 | /// println!( 117 | /// "entry contains invalid data: {}", 118 | /// inner) 119 | /// } 120 | /// io::ErrorKind::PermissionDenied => { 121 | /// println!( 122 | /// "Missing permission to read entry: {}", 123 | /// inner) 124 | /// } 125 | /// _ => { 126 | /// println!( 127 | /// "Unexpected error occurred: {}", 128 | /// inner) 129 | /// } 130 | /// } 131 | /// } 132 | /// } 133 | /// } 134 | /// } 135 | /// ``` 136 | /// 137 | /// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None 138 | /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 139 | /// [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html 140 | /// [`Error`]: struct.Error.html 141 | /// [`into_io_error`]: struct.Error.html#method.into_io_error 142 | /// [impl]: struct.Error.html#impl-From%3CError%3E 143 | pub fn io_error(&self) -> Option<&io::Error> { 144 | match self.inner { 145 | ErrorInner::Io { ref err, .. } => Some(err), 146 | ErrorInner::Loop { .. } => None, 147 | } 148 | } 149 | 150 | /// Similar to [`io_error`] except consumes self to convert to the original 151 | /// [`io::Error`] if one exists. 152 | /// 153 | /// [`io_error`]: struct.Error.html#method.io_error 154 | /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 155 | pub fn into_io_error(self) -> Option { 156 | match self.inner { 157 | ErrorInner::Io { err, .. } => Some(err), 158 | ErrorInner::Loop { .. } => None, 159 | } 160 | } 161 | 162 | pub(crate) fn from_path( 163 | depth: usize, 164 | pb: PathBuf, 165 | err: io::Error, 166 | ) -> Self { 167 | Error { depth, inner: ErrorInner::Io { path: Some(pb), err } } 168 | } 169 | 170 | pub(crate) fn from_entry(dent: &DirEntry, err: io::Error) -> Self { 171 | Error { 172 | depth: dent.depth(), 173 | inner: ErrorInner::Io { 174 | path: Some(dent.path().to_path_buf()), 175 | err, 176 | }, 177 | } 178 | } 179 | 180 | pub(crate) fn from_io(depth: usize, err: io::Error) -> Self { 181 | Error { depth, inner: ErrorInner::Io { path: None, err } } 182 | } 183 | 184 | pub(crate) fn from_loop( 185 | depth: usize, 186 | ancestor: &Path, 187 | child: &Path, 188 | ) -> Self { 189 | Error { 190 | depth, 191 | inner: ErrorInner::Loop { 192 | ancestor: ancestor.to_path_buf(), 193 | child: child.to_path_buf(), 194 | }, 195 | } 196 | } 197 | } 198 | 199 | impl error::Error for Error { 200 | #[allow(deprecated)] 201 | fn description(&self) -> &str { 202 | match self.inner { 203 | ErrorInner::Io { ref err, .. } => err.description(), 204 | ErrorInner::Loop { .. } => "file system loop found", 205 | } 206 | } 207 | 208 | fn cause(&self) -> Option<&dyn error::Error> { 209 | self.source() 210 | } 211 | 212 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 213 | match self.inner { 214 | ErrorInner::Io { ref err, .. } => Some(err), 215 | ErrorInner::Loop { .. } => None, 216 | } 217 | } 218 | } 219 | 220 | impl fmt::Display for Error { 221 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 222 | match self.inner { 223 | ErrorInner::Io { path: None, ref err } => err.fmt(f), 224 | ErrorInner::Io { path: Some(ref path), ref err } => write!( 225 | f, 226 | "IO error for operation on {}: {}", 227 | path.display(), 228 | err 229 | ), 230 | ErrorInner::Loop { ref ancestor, ref child } => write!( 231 | f, 232 | "File system loop found: \ 233 | {} points to an ancestor {}", 234 | child.display(), 235 | ancestor.display() 236 | ), 237 | } 238 | } 239 | } 240 | 241 | impl From for io::Error { 242 | /// Convert the [`Error`] to an [`io::Error`], preserving the original 243 | /// [`Error`] as the ["inner error"]. Note that this also makes the display 244 | /// of the error include the context. 245 | /// 246 | /// This is different from [`into_io_error`] which returns the original 247 | /// [`io::Error`]. 248 | /// 249 | /// [`Error`]: struct.Error.html 250 | /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 251 | /// ["inner error"]: https://doc.rust-lang.org/std/io/struct.Error.html#method.into_inner 252 | /// [`into_io_error`]: struct.WalkDir.html#method.into_io_error 253 | fn from(walk_err: Error) -> io::Error { 254 | let kind = match walk_err { 255 | Error { inner: ErrorInner::Io { ref err, .. }, .. } => err.kind(), 256 | Error { inner: ErrorInner::Loop { .. }, .. } => { 257 | io::ErrorKind::Other 258 | } 259 | }; 260 | io::Error::new(kind, walk_err) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /walkdir-list/main.rs: -------------------------------------------------------------------------------- 1 | // This program isn't necessarily meant to serve as an example of how to use 2 | // walkdir, but rather, is a good example of how a basic `find` utility can be 3 | // written using walkdir in a way that is both correct and as fast as possible. 4 | // This includes doing things like block buffering when not printing to a tty, 5 | // and correctly writing file paths to stdout without allocating on Unix. 6 | // 7 | // Additionally, this program is useful for demonstrating all of walkdir's 8 | // features. That is, when new functionality is added, some demonstration of 9 | // it should be added to this program. 10 | // 11 | // Finally, this can be useful for ad hoc benchmarking. e.g., See the --timeit 12 | // and --count flags. 13 | 14 | use std::error::Error; 15 | use std::ffi::OsStr; 16 | use std::io::{self, Write}; 17 | use std::path::{Path, PathBuf}; 18 | use std::process; 19 | use std::result; 20 | use std::time::Instant; 21 | 22 | use bstr::BString; 23 | use walkdir::WalkDir; 24 | 25 | type Result = result::Result>; 26 | 27 | macro_rules! err { 28 | ($($tt:tt)*) => { Err(From::from(format!($($tt)*))) } 29 | } 30 | 31 | fn main() { 32 | if let Err(err) = try_main() { 33 | eprintln!("{}", err); 34 | process::exit(1); 35 | } 36 | } 37 | 38 | fn try_main() -> Result<()> { 39 | let args = Args::parse()?; 40 | let mut stderr = io::stderr(); 41 | 42 | let start = Instant::now(); 43 | if args.count { 44 | print_count(&args, io::stdout(), &mut stderr)?; 45 | } else if atty::is(atty::Stream::Stdout) { 46 | print_paths(&args, io::stdout(), &mut stderr)?; 47 | } else { 48 | print_paths(&args, io::BufWriter::new(io::stdout()), &mut stderr)?; 49 | } 50 | if args.timeit { 51 | let since = Instant::now().duration_since(start); 52 | writeln!(stderr, "duration: {:?}", since)?; 53 | } 54 | Ok(()) 55 | } 56 | 57 | fn print_count( 58 | args: &Args, 59 | mut stdout: W1, 60 | mut stderr: W2, 61 | ) -> Result<()> 62 | where 63 | W1: io::Write, 64 | W2: io::Write, 65 | { 66 | let mut count: u64 = 0; 67 | for dir in &args.dirs { 68 | for result in args.walkdir(dir) { 69 | match result { 70 | Ok(_) => count += 1, 71 | Err(err) => { 72 | if !args.ignore_errors { 73 | writeln!(stderr, "ERROR: {}", err)?; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | writeln!(stdout, "{}", count)?; 80 | Ok(()) 81 | } 82 | 83 | fn print_paths( 84 | args: &Args, 85 | mut stdout: W1, 86 | mut stderr: W2, 87 | ) -> Result<()> 88 | where 89 | W1: io::Write, 90 | W2: io::Write, 91 | { 92 | for dir in &args.dirs { 93 | if args.tree { 94 | print_paths_tree(&args, &mut stdout, &mut stderr, dir)?; 95 | } else { 96 | print_paths_flat(&args, &mut stdout, &mut stderr, dir)?; 97 | } 98 | } 99 | Ok(()) 100 | } 101 | 102 | fn print_paths_flat( 103 | args: &Args, 104 | mut stdout: W1, 105 | mut stderr: W2, 106 | dir: &Path, 107 | ) -> Result<()> 108 | where 109 | W1: io::Write, 110 | W2: io::Write, 111 | { 112 | for result in args.walkdir(dir) { 113 | let dent = match result { 114 | Ok(dent) => dent, 115 | Err(err) => { 116 | if !args.ignore_errors { 117 | writeln!(stderr, "ERROR: {}", err)?; 118 | } 119 | continue; 120 | } 121 | }; 122 | write_path(&mut stdout, dent.path())?; 123 | stdout.write_all(b"\n")?; 124 | } 125 | Ok(()) 126 | } 127 | 128 | fn print_paths_tree( 129 | args: &Args, 130 | mut stdout: W1, 131 | mut stderr: W2, 132 | dir: &Path, 133 | ) -> Result<()> 134 | where 135 | W1: io::Write, 136 | W2: io::Write, 137 | { 138 | for result in args.walkdir(dir) { 139 | let dent = match result { 140 | Ok(dent) => dent, 141 | Err(err) => { 142 | if !args.ignore_errors { 143 | writeln!(stderr, "ERROR: {}", err)?; 144 | } 145 | continue; 146 | } 147 | }; 148 | stdout.write_all(" ".repeat(dent.depth()).as_bytes())?; 149 | write_os_str(&mut stdout, dent.file_name())?; 150 | stdout.write_all(b"\n")?; 151 | } 152 | Ok(()) 153 | } 154 | 155 | #[derive(Debug)] 156 | struct Args { 157 | dirs: Vec, 158 | follow_links: bool, 159 | min_depth: Option, 160 | max_depth: Option, 161 | max_open: Option, 162 | tree: bool, 163 | ignore_errors: bool, 164 | sort: bool, 165 | depth_first: bool, 166 | same_file_system: bool, 167 | timeit: bool, 168 | count: bool, 169 | } 170 | 171 | impl Args { 172 | fn parse() -> Result { 173 | use clap::{crate_authors, crate_version, App, Arg}; 174 | 175 | let parsed = App::new("List files using walkdir") 176 | .author(crate_authors!()) 177 | .version(crate_version!()) 178 | .max_term_width(100) 179 | .arg(Arg::with_name("dirs").multiple(true)) 180 | .arg( 181 | Arg::with_name("follow-links") 182 | .long("follow-links") 183 | .short("L") 184 | .help("Follow symbolic links."), 185 | ) 186 | .arg( 187 | Arg::with_name("min-depth") 188 | .long("min-depth") 189 | .takes_value(true) 190 | .help("Only show entries at or above this depth."), 191 | ) 192 | .arg( 193 | Arg::with_name("max-depth") 194 | .long("max-depth") 195 | .takes_value(true) 196 | .help("Only show entries at or below this depth."), 197 | ) 198 | .arg( 199 | Arg::with_name("max-open") 200 | .long("max-open") 201 | .takes_value(true) 202 | .default_value("10") 203 | .help("Use at most this many open file descriptors."), 204 | ) 205 | .arg( 206 | Arg::with_name("tree") 207 | .long("tree") 208 | .help("Show file paths in a tree."), 209 | ) 210 | .arg( 211 | Arg::with_name("ignore-errors") 212 | .long("ignore-errors") 213 | .short("q") 214 | .help("Don't print error messages."), 215 | ) 216 | .arg( 217 | Arg::with_name("sort") 218 | .long("sort") 219 | .help("Sort file paths lexicographically."), 220 | ) 221 | .arg( 222 | Arg::with_name("depth-first").long("depth-first").help( 223 | "Show directory contents before the directory path.", 224 | ), 225 | ) 226 | .arg( 227 | Arg::with_name("same-file-system") 228 | .long("same-file-system") 229 | .short("x") 230 | .help( 231 | "Only show paths on the same file system as the root.", 232 | ), 233 | ) 234 | .arg( 235 | Arg::with_name("timeit") 236 | .long("timeit") 237 | .short("t") 238 | .help("Print timing info."), 239 | ) 240 | .arg( 241 | Arg::with_name("count") 242 | .long("count") 243 | .short("c") 244 | .help("Print only a total count of all file paths."), 245 | ) 246 | .get_matches(); 247 | 248 | let dirs = match parsed.values_of_os("dirs") { 249 | None => vec![PathBuf::from("./")], 250 | Some(dirs) => dirs.map(PathBuf::from).collect(), 251 | }; 252 | Ok(Args { 253 | dirs: dirs, 254 | follow_links: parsed.is_present("follow-links"), 255 | min_depth: parse_usize(&parsed, "min-depth")?, 256 | max_depth: parse_usize(&parsed, "max-depth")?, 257 | max_open: parse_usize(&parsed, "max-open")?, 258 | tree: parsed.is_present("tree"), 259 | ignore_errors: parsed.is_present("ignore-errors"), 260 | sort: parsed.is_present("sort"), 261 | depth_first: parsed.is_present("depth-first"), 262 | same_file_system: parsed.is_present("same-file-system"), 263 | timeit: parsed.is_present("timeit"), 264 | count: parsed.is_present("count"), 265 | }) 266 | } 267 | 268 | fn walkdir(&self, path: &Path) -> WalkDir { 269 | let mut walkdir = WalkDir::new(path) 270 | .follow_links(self.follow_links) 271 | .contents_first(self.depth_first) 272 | .same_file_system(self.same_file_system); 273 | if let Some(x) = self.min_depth { 274 | walkdir = walkdir.min_depth(x); 275 | } 276 | if let Some(x) = self.max_depth { 277 | walkdir = walkdir.max_depth(x); 278 | } 279 | if let Some(x) = self.max_open { 280 | walkdir = walkdir.max_open(x); 281 | } 282 | if self.sort { 283 | walkdir = walkdir.sort_by(|a, b| a.file_name().cmp(b.file_name())); 284 | } 285 | walkdir 286 | } 287 | } 288 | 289 | fn parse_usize( 290 | parsed: &clap::ArgMatches, 291 | flag: &str, 292 | ) -> Result> { 293 | match parsed.value_of_lossy(flag) { 294 | None => Ok(None), 295 | Some(x) => x.parse().map(Some).or_else(|e| { 296 | err!("failed to parse --{} as a number: {}", flag, e) 297 | }), 298 | } 299 | } 300 | 301 | fn write_path(wtr: W, path: &Path) -> io::Result<()> { 302 | write_os_str(wtr, path.as_os_str()) 303 | } 304 | 305 | fn write_os_str(mut wtr: W, os: &OsStr) -> io::Result<()> { 306 | // On Unix, this is a no-op, and correctly prints raw paths. On Windows, 307 | // this lossily converts paths that originally contained invalid UTF-16 308 | // to paths that will ultimately contain only valid UTF-16. This doesn't 309 | // correctly print all possible paths, but on Windows, one can't print 310 | // invalid UTF-16 to a console anyway. 311 | wtr.write_all(BString::from_os_str_lossy(os).as_bytes()) 312 | } 313 | -------------------------------------------------------------------------------- /src/dent.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::fmt; 3 | use std::fs::{self, FileType}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use crate::error::Error; 7 | use crate::Result; 8 | 9 | /// A directory entry. 10 | /// 11 | /// This is the type of value that is yielded from the iterators defined in 12 | /// this crate. 13 | /// 14 | /// On Unix systems, this type implements the [`DirEntryExt`] trait, which 15 | /// provides efficient access to the inode number of the directory entry. 16 | /// 17 | /// # Differences with `std::fs::DirEntry` 18 | /// 19 | /// This type mostly mirrors the type by the same name in [`std::fs`]. There 20 | /// are some differences however: 21 | /// 22 | /// * All recursive directory iterators must inspect the entry's type. 23 | /// Therefore, the value is stored and its access is guaranteed to be cheap and 24 | /// successful. 25 | /// * [`path`] and [`file_name`] return borrowed variants. 26 | /// * If [`follow_links`] was enabled on the originating iterator, then all 27 | /// operations except for [`path`] operate on the link target. Otherwise, all 28 | /// operations operate on the symbolic link. 29 | /// 30 | /// [`std::fs`]: https://doc.rust-lang.org/stable/std/fs/index.html 31 | /// [`path`]: #method.path 32 | /// [`file_name`]: #method.file_name 33 | /// [`follow_links`]: struct.WalkDir.html#method.follow_links 34 | /// [`DirEntryExt`]: trait.DirEntryExt.html 35 | pub struct DirEntry { 36 | /// The path as reported by the [`fs::ReadDir`] iterator (even if it's a 37 | /// symbolic link). 38 | /// 39 | /// [`fs::ReadDir`]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html 40 | path: PathBuf, 41 | /// The file type. Necessary for recursive iteration, so store it. 42 | ty: FileType, 43 | /// Is set when this entry was created from a symbolic link and the user 44 | /// expects the iterator to follow symbolic links. 45 | follow_link: bool, 46 | /// The depth at which this entry was generated relative to the root. 47 | depth: usize, 48 | /// The underlying inode number (Unix only). 49 | #[cfg(unix)] 50 | ino: u64, 51 | /// The underlying metadata (Windows only). We store this on Windows 52 | /// because this comes for free while reading a directory. 53 | /// 54 | /// We use this to determine whether an entry is a directory or not, which 55 | /// works around a bug in Rust's standard library: 56 | /// https://github.com/rust-lang/rust/issues/46484 57 | #[cfg(windows)] 58 | metadata: fs::Metadata, 59 | } 60 | 61 | impl DirEntry { 62 | /// The full path that this entry represents. 63 | /// 64 | /// The full path is created by joining the parents of this entry up to the 65 | /// root initially given to [`WalkDir::new`] with the file name of this 66 | /// entry. 67 | /// 68 | /// Note that this *always* returns the path reported by the underlying 69 | /// directory entry, even when symbolic links are followed. To get the 70 | /// target path, use [`path_is_symlink`] to (cheaply) check if this entry 71 | /// corresponds to a symbolic link, and [`std::fs::read_link`] to resolve 72 | /// the target. 73 | /// 74 | /// [`WalkDir::new`]: struct.WalkDir.html#method.new 75 | /// [`path_is_symlink`]: struct.DirEntry.html#method.path_is_symlink 76 | /// [`std::fs::read_link`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html 77 | pub fn path(&self) -> &Path { 78 | &self.path 79 | } 80 | 81 | /// The full path that this entry represents. 82 | /// 83 | /// Analogous to [`path`], but moves ownership of the path. 84 | /// 85 | /// [`path`]: struct.DirEntry.html#method.path 86 | pub fn into_path(self) -> PathBuf { 87 | self.path 88 | } 89 | 90 | /// Returns `true` if and only if this entry was created from a symbolic 91 | /// link. This is unaffected by the [`follow_links`] setting. 92 | /// 93 | /// When `true`, the value returned by the [`path`] method is a 94 | /// symbolic link name. To get the full target path, you must call 95 | /// [`std::fs::read_link(entry.path())`]. 96 | /// 97 | /// [`path`]: struct.DirEntry.html#method.path 98 | /// [`follow_links`]: struct.WalkDir.html#method.follow_links 99 | /// [`std::fs::read_link(entry.path())`]: https://doc.rust-lang.org/stable/std/fs/fn.read_link.html 100 | pub fn path_is_symlink(&self) -> bool { 101 | self.ty.is_symlink() || self.follow_link 102 | } 103 | 104 | /// Return the metadata for the file that this entry points to. 105 | /// 106 | /// This will follow symbolic links if and only if the [`WalkDir`] value 107 | /// has [`follow_links`] enabled. 108 | /// 109 | /// # Platform behavior 110 | /// 111 | /// This always calls [`std::fs::symlink_metadata`]. 112 | /// 113 | /// If this entry is a symbolic link and [`follow_links`] is enabled, then 114 | /// [`std::fs::metadata`] is called instead. 115 | /// 116 | /// # Errors 117 | /// 118 | /// Similar to [`std::fs::metadata`], returns errors for path values that 119 | /// the program does not have permissions to access or if the path does not 120 | /// exist. 121 | /// 122 | /// [`WalkDir`]: struct.WalkDir.html 123 | /// [`follow_links`]: struct.WalkDir.html#method.follow_links 124 | /// [`std::fs::metadata`]: https://doc.rust-lang.org/std/fs/fn.metadata.html 125 | /// [`std::fs::symlink_metadata`]: https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html 126 | pub fn metadata(&self) -> Result { 127 | self.metadata_internal() 128 | } 129 | 130 | #[cfg(windows)] 131 | fn metadata_internal(&self) -> Result { 132 | if self.follow_link { 133 | fs::metadata(&self.path) 134 | } else { 135 | Ok(self.metadata.clone()) 136 | } 137 | .map_err(|err| Error::from_entry(self, err)) 138 | } 139 | 140 | #[cfg(not(windows))] 141 | fn metadata_internal(&self) -> Result { 142 | if self.follow_link { 143 | fs::metadata(&self.path) 144 | } else { 145 | fs::symlink_metadata(&self.path) 146 | } 147 | .map_err(|err| Error::from_entry(self, err)) 148 | } 149 | 150 | /// Return the file type for the file that this entry points to. 151 | /// 152 | /// If this is a symbolic link and [`follow_links`] is `true`, then this 153 | /// returns the type of the target. 154 | /// 155 | /// This never makes any system calls. 156 | /// 157 | /// [`follow_links`]: struct.WalkDir.html#method.follow_links 158 | pub fn file_type(&self) -> fs::FileType { 159 | self.ty 160 | } 161 | 162 | /// Return the file name of this entry. 163 | /// 164 | /// If this entry has no file name (e.g., `/`), then the full path is 165 | /// returned. 166 | pub fn file_name(&self) -> &OsStr { 167 | self.path.file_name().unwrap_or_else(|| self.path.as_os_str()) 168 | } 169 | 170 | /// Returns the depth at which this entry was created relative to the root. 171 | /// 172 | /// The smallest depth is `0` and always corresponds to the path given 173 | /// to the `new` function on `WalkDir`. Its direct descendents have depth 174 | /// `1`, and their descendents have depth `2`, and so on. 175 | pub fn depth(&self) -> usize { 176 | self.depth 177 | } 178 | 179 | /// Returns true if and only if this entry points to a directory. 180 | pub(crate) fn is_dir(&self) -> bool { 181 | self.ty.is_dir() 182 | } 183 | 184 | #[cfg(windows)] 185 | pub(crate) fn from_entry( 186 | depth: usize, 187 | ent: &fs::DirEntry, 188 | ) -> Result { 189 | let path = ent.path(); 190 | let ty = ent 191 | .file_type() 192 | .map_err(|err| Error::from_path(depth, path.clone(), err))?; 193 | let md = ent 194 | .metadata() 195 | .map_err(|err| Error::from_path(depth, path.clone(), err))?; 196 | Ok(DirEntry { path, ty, follow_link: false, depth, metadata: md }) 197 | } 198 | 199 | #[cfg(unix)] 200 | pub(crate) fn from_entry( 201 | depth: usize, 202 | ent: &fs::DirEntry, 203 | ) -> Result { 204 | use std::os::unix::fs::DirEntryExt; 205 | 206 | let ty = ent 207 | .file_type() 208 | .map_err(|err| Error::from_path(depth, ent.path(), err))?; 209 | Ok(DirEntry { 210 | path: ent.path(), 211 | ty, 212 | follow_link: false, 213 | depth, 214 | ino: ent.ino(), 215 | }) 216 | } 217 | 218 | #[cfg(not(any(unix, windows)))] 219 | pub(crate) fn from_entry( 220 | depth: usize, 221 | ent: &fs::DirEntry, 222 | ) -> Result { 223 | let ty = ent 224 | .file_type() 225 | .map_err(|err| Error::from_path(depth, ent.path(), err))?; 226 | Ok(DirEntry { path: ent.path(), ty, follow_link: false, depth }) 227 | } 228 | 229 | #[cfg(windows)] 230 | pub(crate) fn from_path( 231 | depth: usize, 232 | pb: PathBuf, 233 | follow: bool, 234 | ) -> Result { 235 | let md = if follow { 236 | fs::metadata(&pb) 237 | .map_err(|err| Error::from_path(depth, pb.clone(), err))? 238 | } else { 239 | fs::symlink_metadata(&pb) 240 | .map_err(|err| Error::from_path(depth, pb.clone(), err))? 241 | }; 242 | Ok(DirEntry { 243 | path: pb, 244 | ty: md.file_type(), 245 | follow_link: follow, 246 | depth, 247 | metadata: md, 248 | }) 249 | } 250 | 251 | #[cfg(unix)] 252 | pub(crate) fn from_path( 253 | depth: usize, 254 | pb: PathBuf, 255 | follow: bool, 256 | ) -> Result { 257 | use std::os::unix::fs::MetadataExt; 258 | 259 | let md = if follow { 260 | fs::metadata(&pb) 261 | .map_err(|err| Error::from_path(depth, pb.clone(), err))? 262 | } else { 263 | fs::symlink_metadata(&pb) 264 | .map_err(|err| Error::from_path(depth, pb.clone(), err))? 265 | }; 266 | Ok(DirEntry { 267 | path: pb, 268 | ty: md.file_type(), 269 | follow_link: follow, 270 | depth, 271 | ino: md.ino(), 272 | }) 273 | } 274 | 275 | #[cfg(not(any(unix, windows)))] 276 | pub(crate) fn from_path( 277 | depth: usize, 278 | pb: PathBuf, 279 | follow: bool, 280 | ) -> Result { 281 | let md = if follow { 282 | fs::metadata(&pb) 283 | .map_err(|err| Error::from_path(depth, pb.clone(), err))? 284 | } else { 285 | fs::symlink_metadata(&pb) 286 | .map_err(|err| Error::from_path(depth, pb.clone(), err))? 287 | }; 288 | Ok(DirEntry { 289 | path: pb, 290 | ty: md.file_type(), 291 | follow_link: follow, 292 | depth, 293 | }) 294 | } 295 | } 296 | 297 | impl Clone for DirEntry { 298 | #[cfg(windows)] 299 | fn clone(&self) -> DirEntry { 300 | DirEntry { 301 | path: self.path.clone(), 302 | ty: self.ty, 303 | follow_link: self.follow_link, 304 | depth: self.depth, 305 | metadata: self.metadata.clone(), 306 | } 307 | } 308 | 309 | #[cfg(unix)] 310 | fn clone(&self) -> DirEntry { 311 | DirEntry { 312 | path: self.path.clone(), 313 | ty: self.ty, 314 | follow_link: self.follow_link, 315 | depth: self.depth, 316 | ino: self.ino, 317 | } 318 | } 319 | 320 | #[cfg(not(any(unix, windows)))] 321 | fn clone(&self) -> DirEntry { 322 | DirEntry { 323 | path: self.path.clone(), 324 | ty: self.ty, 325 | follow_link: self.follow_link, 326 | depth: self.depth, 327 | } 328 | } 329 | } 330 | 331 | impl fmt::Debug for DirEntry { 332 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 333 | write!(f, "DirEntry({:?})", self.path) 334 | } 335 | } 336 | 337 | /// Unix-specific extension methods for `walkdir::DirEntry` 338 | #[cfg(unix)] 339 | pub trait DirEntryExt { 340 | /// Returns the underlying `d_ino` field in the contained `dirent` 341 | /// structure. 342 | fn ino(&self) -> u64; 343 | } 344 | 345 | #[cfg(unix)] 346 | impl DirEntryExt for DirEntry { 347 | /// Returns the underlying `d_ino` field in the contained `dirent` 348 | /// structure. 349 | fn ino(&self) -> u64 { 350 | self.ino 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/tests/recursive.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use crate::tests::util::Dir; 5 | use crate::WalkDir; 6 | 7 | #[test] 8 | fn send_sync_traits() { 9 | use crate::{FilterEntry, IntoIter}; 10 | 11 | fn assert_send() {} 12 | fn assert_sync() {} 13 | 14 | assert_send::(); 15 | assert_sync::(); 16 | assert_send::(); 17 | assert_sync::(); 18 | assert_send::>(); 19 | assert_sync::>(); 20 | } 21 | 22 | #[test] 23 | fn empty() { 24 | let dir = Dir::tmp(); 25 | let wd = WalkDir::new(dir.path()); 26 | let r = dir.run_recursive(wd); 27 | r.assert_no_errors(); 28 | 29 | assert_eq!(1, r.ents().len()); 30 | let ent = &r.ents()[0]; 31 | assert!(ent.file_type().is_dir()); 32 | assert!(!ent.path_is_symlink()); 33 | assert_eq!(0, ent.depth()); 34 | assert_eq!(dir.path(), ent.path()); 35 | assert_eq!(dir.path().file_name().unwrap(), ent.file_name()); 36 | } 37 | 38 | #[test] 39 | fn empty_follow() { 40 | let dir = Dir::tmp(); 41 | let wd = WalkDir::new(dir.path()).follow_links(true); 42 | let r = dir.run_recursive(wd); 43 | r.assert_no_errors(); 44 | 45 | assert_eq!(1, r.ents().len()); 46 | let ent = &r.ents()[0]; 47 | assert!(ent.file_type().is_dir()); 48 | assert!(!ent.path_is_symlink()); 49 | assert_eq!(0, ent.depth()); 50 | assert_eq!(dir.path(), ent.path()); 51 | assert_eq!(dir.path().file_name().unwrap(), ent.file_name()); 52 | } 53 | 54 | #[test] 55 | fn empty_file() { 56 | let dir = Dir::tmp(); 57 | dir.touch("a"); 58 | 59 | let wd = WalkDir::new(dir.path().join("a")); 60 | let r = dir.run_recursive(wd); 61 | r.assert_no_errors(); 62 | 63 | assert_eq!(1, r.ents().len()); 64 | let ent = &r.ents()[0]; 65 | assert!(ent.file_type().is_file()); 66 | assert!(!ent.path_is_symlink()); 67 | assert_eq!(0, ent.depth()); 68 | assert_eq!(dir.join("a"), ent.path()); 69 | assert_eq!("a", ent.file_name()); 70 | } 71 | 72 | #[test] 73 | fn empty_file_follow() { 74 | let dir = Dir::tmp(); 75 | dir.touch("a"); 76 | 77 | let wd = WalkDir::new(dir.path().join("a")).follow_links(true); 78 | let r = dir.run_recursive(wd); 79 | r.assert_no_errors(); 80 | 81 | assert_eq!(1, r.ents().len()); 82 | let ent = &r.ents()[0]; 83 | assert!(ent.file_type().is_file()); 84 | assert!(!ent.path_is_symlink()); 85 | assert_eq!(0, ent.depth()); 86 | assert_eq!(dir.join("a"), ent.path()); 87 | assert_eq!("a", ent.file_name()); 88 | } 89 | 90 | #[test] 91 | fn one_dir() { 92 | let dir = Dir::tmp(); 93 | dir.mkdirp("a"); 94 | 95 | let wd = WalkDir::new(dir.path()); 96 | let r = dir.run_recursive(wd); 97 | r.assert_no_errors(); 98 | 99 | let ents = r.ents(); 100 | assert_eq!(2, ents.len()); 101 | let ent = &ents[1]; 102 | assert_eq!(dir.join("a"), ent.path()); 103 | assert_eq!(1, ent.depth()); 104 | assert_eq!("a", ent.file_name()); 105 | assert!(ent.file_type().is_dir()); 106 | } 107 | 108 | #[test] 109 | fn one_file() { 110 | let dir = Dir::tmp(); 111 | dir.touch("a"); 112 | 113 | let wd = WalkDir::new(dir.path()); 114 | let r = dir.run_recursive(wd); 115 | r.assert_no_errors(); 116 | 117 | let ents = r.ents(); 118 | assert_eq!(2, ents.len()); 119 | let ent = &ents[1]; 120 | assert_eq!(dir.join("a"), ent.path()); 121 | assert_eq!(1, ent.depth()); 122 | assert_eq!("a", ent.file_name()); 123 | assert!(ent.file_type().is_file()); 124 | } 125 | 126 | #[test] 127 | fn one_dir_one_file() { 128 | let dir = Dir::tmp(); 129 | dir.mkdirp("foo"); 130 | dir.touch("foo/a"); 131 | 132 | let wd = WalkDir::new(dir.path()); 133 | let r = dir.run_recursive(wd); 134 | r.assert_no_errors(); 135 | 136 | let expected = vec![ 137 | dir.path().to_path_buf(), 138 | dir.join("foo"), 139 | dir.join("foo").join("a"), 140 | ]; 141 | assert_eq!(expected, r.sorted_paths()); 142 | } 143 | 144 | #[test] 145 | fn many_files() { 146 | let dir = Dir::tmp(); 147 | dir.mkdirp("foo"); 148 | dir.touch_all(&["foo/a", "foo/b", "foo/c"]); 149 | 150 | let wd = WalkDir::new(dir.path()); 151 | let r = dir.run_recursive(wd); 152 | r.assert_no_errors(); 153 | 154 | let expected = vec![ 155 | dir.path().to_path_buf(), 156 | dir.join("foo"), 157 | dir.join("foo").join("a"), 158 | dir.join("foo").join("b"), 159 | dir.join("foo").join("c"), 160 | ]; 161 | assert_eq!(expected, r.sorted_paths()); 162 | } 163 | 164 | #[test] 165 | fn many_dirs() { 166 | let dir = Dir::tmp(); 167 | dir.mkdirp("foo/a"); 168 | dir.mkdirp("foo/b"); 169 | dir.mkdirp("foo/c"); 170 | 171 | let wd = WalkDir::new(dir.path()); 172 | let r = dir.run_recursive(wd); 173 | r.assert_no_errors(); 174 | 175 | let expected = vec![ 176 | dir.path().to_path_buf(), 177 | dir.join("foo"), 178 | dir.join("foo").join("a"), 179 | dir.join("foo").join("b"), 180 | dir.join("foo").join("c"), 181 | ]; 182 | assert_eq!(expected, r.sorted_paths()); 183 | } 184 | 185 | #[test] 186 | fn many_mixed() { 187 | let dir = Dir::tmp(); 188 | dir.mkdirp("foo/a"); 189 | dir.mkdirp("foo/c"); 190 | dir.mkdirp("foo/e"); 191 | dir.touch_all(&["foo/b", "foo/d", "foo/f"]); 192 | 193 | let wd = WalkDir::new(dir.path()); 194 | let r = dir.run_recursive(wd); 195 | r.assert_no_errors(); 196 | 197 | let expected = vec![ 198 | dir.path().to_path_buf(), 199 | dir.join("foo"), 200 | dir.join("foo").join("a"), 201 | dir.join("foo").join("b"), 202 | dir.join("foo").join("c"), 203 | dir.join("foo").join("d"), 204 | dir.join("foo").join("e"), 205 | dir.join("foo").join("f"), 206 | ]; 207 | assert_eq!(expected, r.sorted_paths()); 208 | } 209 | 210 | #[test] 211 | fn nested() { 212 | let nested = 213 | PathBuf::from("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"); 214 | let dir = Dir::tmp(); 215 | dir.mkdirp(&nested); 216 | dir.touch(nested.join("A")); 217 | 218 | let wd = WalkDir::new(dir.path()); 219 | let r = dir.run_recursive(wd); 220 | r.assert_no_errors(); 221 | 222 | let expected = vec![ 223 | dir.path().to_path_buf(), 224 | dir.join("a"), 225 | dir.join("a/b"), 226 | dir.join("a/b/c"), 227 | dir.join("a/b/c/d"), 228 | dir.join("a/b/c/d/e"), 229 | dir.join("a/b/c/d/e/f"), 230 | dir.join("a/b/c/d/e/f/g"), 231 | dir.join("a/b/c/d/e/f/g/h"), 232 | dir.join("a/b/c/d/e/f/g/h/i"), 233 | dir.join("a/b/c/d/e/f/g/h/i/j"), 234 | dir.join("a/b/c/d/e/f/g/h/i/j/k"), 235 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l"), 236 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m"), 237 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n"), 238 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o"), 239 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p"), 240 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q"), 241 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r"), 242 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s"), 243 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t"), 244 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u"), 245 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v"), 246 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w"), 247 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x"), 248 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y"), 249 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"), 250 | dir.join(&nested).join("A"), 251 | ]; 252 | assert_eq!(expected, r.sorted_paths()); 253 | } 254 | 255 | #[test] 256 | fn nested_small_max_open() { 257 | let nested = 258 | PathBuf::from("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"); 259 | let dir = Dir::tmp(); 260 | dir.mkdirp(&nested); 261 | dir.touch(nested.join("A")); 262 | 263 | let wd = WalkDir::new(dir.path()).max_open(1); 264 | let r = dir.run_recursive(wd); 265 | r.assert_no_errors(); 266 | 267 | let expected = vec![ 268 | dir.path().to_path_buf(), 269 | dir.join("a"), 270 | dir.join("a/b"), 271 | dir.join("a/b/c"), 272 | dir.join("a/b/c/d"), 273 | dir.join("a/b/c/d/e"), 274 | dir.join("a/b/c/d/e/f"), 275 | dir.join("a/b/c/d/e/f/g"), 276 | dir.join("a/b/c/d/e/f/g/h"), 277 | dir.join("a/b/c/d/e/f/g/h/i"), 278 | dir.join("a/b/c/d/e/f/g/h/i/j"), 279 | dir.join("a/b/c/d/e/f/g/h/i/j/k"), 280 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l"), 281 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m"), 282 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n"), 283 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o"), 284 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p"), 285 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q"), 286 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r"), 287 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s"), 288 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t"), 289 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u"), 290 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v"), 291 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w"), 292 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x"), 293 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y"), 294 | dir.join("a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"), 295 | dir.join(&nested).join("A"), 296 | ]; 297 | assert_eq!(expected, r.sorted_paths()); 298 | } 299 | 300 | #[test] 301 | fn siblings() { 302 | let dir = Dir::tmp(); 303 | dir.mkdirp("foo"); 304 | dir.mkdirp("bar"); 305 | dir.touch_all(&["foo/a", "foo/b"]); 306 | dir.touch_all(&["bar/a", "bar/b"]); 307 | 308 | let wd = WalkDir::new(dir.path()); 309 | let r = dir.run_recursive(wd); 310 | r.assert_no_errors(); 311 | 312 | let expected = vec![ 313 | dir.path().to_path_buf(), 314 | dir.join("bar"), 315 | dir.join("bar").join("a"), 316 | dir.join("bar").join("b"), 317 | dir.join("foo"), 318 | dir.join("foo").join("a"), 319 | dir.join("foo").join("b"), 320 | ]; 321 | assert_eq!(expected, r.sorted_paths()); 322 | } 323 | 324 | #[test] 325 | fn sym_root_file_nofollow() { 326 | let dir = Dir::tmp(); 327 | dir.touch("a"); 328 | dir.symlink_file("a", "a-link"); 329 | 330 | let wd = WalkDir::new(dir.join("a-link")); 331 | let r = dir.run_recursive(wd); 332 | r.assert_no_errors(); 333 | 334 | let ents = r.sorted_ents(); 335 | assert_eq!(1, ents.len()); 336 | let link = &ents[0]; 337 | 338 | assert_eq!(dir.join("a-link"), link.path()); 339 | 340 | assert!(link.path_is_symlink()); 341 | 342 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 343 | 344 | assert_eq!(0, link.depth()); 345 | 346 | assert!(link.file_type().is_symlink()); 347 | assert!(!link.file_type().is_file()); 348 | assert!(!link.file_type().is_dir()); 349 | 350 | assert!(link.metadata().unwrap().file_type().is_symlink()); 351 | assert!(!link.metadata().unwrap().is_file()); 352 | assert!(!link.metadata().unwrap().is_dir()); 353 | } 354 | 355 | #[test] 356 | fn sym_root_file_follow() { 357 | let dir = Dir::tmp(); 358 | dir.touch("a"); 359 | dir.symlink_file("a", "a-link"); 360 | 361 | let wd = WalkDir::new(dir.join("a-link")).follow_links(true); 362 | let r = dir.run_recursive(wd); 363 | r.assert_no_errors(); 364 | 365 | let ents = r.sorted_ents(); 366 | let link = &ents[0]; 367 | 368 | assert_eq!(dir.join("a-link"), link.path()); 369 | 370 | assert!(link.path_is_symlink()); 371 | 372 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 373 | 374 | assert_eq!(0, link.depth()); 375 | 376 | assert!(!link.file_type().is_symlink()); 377 | assert!(link.file_type().is_file()); 378 | assert!(!link.file_type().is_dir()); 379 | 380 | assert!(!link.metadata().unwrap().file_type().is_symlink()); 381 | assert!(link.metadata().unwrap().is_file()); 382 | assert!(!link.metadata().unwrap().is_dir()); 383 | } 384 | 385 | #[test] 386 | fn broken_sym_root_dir_nofollow_and_root_nofollow() { 387 | let dir = Dir::tmp(); 388 | dir.symlink_dir("broken", "a-link"); 389 | 390 | let wd = WalkDir::new(dir.join("a-link")) 391 | .follow_links(false) 392 | .follow_root_links(false); 393 | let r = dir.run_recursive(wd); 394 | let ents = r.sorted_ents(); 395 | assert_eq!(ents.len(), 1); 396 | let link = &ents[0]; 397 | assert_eq!(dir.join("a-link"), link.path()); 398 | assert!(link.path_is_symlink()); 399 | } 400 | 401 | #[test] 402 | fn broken_sym_root_dir_follow_and_root_nofollow() { 403 | let dir = Dir::tmp(); 404 | dir.symlink_dir("broken", "a-link"); 405 | 406 | let wd = WalkDir::new(dir.join("a-link")) 407 | .follow_links(true) 408 | .follow_root_links(false); 409 | let r = dir.run_recursive(wd); 410 | assert!(r.sorted_ents().is_empty()); 411 | assert_eq!( 412 | r.errs().len(), 413 | 1, 414 | "broken symlink cannot be traversed - they are followed if symlinks are followed" 415 | ); 416 | } 417 | 418 | #[test] 419 | fn broken_sym_root_dir_root_is_always_followed() { 420 | let dir = Dir::tmp(); 421 | dir.symlink_dir("broken", "a-link"); 422 | 423 | for follow_symlinks in &[true, false] { 424 | let wd = 425 | WalkDir::new(dir.join("a-link")).follow_links(*follow_symlinks); 426 | let r = dir.run_recursive(wd); 427 | assert!(r.sorted_ents().is_empty()); 428 | assert_eq!( 429 | r.errs().len(), 430 | 1, 431 | "broken symlink in roots cannot be traversed, they are always followed" 432 | ); 433 | } 434 | } 435 | 436 | #[test] 437 | fn sym_root_dir_nofollow_root_nofollow() { 438 | let dir = Dir::tmp(); 439 | dir.mkdirp("a"); 440 | dir.symlink_dir("a", "a-link"); 441 | dir.touch("a/zzz"); 442 | 443 | let wd = WalkDir::new(dir.join("a-link")).follow_root_links(false); 444 | let r = dir.run_recursive(wd); 445 | r.assert_no_errors(); 446 | 447 | let ents = r.sorted_ents(); 448 | assert_eq!(1, ents.len()); 449 | let link = &ents[0]; 450 | assert_eq!(dir.join("a-link"), link.path()); 451 | assert_eq!(0, link.depth()); 452 | } 453 | 454 | #[test] 455 | fn sym_root_dir_nofollow_root_follow() { 456 | let dir = Dir::tmp(); 457 | dir.mkdirp("a"); 458 | dir.symlink_dir("a", "a-link"); 459 | dir.touch("a/zzz"); 460 | 461 | let wd = WalkDir::new(dir.join("a-link")); 462 | let r = dir.run_recursive(wd); 463 | r.assert_no_errors(); 464 | 465 | let ents = r.sorted_ents(); 466 | assert_eq!(2, ents.len()); 467 | let link = &ents[0]; 468 | 469 | assert_eq!(dir.join("a-link"), link.path()); 470 | 471 | assert!(link.path_is_symlink()); 472 | 473 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 474 | 475 | assert_eq!(0, link.depth()); 476 | 477 | assert!(link.file_type().is_symlink()); 478 | assert!(!link.file_type().is_file()); 479 | assert!(!link.file_type().is_dir()); 480 | 481 | assert!(link.metadata().unwrap().file_type().is_symlink()); 482 | assert!(!link.metadata().unwrap().is_file()); 483 | assert!(!link.metadata().unwrap().is_dir()); 484 | 485 | let link_zzz = &ents[1]; 486 | assert_eq!(dir.join("a-link").join("zzz"), link_zzz.path()); 487 | assert!(!link_zzz.path_is_symlink()); 488 | } 489 | 490 | #[test] 491 | fn sym_root_dir_follow() { 492 | let dir = Dir::tmp(); 493 | dir.mkdirp("a"); 494 | dir.symlink_dir("a", "a-link"); 495 | dir.touch("a/zzz"); 496 | 497 | let wd = WalkDir::new(dir.join("a-link")).follow_links(true); 498 | let r = dir.run_recursive(wd); 499 | r.assert_no_errors(); 500 | 501 | let ents = r.sorted_ents(); 502 | assert_eq!(2, ents.len()); 503 | let link = &ents[0]; 504 | 505 | assert_eq!(dir.join("a-link"), link.path()); 506 | 507 | assert!(link.path_is_symlink()); 508 | 509 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 510 | 511 | assert_eq!(0, link.depth()); 512 | 513 | assert!(!link.file_type().is_symlink()); 514 | assert!(!link.file_type().is_file()); 515 | assert!(link.file_type().is_dir()); 516 | 517 | assert!(!link.metadata().unwrap().file_type().is_symlink()); 518 | assert!(!link.metadata().unwrap().is_file()); 519 | assert!(link.metadata().unwrap().is_dir()); 520 | 521 | let link_zzz = &ents[1]; 522 | assert_eq!(dir.join("a-link").join("zzz"), link_zzz.path()); 523 | assert!(!link_zzz.path_is_symlink()); 524 | } 525 | 526 | #[test] 527 | fn sym_file_nofollow() { 528 | let dir = Dir::tmp(); 529 | dir.touch("a"); 530 | dir.symlink_file("a", "a-link"); 531 | 532 | let wd = WalkDir::new(dir.path()); 533 | let r = dir.run_recursive(wd); 534 | r.assert_no_errors(); 535 | 536 | let ents = r.sorted_ents(); 537 | assert_eq!(3, ents.len()); 538 | let (src, link) = (&ents[1], &ents[2]); 539 | 540 | assert_eq!(dir.join("a"), src.path()); 541 | assert_eq!(dir.join("a-link"), link.path()); 542 | 543 | assert!(!src.path_is_symlink()); 544 | assert!(link.path_is_symlink()); 545 | 546 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 547 | 548 | assert_eq!(1, src.depth()); 549 | assert_eq!(1, link.depth()); 550 | 551 | assert!(src.file_type().is_file()); 552 | assert!(link.file_type().is_symlink()); 553 | assert!(!link.file_type().is_file()); 554 | assert!(!link.file_type().is_dir()); 555 | 556 | assert!(src.metadata().unwrap().is_file()); 557 | assert!(link.metadata().unwrap().file_type().is_symlink()); 558 | assert!(!link.metadata().unwrap().is_file()); 559 | assert!(!link.metadata().unwrap().is_dir()); 560 | } 561 | 562 | #[test] 563 | fn sym_file_follow() { 564 | let dir = Dir::tmp(); 565 | dir.touch("a"); 566 | dir.symlink_file("a", "a-link"); 567 | 568 | let wd = WalkDir::new(dir.path()).follow_links(true); 569 | let r = dir.run_recursive(wd); 570 | r.assert_no_errors(); 571 | 572 | let ents = r.sorted_ents(); 573 | assert_eq!(3, ents.len()); 574 | let (src, link) = (&ents[1], &ents[2]); 575 | 576 | assert_eq!(dir.join("a"), src.path()); 577 | assert_eq!(dir.join("a-link"), link.path()); 578 | 579 | assert!(!src.path_is_symlink()); 580 | assert!(link.path_is_symlink()); 581 | 582 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 583 | 584 | assert_eq!(1, src.depth()); 585 | assert_eq!(1, link.depth()); 586 | 587 | assert!(src.file_type().is_file()); 588 | assert!(!link.file_type().is_symlink()); 589 | assert!(link.file_type().is_file()); 590 | assert!(!link.file_type().is_dir()); 591 | 592 | assert!(src.metadata().unwrap().is_file()); 593 | assert!(!link.metadata().unwrap().file_type().is_symlink()); 594 | assert!(link.metadata().unwrap().is_file()); 595 | assert!(!link.metadata().unwrap().is_dir()); 596 | } 597 | 598 | #[test] 599 | fn sym_dir_nofollow() { 600 | let dir = Dir::tmp(); 601 | dir.mkdirp("a"); 602 | dir.symlink_dir("a", "a-link"); 603 | dir.touch("a/zzz"); 604 | 605 | let wd = WalkDir::new(dir.path()); 606 | let r = dir.run_recursive(wd); 607 | r.assert_no_errors(); 608 | 609 | let ents = r.sorted_ents(); 610 | assert_eq!(4, ents.len()); 611 | let (src, link) = (&ents[1], &ents[3]); 612 | 613 | assert_eq!(dir.join("a"), src.path()); 614 | assert_eq!(dir.join("a-link"), link.path()); 615 | 616 | assert!(!src.path_is_symlink()); 617 | assert!(link.path_is_symlink()); 618 | 619 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 620 | 621 | assert_eq!(1, src.depth()); 622 | assert_eq!(1, link.depth()); 623 | 624 | assert!(src.file_type().is_dir()); 625 | assert!(link.file_type().is_symlink()); 626 | assert!(!link.file_type().is_file()); 627 | assert!(!link.file_type().is_dir()); 628 | 629 | assert!(src.metadata().unwrap().is_dir()); 630 | assert!(link.metadata().unwrap().file_type().is_symlink()); 631 | assert!(!link.metadata().unwrap().is_file()); 632 | assert!(!link.metadata().unwrap().is_dir()); 633 | } 634 | 635 | #[test] 636 | fn sym_dir_follow() { 637 | let dir = Dir::tmp(); 638 | dir.mkdirp("a"); 639 | dir.symlink_dir("a", "a-link"); 640 | dir.touch("a/zzz"); 641 | 642 | let wd = WalkDir::new(dir.path()).follow_links(true); 643 | let r = dir.run_recursive(wd); 644 | r.assert_no_errors(); 645 | 646 | let ents = r.sorted_ents(); 647 | assert_eq!(5, ents.len()); 648 | let (src, link) = (&ents[1], &ents[3]); 649 | 650 | assert_eq!(dir.join("a"), src.path()); 651 | assert_eq!(dir.join("a-link"), link.path()); 652 | 653 | assert!(!src.path_is_symlink()); 654 | assert!(link.path_is_symlink()); 655 | 656 | assert_eq!(dir.join("a"), fs::read_link(link.path()).unwrap()); 657 | 658 | assert_eq!(1, src.depth()); 659 | assert_eq!(1, link.depth()); 660 | 661 | assert!(src.file_type().is_dir()); 662 | assert!(!link.file_type().is_symlink()); 663 | assert!(!link.file_type().is_file()); 664 | assert!(link.file_type().is_dir()); 665 | 666 | assert!(src.metadata().unwrap().is_dir()); 667 | assert!(!link.metadata().unwrap().file_type().is_symlink()); 668 | assert!(!link.metadata().unwrap().is_file()); 669 | assert!(link.metadata().unwrap().is_dir()); 670 | 671 | let (src_zzz, link_zzz) = (&ents[2], &ents[4]); 672 | assert_eq!(dir.join("a").join("zzz"), src_zzz.path()); 673 | assert_eq!(dir.join("a-link").join("zzz"), link_zzz.path()); 674 | assert!(!src_zzz.path_is_symlink()); 675 | assert!(!link_zzz.path_is_symlink()); 676 | } 677 | 678 | #[test] 679 | fn sym_noloop() { 680 | let dir = Dir::tmp(); 681 | dir.mkdirp("a/b/c"); 682 | dir.symlink_dir("a", "a/b/c/a-link"); 683 | 684 | let wd = WalkDir::new(dir.path()); 685 | let r = dir.run_recursive(wd); 686 | // There's no loop if we aren't following symlinks. 687 | r.assert_no_errors(); 688 | 689 | assert_eq!(5, r.ents().len()); 690 | } 691 | 692 | #[test] 693 | fn sym_loop_detect() { 694 | let dir = Dir::tmp(); 695 | dir.mkdirp("a/b/c"); 696 | dir.symlink_dir("a", "a/b/c/a-link"); 697 | 698 | let wd = WalkDir::new(dir.path()).follow_links(true); 699 | let r = dir.run_recursive(wd); 700 | 701 | let (ents, errs) = (r.sorted_ents(), r.errs()); 702 | assert_eq!(4, ents.len()); 703 | assert_eq!(1, errs.len()); 704 | 705 | let err = &errs[0]; 706 | 707 | let expected = dir.join("a/b/c/a-link"); 708 | assert_eq!(Some(&*expected), err.path()); 709 | 710 | let expected = dir.join("a"); 711 | assert_eq!(Some(&*expected), err.loop_ancestor()); 712 | 713 | assert_eq!(4, err.depth()); 714 | assert!(err.io_error().is_none()); 715 | } 716 | 717 | #[test] 718 | fn sym_self_loop_no_error() { 719 | let dir = Dir::tmp(); 720 | dir.symlink_file("a", "a"); 721 | 722 | let wd = WalkDir::new(dir.path()); 723 | let r = dir.run_recursive(wd); 724 | // No errors occur because even though the symlink points to nowhere, it 725 | // is never followed, and thus no error occurs. 726 | r.assert_no_errors(); 727 | assert_eq!(2, r.ents().len()); 728 | 729 | let ent = &r.ents()[1]; 730 | assert_eq!(dir.join("a"), ent.path()); 731 | assert!(ent.path_is_symlink()); 732 | 733 | assert!(ent.file_type().is_symlink()); 734 | assert!(!ent.file_type().is_file()); 735 | assert!(!ent.file_type().is_dir()); 736 | 737 | assert!(ent.metadata().unwrap().file_type().is_symlink()); 738 | assert!(!ent.metadata().unwrap().file_type().is_file()); 739 | assert!(!ent.metadata().unwrap().file_type().is_dir()); 740 | } 741 | 742 | #[test] 743 | fn sym_file_self_loop_io_error() { 744 | let dir = Dir::tmp(); 745 | dir.symlink_file("a", "a"); 746 | 747 | let wd = WalkDir::new(dir.path()).follow_links(true); 748 | let r = dir.run_recursive(wd); 749 | 750 | let (ents, errs) = (r.sorted_ents(), r.errs()); 751 | assert_eq!(1, ents.len()); 752 | assert_eq!(1, errs.len()); 753 | 754 | let err = &errs[0]; 755 | 756 | let expected = dir.join("a"); 757 | assert_eq!(Some(&*expected), err.path()); 758 | assert_eq!(1, err.depth()); 759 | assert!(err.loop_ancestor().is_none()); 760 | assert!(err.io_error().is_some()); 761 | } 762 | 763 | #[test] 764 | fn sym_dir_self_loop_io_error() { 765 | let dir = Dir::tmp(); 766 | dir.symlink_dir("a", "a"); 767 | 768 | let wd = WalkDir::new(dir.path()).follow_links(true); 769 | let r = dir.run_recursive(wd); 770 | 771 | let (ents, errs) = (r.sorted_ents(), r.errs()); 772 | assert_eq!(1, ents.len()); 773 | assert_eq!(1, errs.len()); 774 | 775 | let err = &errs[0]; 776 | 777 | let expected = dir.join("a"); 778 | assert_eq!(Some(&*expected), err.path()); 779 | assert_eq!(1, err.depth()); 780 | assert!(err.loop_ancestor().is_none()); 781 | assert!(err.io_error().is_some()); 782 | } 783 | 784 | #[test] 785 | fn min_depth_1() { 786 | let dir = Dir::tmp(); 787 | dir.mkdirp("a/b"); 788 | 789 | let wd = WalkDir::new(dir.path()).min_depth(1); 790 | let r = dir.run_recursive(wd); 791 | r.assert_no_errors(); 792 | 793 | let expected = vec![dir.join("a"), dir.join("a").join("b")]; 794 | assert_eq!(expected, r.sorted_paths()); 795 | } 796 | 797 | #[test] 798 | fn min_depth_2() { 799 | let dir = Dir::tmp(); 800 | dir.mkdirp("a/b"); 801 | 802 | let wd = WalkDir::new(dir.path()).min_depth(2); 803 | let r = dir.run_recursive(wd); 804 | r.assert_no_errors(); 805 | 806 | let expected = vec![dir.join("a").join("b")]; 807 | assert_eq!(expected, r.sorted_paths()); 808 | } 809 | 810 | #[test] 811 | fn max_depth_0() { 812 | let dir = Dir::tmp(); 813 | dir.mkdirp("a/b"); 814 | 815 | let wd = WalkDir::new(dir.path()).max_depth(0); 816 | let r = dir.run_recursive(wd); 817 | r.assert_no_errors(); 818 | 819 | let expected = vec![dir.path().to_path_buf()]; 820 | assert_eq!(expected, r.sorted_paths()); 821 | } 822 | 823 | #[test] 824 | fn max_depth_1() { 825 | let dir = Dir::tmp(); 826 | dir.mkdirp("a/b"); 827 | 828 | let wd = WalkDir::new(dir.path()).max_depth(1); 829 | let r = dir.run_recursive(wd); 830 | r.assert_no_errors(); 831 | 832 | let expected = vec![dir.path().to_path_buf(), dir.join("a")]; 833 | assert_eq!(expected, r.sorted_paths()); 834 | } 835 | 836 | #[test] 837 | fn max_depth_2() { 838 | let dir = Dir::tmp(); 839 | dir.mkdirp("a/b"); 840 | 841 | let wd = WalkDir::new(dir.path()).max_depth(2); 842 | let r = dir.run_recursive(wd); 843 | r.assert_no_errors(); 844 | 845 | let expected = 846 | vec![dir.path().to_path_buf(), dir.join("a"), dir.join("a").join("b")]; 847 | assert_eq!(expected, r.sorted_paths()); 848 | } 849 | 850 | // FIXME: This test seems wrong. It should return nothing! 851 | #[test] 852 | fn min_max_depth_diff_nada() { 853 | let dir = Dir::tmp(); 854 | dir.mkdirp("a/b/c"); 855 | 856 | let wd = WalkDir::new(dir.path()).min_depth(3).max_depth(2); 857 | let r = dir.run_recursive(wd); 858 | r.assert_no_errors(); 859 | 860 | let expected = vec![dir.join("a").join("b").join("c")]; 861 | assert_eq!(expected, r.sorted_paths()); 862 | } 863 | 864 | #[test] 865 | fn min_max_depth_diff_0() { 866 | let dir = Dir::tmp(); 867 | dir.mkdirp("a/b/c"); 868 | 869 | let wd = WalkDir::new(dir.path()).min_depth(2).max_depth(2); 870 | let r = dir.run_recursive(wd); 871 | r.assert_no_errors(); 872 | 873 | let expected = vec![dir.join("a").join("b")]; 874 | assert_eq!(expected, r.sorted_paths()); 875 | } 876 | 877 | #[test] 878 | fn min_max_depth_diff_1() { 879 | let dir = Dir::tmp(); 880 | dir.mkdirp("a/b/c"); 881 | 882 | let wd = WalkDir::new(dir.path()).min_depth(1).max_depth(2); 883 | let r = dir.run_recursive(wd); 884 | r.assert_no_errors(); 885 | 886 | let expected = vec![dir.join("a"), dir.join("a").join("b")]; 887 | assert_eq!(expected, r.sorted_paths()); 888 | } 889 | 890 | #[test] 891 | fn contents_first() { 892 | let dir = Dir::tmp(); 893 | dir.touch("a"); 894 | 895 | let wd = WalkDir::new(dir.path()).contents_first(true); 896 | let r = dir.run_recursive(wd); 897 | r.assert_no_errors(); 898 | 899 | let expected = vec![dir.join("a"), dir.path().to_path_buf()]; 900 | assert_eq!(expected, r.paths()); 901 | } 902 | 903 | #[test] 904 | fn skip_current_dir() { 905 | let dir = Dir::tmp(); 906 | dir.mkdirp("foo/bar/baz"); 907 | dir.mkdirp("quux"); 908 | 909 | let mut paths = vec![]; 910 | let mut it = WalkDir::new(dir.path()).into_iter(); 911 | while let Some(result) = it.next() { 912 | let ent = result.unwrap(); 913 | paths.push(ent.path().to_path_buf()); 914 | if ent.file_name() == "bar" { 915 | it.skip_current_dir(); 916 | } 917 | } 918 | paths.sort(); 919 | 920 | let expected = vec![ 921 | dir.path().to_path_buf(), 922 | dir.join("foo"), 923 | dir.join("foo").join("bar"), 924 | dir.join("quux"), 925 | ]; 926 | assert_eq!(expected, paths); 927 | } 928 | 929 | #[test] 930 | fn filter_entry() { 931 | let dir = Dir::tmp(); 932 | dir.mkdirp("foo/bar/baz/abc"); 933 | dir.mkdirp("quux"); 934 | 935 | let wd = WalkDir::new(dir.path()) 936 | .into_iter() 937 | .filter_entry(|ent| ent.file_name() != "baz"); 938 | let r = dir.run_recursive(wd); 939 | r.assert_no_errors(); 940 | 941 | let expected = vec![ 942 | dir.path().to_path_buf(), 943 | dir.join("foo"), 944 | dir.join("foo").join("bar"), 945 | dir.join("quux"), 946 | ]; 947 | assert_eq!(expected, r.sorted_paths()); 948 | } 949 | 950 | #[test] 951 | fn sort_by() { 952 | let dir = Dir::tmp(); 953 | dir.mkdirp("foo/bar/baz/abc"); 954 | dir.mkdirp("quux"); 955 | 956 | let wd = WalkDir::new(dir.path()) 957 | .sort_by(|a, b| a.file_name().cmp(b.file_name()).reverse()); 958 | let r = dir.run_recursive(wd); 959 | r.assert_no_errors(); 960 | 961 | let expected = vec![ 962 | dir.path().to_path_buf(), 963 | dir.join("quux"), 964 | dir.join("foo"), 965 | dir.join("foo").join("bar"), 966 | dir.join("foo").join("bar").join("baz"), 967 | dir.join("foo").join("bar").join("baz").join("abc"), 968 | ]; 969 | assert_eq!(expected, r.paths()); 970 | } 971 | 972 | #[test] 973 | fn sort_by_key() { 974 | let dir = Dir::tmp(); 975 | dir.mkdirp("foo/bar/baz/abc"); 976 | dir.mkdirp("quux"); 977 | 978 | let wd = 979 | WalkDir::new(dir.path()).sort_by_key(|a| a.file_name().to_owned()); 980 | let r = dir.run_recursive(wd); 981 | r.assert_no_errors(); 982 | 983 | let expected = vec![ 984 | dir.path().to_path_buf(), 985 | dir.join("foo"), 986 | dir.join("foo").join("bar"), 987 | dir.join("foo").join("bar").join("baz"), 988 | dir.join("foo").join("bar").join("baz").join("abc"), 989 | dir.join("quux"), 990 | ]; 991 | assert_eq!(expected, r.paths()); 992 | } 993 | 994 | #[test] 995 | fn sort_by_file_name() { 996 | let dir = Dir::tmp(); 997 | dir.mkdirp("foo/bar/baz/abc"); 998 | dir.mkdirp("quux"); 999 | 1000 | let wd = WalkDir::new(dir.path()).sort_by_file_name(); 1001 | let r = dir.run_recursive(wd); 1002 | r.assert_no_errors(); 1003 | 1004 | let expected = vec![ 1005 | dir.path().to_path_buf(), 1006 | dir.join("foo"), 1007 | dir.join("foo").join("bar"), 1008 | dir.join("foo").join("bar").join("baz"), 1009 | dir.join("foo").join("bar").join("baz").join("abc"), 1010 | dir.join("quux"), 1011 | ]; 1012 | assert_eq!(expected, r.paths()); 1013 | } 1014 | 1015 | #[test] 1016 | fn sort_max_open() { 1017 | let dir = Dir::tmp(); 1018 | dir.mkdirp("foo/bar/baz/abc"); 1019 | dir.mkdirp("quux"); 1020 | 1021 | let wd = WalkDir::new(dir.path()) 1022 | .max_open(1) 1023 | .sort_by(|a, b| a.file_name().cmp(b.file_name()).reverse()); 1024 | let r = dir.run_recursive(wd); 1025 | r.assert_no_errors(); 1026 | 1027 | let expected = vec![ 1028 | dir.path().to_path_buf(), 1029 | dir.join("quux"), 1030 | dir.join("foo"), 1031 | dir.join("foo").join("bar"), 1032 | dir.join("foo").join("bar").join("baz"), 1033 | dir.join("foo").join("bar").join("baz").join("abc"), 1034 | ]; 1035 | assert_eq!(expected, r.paths()); 1036 | } 1037 | 1038 | #[cfg(target_os = "linux")] 1039 | #[test] 1040 | fn same_file_system() { 1041 | use std::path::Path; 1042 | 1043 | // This test is a little weird since it's not clear whether it's a good 1044 | // idea to setup a distinct mounted volume in these tests. Instead, we 1045 | // probe for an existing one. 1046 | if !Path::new("/sys").is_dir() { 1047 | return; 1048 | } 1049 | 1050 | let dir = Dir::tmp(); 1051 | dir.touch("a"); 1052 | dir.symlink_dir("/sys", "sys-link"); 1053 | 1054 | // First, do a sanity check that things work without following symlinks. 1055 | let wd = WalkDir::new(dir.path()); 1056 | let r = dir.run_recursive(wd); 1057 | r.assert_no_errors(); 1058 | 1059 | let expected = 1060 | vec![dir.path().to_path_buf(), dir.join("a"), dir.join("sys-link")]; 1061 | assert_eq!(expected, r.sorted_paths()); 1062 | 1063 | // ... now follow symlinks and ensure we don't descend into /sys. 1064 | let wd = 1065 | WalkDir::new(dir.path()).same_file_system(true).follow_links(true); 1066 | let r = dir.run_recursive(wd); 1067 | r.assert_no_errors(); 1068 | 1069 | let expected = 1070 | vec![dir.path().to_path_buf(), dir.join("a"), dir.join("sys-link")]; 1071 | assert_eq!(expected, r.sorted_paths()); 1072 | } 1073 | 1074 | // Tests that skip_current_dir doesn't destroy internal invariants. 1075 | // 1076 | // See: https://github.com/BurntSushi/walkdir/issues/118 1077 | #[test] 1078 | fn regression_skip_current_dir() { 1079 | let dir = Dir::tmp(); 1080 | dir.mkdirp("foo/a/b"); 1081 | dir.mkdirp("foo/1/2"); 1082 | 1083 | let mut wd = WalkDir::new(dir.path()).max_open(1).into_iter(); 1084 | wd.next(); 1085 | wd.next(); 1086 | wd.next(); 1087 | wd.next(); 1088 | 1089 | wd.skip_current_dir(); 1090 | wd.skip_current_dir(); 1091 | wd.next(); 1092 | } 1093 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Crate `walkdir` provides an efficient and cross platform implementation 3 | of recursive directory traversal. Several options are exposed to control 4 | iteration, such as whether to follow symbolic links (default off), limit the 5 | maximum number of simultaneous open file descriptors and the ability to 6 | efficiently skip descending into directories. 7 | 8 | To use this crate, add `walkdir` as a dependency to your project's 9 | `Cargo.toml`: 10 | 11 | ```toml 12 | [dependencies] 13 | walkdir = "2" 14 | ``` 15 | 16 | # From the top 17 | 18 | The [`WalkDir`] type builds iterators. The [`DirEntry`] type describes values 19 | yielded by the iterator. Finally, the [`Error`] type is a small wrapper around 20 | [`std::io::Error`] with additional information, such as if a loop was detected 21 | while following symbolic links (not enabled by default). 22 | 23 | [`WalkDir`]: struct.WalkDir.html 24 | [`DirEntry`]: struct.DirEntry.html 25 | [`Error`]: struct.Error.html 26 | [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 27 | 28 | # Example 29 | 30 | The following code recursively iterates over the directory given and prints 31 | the path for each entry: 32 | 33 | ```no_run 34 | use walkdir::WalkDir; 35 | # use walkdir::Error; 36 | 37 | # fn try_main() -> Result<(), Error> { 38 | for entry in WalkDir::new("foo") { 39 | println!("{}", entry?.path().display()); 40 | } 41 | # Ok(()) 42 | # } 43 | ``` 44 | 45 | Or, if you'd like to iterate over all entries and ignore any errors that 46 | may arise, use [`filter_map`]. (e.g., This code below will silently skip 47 | directories that the owner of the running process does not have permission to 48 | access.) 49 | 50 | ```no_run 51 | use walkdir::WalkDir; 52 | 53 | for entry in WalkDir::new("foo").into_iter().filter_map(|e| e.ok()) { 54 | println!("{}", entry.path().display()); 55 | } 56 | ``` 57 | 58 | [`filter_map`]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.filter_map 59 | 60 | # Example: follow symbolic links 61 | 62 | The same code as above, except [`follow_links`] is enabled: 63 | 64 | ```no_run 65 | use walkdir::WalkDir; 66 | # use walkdir::Error; 67 | 68 | # fn try_main() -> Result<(), Error> { 69 | for entry in WalkDir::new("foo").follow_links(true) { 70 | println!("{}", entry?.path().display()); 71 | } 72 | # Ok(()) 73 | # } 74 | ``` 75 | 76 | [`follow_links`]: struct.WalkDir.html#method.follow_links 77 | 78 | # Example: skip hidden files and directories on unix 79 | 80 | This uses the [`filter_entry`] iterator adapter to avoid yielding hidden files 81 | and directories efficiently (i.e. without recursing into hidden directories): 82 | 83 | ```no_run 84 | use walkdir::{DirEntry, WalkDir}; 85 | # use walkdir::Error; 86 | 87 | fn is_hidden(entry: &DirEntry) -> bool { 88 | entry.file_name() 89 | .to_str() 90 | .map(|s| s.starts_with(".")) 91 | .unwrap_or(false) 92 | } 93 | 94 | # fn try_main() -> Result<(), Error> { 95 | let walker = WalkDir::new("foo").into_iter(); 96 | for entry in walker.filter_entry(|e| !is_hidden(e)) { 97 | println!("{}", entry?.path().display()); 98 | } 99 | # Ok(()) 100 | # } 101 | ``` 102 | 103 | [`filter_entry`]: struct.IntoIter.html#method.filter_entry 104 | */ 105 | 106 | #![deny(missing_docs)] 107 | #![allow(unknown_lints)] 108 | 109 | #[cfg(doctest)] 110 | doc_comment::doctest!("../README.md"); 111 | 112 | use std::cmp::{min, Ordering}; 113 | use std::fmt; 114 | use std::fs::{self, ReadDir}; 115 | use std::io; 116 | use std::iter; 117 | use std::path::{Path, PathBuf}; 118 | use std::result; 119 | use std::vec; 120 | 121 | use same_file::Handle; 122 | 123 | pub use crate::dent::DirEntry; 124 | #[cfg(unix)] 125 | pub use crate::dent::DirEntryExt; 126 | pub use crate::error::Error; 127 | 128 | mod dent; 129 | mod error; 130 | #[cfg(test)] 131 | mod tests; 132 | mod util; 133 | 134 | /// Like try, but for iterators that return [`Option>`]. 135 | /// 136 | /// [`Option>`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html 137 | macro_rules! itry { 138 | ($e:expr) => { 139 | match $e { 140 | Ok(v) => v, 141 | Err(err) => return Some(Err(From::from(err))), 142 | } 143 | }; 144 | } 145 | 146 | /// A result type for walkdir operations. 147 | /// 148 | /// Note that this result type embeds the error type in this crate. This 149 | /// is only useful if you care about the additional information provided by 150 | /// the error (such as the path associated with the error or whether a loop 151 | /// was dectected). If you want things to Just Work, then you can use 152 | /// [`io::Result`] instead since the error type in this package will 153 | /// automatically convert to an [`io::Result`] when using the [`try!`] macro. 154 | /// 155 | /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html 156 | /// [`try!`]: https://doc.rust-lang.org/stable/std/macro.try.html 157 | pub type Result = ::std::result::Result; 158 | 159 | /// A builder to create an iterator for recursively walking a directory. 160 | /// 161 | /// Results are returned in depth first fashion, with directories yielded 162 | /// before their contents. If [`contents_first`] is true, contents are yielded 163 | /// before their directories. The order is unspecified but if [`sort_by`] is 164 | /// given, directory entries are sorted according to this function. Directory 165 | /// entries `.` and `..` are always omitted. 166 | /// 167 | /// If an error occurs at any point during iteration, then it is returned in 168 | /// place of its corresponding directory entry and iteration continues as 169 | /// normal. If an error occurs while opening a directory for reading, then it 170 | /// is not descended into (but the error is still yielded by the iterator). 171 | /// Iteration may be stopped at any time. When the iterator is destroyed, all 172 | /// resources associated with it are freed. 173 | /// 174 | /// [`contents_first`]: struct.WalkDir.html#method.contents_first 175 | /// [`sort_by`]: struct.WalkDir.html#method.sort_by 176 | /// 177 | /// # Usage 178 | /// 179 | /// This type implements [`IntoIterator`] so that it may be used as the subject 180 | /// of a `for` loop. You may need to call [`into_iter`] explicitly if you want 181 | /// to use iterator adapters such as [`filter_entry`]. 182 | /// 183 | /// Idiomatic use of this type should use method chaining to set desired 184 | /// options. For example, this only shows entries with a depth of `1`, `2` or 185 | /// `3` (relative to `foo`): 186 | /// 187 | /// ```no_run 188 | /// use walkdir::WalkDir; 189 | /// # use walkdir::Error; 190 | /// 191 | /// # fn try_main() -> Result<(), Error> { 192 | /// for entry in WalkDir::new("foo").min_depth(1).max_depth(3) { 193 | /// println!("{}", entry?.path().display()); 194 | /// } 195 | /// # Ok(()) 196 | /// # } 197 | /// ``` 198 | /// 199 | /// [`IntoIterator`]: https://doc.rust-lang.org/stable/std/iter/trait.IntoIterator.html 200 | /// [`into_iter`]: https://doc.rust-lang.org/nightly/core/iter/trait.IntoIterator.html#tymethod.into_iter 201 | /// [`filter_entry`]: struct.IntoIter.html#method.filter_entry 202 | /// 203 | /// Note that the iterator by default includes the top-most directory. Since 204 | /// this is the only directory yielded with depth `0`, it is easy to ignore it 205 | /// with the [`min_depth`] setting: 206 | /// 207 | /// ```no_run 208 | /// use walkdir::WalkDir; 209 | /// # use walkdir::Error; 210 | /// 211 | /// # fn try_main() -> Result<(), Error> { 212 | /// for entry in WalkDir::new("foo").min_depth(1) { 213 | /// println!("{}", entry?.path().display()); 214 | /// } 215 | /// # Ok(()) 216 | /// # } 217 | /// ``` 218 | /// 219 | /// [`min_depth`]: struct.WalkDir.html#method.min_depth 220 | /// 221 | /// This will only return descendents of the `foo` directory and not `foo` 222 | /// itself. 223 | /// 224 | /// # Loops 225 | /// 226 | /// This iterator (like most/all recursive directory iterators) assumes that 227 | /// no loops can be made with *hard* links on your file system. In particular, 228 | /// this would require creating a hard link to a directory such that it creates 229 | /// a loop. On most platforms, this operation is illegal. 230 | /// 231 | /// Note that when following symbolic/soft links, loops are detected and an 232 | /// error is reported. 233 | #[derive(Debug)] 234 | pub struct WalkDir { 235 | opts: WalkDirOptions, 236 | root: PathBuf, 237 | } 238 | 239 | struct WalkDirOptions { 240 | follow_links: bool, 241 | follow_root_links: bool, 242 | max_open: usize, 243 | min_depth: usize, 244 | max_depth: usize, 245 | sorter: Option< 246 | Box< 247 | dyn FnMut(&DirEntry, &DirEntry) -> Ordering 248 | + Send 249 | + Sync 250 | + 'static, 251 | >, 252 | >, 253 | contents_first: bool, 254 | same_file_system: bool, 255 | } 256 | 257 | impl fmt::Debug for WalkDirOptions { 258 | fn fmt( 259 | &self, 260 | f: &mut fmt::Formatter<'_>, 261 | ) -> result::Result<(), fmt::Error> { 262 | let sorter_str = if self.sorter.is_some() { 263 | // FnMut isn't `Debug` 264 | "Some(...)" 265 | } else { 266 | "None" 267 | }; 268 | f.debug_struct("WalkDirOptions") 269 | .field("follow_links", &self.follow_links) 270 | .field("follow_root_link", &self.follow_root_links) 271 | .field("max_open", &self.max_open) 272 | .field("min_depth", &self.min_depth) 273 | .field("max_depth", &self.max_depth) 274 | .field("sorter", &sorter_str) 275 | .field("contents_first", &self.contents_first) 276 | .field("same_file_system", &self.same_file_system) 277 | .finish() 278 | } 279 | } 280 | 281 | impl WalkDir { 282 | /// Create a builder for a recursive directory iterator starting at the 283 | /// file path `root`. If `root` is a directory, then it is the first item 284 | /// yielded by the iterator. If `root` is a file, then it is the first 285 | /// and only item yielded by the iterator. If `root` is a symlink, then it 286 | /// is always followed for the purposes of directory traversal. (A root 287 | /// `DirEntry` still obeys its documentation with respect to symlinks and 288 | /// the `follow_links` setting.) 289 | pub fn new>(root: P) -> Self { 290 | WalkDir { 291 | opts: WalkDirOptions { 292 | follow_links: false, 293 | follow_root_links: true, 294 | max_open: 10, 295 | min_depth: 0, 296 | max_depth: ::std::usize::MAX, 297 | sorter: None, 298 | contents_first: false, 299 | same_file_system: false, 300 | }, 301 | root: root.as_ref().to_path_buf(), 302 | } 303 | } 304 | 305 | /// Set the minimum depth of entries yielded by the iterator. 306 | /// 307 | /// The smallest depth is `0` and always corresponds to the path given 308 | /// to the `new` function on this type. Its direct descendents have depth 309 | /// `1`, and their descendents have depth `2`, and so on. 310 | pub fn min_depth(mut self, depth: usize) -> Self { 311 | self.opts.min_depth = depth; 312 | if self.opts.min_depth > self.opts.max_depth { 313 | self.opts.min_depth = self.opts.max_depth; 314 | } 315 | self 316 | } 317 | 318 | /// Set the maximum depth of entries yield by the iterator. 319 | /// 320 | /// The smallest depth is `0` and always corresponds to the path given 321 | /// to the `new` function on this type. Its direct descendents have depth 322 | /// `1`, and their descendents have depth `2`, and so on. 323 | /// 324 | /// Note that this will not simply filter the entries of the iterator, but 325 | /// it will actually avoid descending into directories when the depth is 326 | /// exceeded. 327 | pub fn max_depth(mut self, depth: usize) -> Self { 328 | self.opts.max_depth = depth; 329 | if self.opts.max_depth < self.opts.min_depth { 330 | self.opts.max_depth = self.opts.min_depth; 331 | } 332 | self 333 | } 334 | 335 | /// Follow symbolic links. By default, this is disabled. 336 | /// 337 | /// When `yes` is `true`, symbolic links are followed as if they were 338 | /// normal directories and files. If a symbolic link is broken or is 339 | /// involved in a loop, an error is yielded. 340 | /// 341 | /// When enabled, the yielded [`DirEntry`] values represent the target of 342 | /// the link while the path corresponds to the link. See the [`DirEntry`] 343 | /// type for more details. 344 | /// 345 | /// [`DirEntry`]: struct.DirEntry.html 346 | pub fn follow_links(mut self, yes: bool) -> Self { 347 | self.opts.follow_links = yes; 348 | self 349 | } 350 | 351 | /// Follow symbolic links if these are the root of the traversal. 352 | /// By default, this is enabled. 353 | /// 354 | /// When `yes` is `true`, symbolic links on root paths are followed 355 | /// which is effective if the symbolic link points to a directory. 356 | /// If a symbolic link is broken or is involved in a loop, an error is yielded 357 | /// as the first entry of the traversal. 358 | /// 359 | /// When enabled, the yielded [`DirEntry`] values represent the target of 360 | /// the link while the path corresponds to the link. See the [`DirEntry`] 361 | /// type for more details, and all future entries will be contained within 362 | /// the resolved directory behind the symbolic link of the root path. 363 | /// 364 | /// [`DirEntry`]: struct.DirEntry.html 365 | pub fn follow_root_links(mut self, yes: bool) -> Self { 366 | self.opts.follow_root_links = yes; 367 | self 368 | } 369 | 370 | /// Set the maximum number of simultaneously open file descriptors used 371 | /// by the iterator. 372 | /// 373 | /// `n` must be greater than or equal to `1`. If `n` is `0`, then it is set 374 | /// to `1` automatically. If this is not set, then it defaults to some 375 | /// reasonably low number. 376 | /// 377 | /// This setting has no impact on the results yielded by the iterator 378 | /// (even when `n` is `1`). Instead, this setting represents a trade off 379 | /// between scarce resources (file descriptors) and memory. Namely, when 380 | /// the maximum number of file descriptors is reached and a new directory 381 | /// needs to be opened to continue iteration, then a previous directory 382 | /// handle is closed and has its unyielded entries stored in memory. In 383 | /// practice, this is a satisfying trade off because it scales with respect 384 | /// to the *depth* of your file tree. Therefore, low values (even `1`) are 385 | /// acceptable. 386 | /// 387 | /// Note that this value does not impact the number of system calls made by 388 | /// an exhausted iterator. 389 | /// 390 | /// # Platform behavior 391 | /// 392 | /// On Windows, if `follow_links` is enabled, then this limit is not 393 | /// respected. In particular, the maximum number of file descriptors opened 394 | /// is proportional to the depth of the directory tree traversed. 395 | pub fn max_open(mut self, mut n: usize) -> Self { 396 | if n == 0 { 397 | n = 1; 398 | } 399 | self.opts.max_open = n; 400 | self 401 | } 402 | 403 | /// Set a function for sorting directory entries with a comparator 404 | /// function. 405 | /// 406 | /// If a compare function is set, the resulting iterator will return all 407 | /// paths in sorted order. The compare function will be called to compare 408 | /// entries from the same directory. 409 | /// 410 | /// ```rust,no_run 411 | /// use std::cmp; 412 | /// use std::ffi::OsString; 413 | /// use walkdir::WalkDir; 414 | /// 415 | /// WalkDir::new("foo").sort_by(|a,b| a.file_name().cmp(b.file_name())); 416 | /// ``` 417 | pub fn sort_by(mut self, cmp: F) -> Self 418 | where 419 | F: FnMut(&DirEntry, &DirEntry) -> Ordering + Send + Sync + 'static, 420 | { 421 | self.opts.sorter = Some(Box::new(cmp)); 422 | self 423 | } 424 | 425 | /// Set a function for sorting directory entries with a key extraction 426 | /// function. 427 | /// 428 | /// If a compare function is set, the resulting iterator will return all 429 | /// paths in sorted order. The compare function will be called to compare 430 | /// entries from the same directory. 431 | /// 432 | /// ```rust,no_run 433 | /// use std::cmp; 434 | /// use std::ffi::OsString; 435 | /// use walkdir::WalkDir; 436 | /// 437 | /// WalkDir::new("foo").sort_by_key(|a| a.file_name().to_owned()); 438 | /// ``` 439 | pub fn sort_by_key(self, mut cmp: F) -> Self 440 | where 441 | F: FnMut(&DirEntry) -> K + Send + Sync + 'static, 442 | K: Ord, 443 | { 444 | self.sort_by(move |a, b| cmp(a).cmp(&cmp(b))) 445 | } 446 | 447 | /// Sort directory entries by file name, to ensure a deterministic order. 448 | /// 449 | /// This is a convenience function for calling `Self::sort_by()`. 450 | /// 451 | /// ```rust,no_run 452 | /// use walkdir::WalkDir; 453 | /// 454 | /// WalkDir::new("foo").sort_by_file_name(); 455 | /// ``` 456 | pub fn sort_by_file_name(self) -> Self { 457 | self.sort_by(|a, b| a.file_name().cmp(b.file_name())) 458 | } 459 | 460 | /// Yield a directory's contents before the directory itself. By default, 461 | /// this is disabled. 462 | /// 463 | /// When `yes` is `false` (as is the default), the directory is yielded 464 | /// before its contents are read. This is useful when, e.g. you want to 465 | /// skip processing of some directories. 466 | /// 467 | /// When `yes` is `true`, the iterator yields the contents of a directory 468 | /// before yielding the directory itself. This is useful when, e.g. you 469 | /// want to recursively delete a directory. 470 | /// 471 | /// # Example 472 | /// 473 | /// Assume the following directory tree: 474 | /// 475 | /// ```text 476 | /// foo/ 477 | /// abc/ 478 | /// qrs 479 | /// tuv 480 | /// def/ 481 | /// ``` 482 | /// 483 | /// With contents_first disabled (the default), the following code visits 484 | /// the directory tree in depth-first order: 485 | /// 486 | /// ```no_run 487 | /// use walkdir::WalkDir; 488 | /// 489 | /// for entry in WalkDir::new("foo") { 490 | /// let entry = entry.unwrap(); 491 | /// println!("{}", entry.path().display()); 492 | /// } 493 | /// 494 | /// // foo 495 | /// // foo/abc 496 | /// // foo/abc/qrs 497 | /// // foo/abc/tuv 498 | /// // foo/def 499 | /// ``` 500 | /// 501 | /// With contents_first enabled: 502 | /// 503 | /// ```no_run 504 | /// use walkdir::WalkDir; 505 | /// 506 | /// for entry in WalkDir::new("foo").contents_first(true) { 507 | /// let entry = entry.unwrap(); 508 | /// println!("{}", entry.path().display()); 509 | /// } 510 | /// 511 | /// // foo/abc/qrs 512 | /// // foo/abc/tuv 513 | /// // foo/abc 514 | /// // foo/def 515 | /// // foo 516 | /// ``` 517 | pub fn contents_first(mut self, yes: bool) -> Self { 518 | self.opts.contents_first = yes; 519 | self 520 | } 521 | 522 | /// Do not cross file system boundaries. 523 | /// 524 | /// When this option is enabled, directory traversal will not descend into 525 | /// directories that are on a different file system from the root path. 526 | /// 527 | /// Currently, this option is only supported on Unix and Windows. If this 528 | /// option is used on an unsupported platform, then directory traversal 529 | /// will immediately return an error and will not yield any entries. 530 | pub fn same_file_system(mut self, yes: bool) -> Self { 531 | self.opts.same_file_system = yes; 532 | self 533 | } 534 | } 535 | 536 | impl IntoIterator for WalkDir { 537 | type Item = Result; 538 | type IntoIter = IntoIter; 539 | 540 | fn into_iter(self) -> IntoIter { 541 | IntoIter { 542 | opts: self.opts, 543 | start: Some(self.root), 544 | stack_list: vec![], 545 | stack_path: vec![], 546 | oldest_opened: 0, 547 | depth: 0, 548 | deferred_dirs: vec![], 549 | root_device: None, 550 | } 551 | } 552 | } 553 | 554 | /// An iterator for recursively descending into a directory. 555 | /// 556 | /// A value with this type must be constructed with the [`WalkDir`] type, which 557 | /// uses a builder pattern to set options such as min/max depth, max open file 558 | /// descriptors and whether the iterator should follow symbolic links. After 559 | /// constructing a `WalkDir`, call [`.into_iter()`] at the end of the chain. 560 | /// 561 | /// The order of elements yielded by this iterator is unspecified. 562 | /// 563 | /// [`WalkDir`]: struct.WalkDir.html 564 | /// [`.into_iter()`]: struct.WalkDir.html#into_iter.v 565 | #[derive(Debug)] 566 | pub struct IntoIter { 567 | /// Options specified in the builder. Depths, max fds, etc. 568 | opts: WalkDirOptions, 569 | /// The start path. 570 | /// 571 | /// This is only `Some(...)` at the beginning. After the first iteration, 572 | /// this is always `None`. 573 | start: Option, 574 | /// A stack of open (up to max fd) or closed handles to directories. 575 | /// An open handle is a plain [`fs::ReadDir`] while a closed handle is 576 | /// a `Vec` corresponding to the as-of-yet consumed entries. 577 | /// 578 | /// [`fs::ReadDir`]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html 579 | stack_list: Vec, 580 | /// A stack of file paths. 581 | /// 582 | /// This is *only* used when [`follow_links`] is enabled. In all other 583 | /// cases this stack is empty. 584 | /// 585 | /// [`follow_links`]: struct.WalkDir.html#method.follow_links 586 | stack_path: Vec, 587 | /// An index into `stack_list` that points to the oldest open directory 588 | /// handle. If the maximum fd limit is reached and a new directory needs to 589 | /// be read, the handle at this index is closed before the new directory is 590 | /// opened. 591 | oldest_opened: usize, 592 | /// The current depth of iteration (the length of the stack at the 593 | /// beginning of each iteration). 594 | depth: usize, 595 | /// A list of DirEntries corresponding to directories, that are 596 | /// yielded after their contents has been fully yielded. This is only 597 | /// used when `contents_first` is enabled. 598 | deferred_dirs: Vec, 599 | /// The device of the root file path when the first call to `next` was 600 | /// made. 601 | /// 602 | /// If the `same_file_system` option isn't enabled, then this is always 603 | /// `None`. Conversely, if it is enabled, this is always `Some(...)` after 604 | /// handling the root path. 605 | root_device: Option, 606 | } 607 | 608 | /// An ancestor is an item in the directory tree traversed by walkdir, and is 609 | /// used to check for loops in the tree when traversing symlinks. 610 | #[derive(Debug)] 611 | struct Ancestor { 612 | /// The path of this ancestor. 613 | path: PathBuf, 614 | /// An open file to this ancesor. This is only used on Windows where 615 | /// opening a file handle appears to be quite expensive, so we choose to 616 | /// cache it. This comes at the cost of not respecting the file descriptor 617 | /// limit set by the user. 618 | #[cfg(windows)] 619 | handle: Handle, 620 | } 621 | 622 | impl Ancestor { 623 | /// Create a new ancestor from the given directory path. 624 | #[cfg(windows)] 625 | fn new(dent: &DirEntry) -> io::Result { 626 | let handle = Handle::from_path(dent.path())?; 627 | Ok(Ancestor { path: dent.path().to_path_buf(), handle }) 628 | } 629 | 630 | /// Create a new ancestor from the given directory path. 631 | #[cfg(not(windows))] 632 | fn new(dent: &DirEntry) -> io::Result { 633 | Ok(Ancestor { path: dent.path().to_path_buf() }) 634 | } 635 | 636 | /// Returns true if and only if the given open file handle corresponds to 637 | /// the same directory as this ancestor. 638 | #[cfg(windows)] 639 | fn is_same(&self, child: &Handle) -> io::Result { 640 | Ok(child == &self.handle) 641 | } 642 | 643 | /// Returns true if and only if the given open file handle corresponds to 644 | /// the same directory as this ancestor. 645 | #[cfg(not(windows))] 646 | fn is_same(&self, child: &Handle) -> io::Result { 647 | Ok(child == &Handle::from_path(&self.path)?) 648 | } 649 | } 650 | 651 | /// A sequence of unconsumed directory entries. 652 | /// 653 | /// This represents the opened or closed state of a directory handle. When 654 | /// open, future entries are read by iterating over the raw `fs::ReadDir`. 655 | /// When closed, all future entries are read into memory. Iteration then 656 | /// proceeds over a [`Vec`]. 657 | /// 658 | /// [`fs::ReadDir`]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html 659 | /// [`Vec`]: https://doc.rust-lang.org/stable/std/vec/struct.Vec.html 660 | #[derive(Debug)] 661 | enum DirList { 662 | /// An opened handle. 663 | /// 664 | /// This includes the depth of the handle itself. 665 | /// 666 | /// If there was an error with the initial [`fs::read_dir`] call, then it 667 | /// is stored here. (We use an [`Option<...>`] to make yielding the error 668 | /// exactly once simpler.) 669 | /// 670 | /// [`fs::read_dir`]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html 671 | /// [`Option<...>`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html 672 | Opened { depth: usize, it: result::Result> }, 673 | /// A closed handle. 674 | /// 675 | /// All remaining directory entries are read into memory. 676 | Closed(vec::IntoIter>), 677 | } 678 | 679 | impl Iterator for IntoIter { 680 | type Item = Result; 681 | /// Advances the iterator and returns the next value. 682 | /// 683 | /// # Errors 684 | /// 685 | /// If the iterator fails to retrieve the next value, this method returns 686 | /// an error value. The error will be wrapped in an Option::Some. 687 | fn next(&mut self) -> Option> { 688 | if let Some(start) = self.start.take() { 689 | if self.opts.same_file_system { 690 | let result = util::device_num(&start) 691 | .map_err(|e| Error::from_path(0, start.clone(), e)); 692 | self.root_device = Some(itry!(result)); 693 | } 694 | let dent = itry!(DirEntry::from_path(0, start, false)); 695 | if let Some(result) = self.handle_entry(dent) { 696 | return Some(result); 697 | } 698 | } 699 | while !self.stack_list.is_empty() { 700 | self.depth = self.stack_list.len(); 701 | if let Some(dentry) = self.get_deferred_dir() { 702 | return Some(Ok(dentry)); 703 | } 704 | if self.depth > self.opts.max_depth { 705 | // If we've exceeded the max depth, pop the current dir 706 | // so that we don't descend. 707 | self.pop(); 708 | continue; 709 | } 710 | // Unwrap is safe here because we've verified above that 711 | // `self.stack_list` is not empty 712 | let next = self 713 | .stack_list 714 | .last_mut() 715 | .expect("BUG: stack should be non-empty") 716 | .next(); 717 | match next { 718 | None => self.pop(), 719 | Some(Err(err)) => return Some(Err(err)), 720 | Some(Ok(dent)) => { 721 | if let Some(result) = self.handle_entry(dent) { 722 | return Some(result); 723 | } 724 | } 725 | } 726 | } 727 | if self.opts.contents_first { 728 | self.depth = self.stack_list.len(); 729 | if let Some(dentry) = self.get_deferred_dir() { 730 | return Some(Ok(dentry)); 731 | } 732 | } 733 | None 734 | } 735 | } 736 | 737 | impl IntoIter { 738 | /// Skips the current directory. 739 | /// 740 | /// This causes the iterator to stop traversing the contents of the least 741 | /// recently yielded directory. This means any remaining entries in that 742 | /// directory will be skipped (including sub-directories). 743 | /// 744 | /// Note that the ergonomics of this method are questionable since it 745 | /// borrows the iterator mutably. Namely, you must write out the looping 746 | /// condition manually. For example, to skip hidden entries efficiently on 747 | /// unix systems: 748 | /// 749 | /// ```no_run 750 | /// use walkdir::{DirEntry, WalkDir}; 751 | /// 752 | /// fn is_hidden(entry: &DirEntry) -> bool { 753 | /// entry.file_name() 754 | /// .to_str() 755 | /// .map(|s| s.starts_with(".")) 756 | /// .unwrap_or(false) 757 | /// } 758 | /// 759 | /// let mut it = WalkDir::new("foo").into_iter(); 760 | /// loop { 761 | /// let entry = match it.next() { 762 | /// None => break, 763 | /// Some(Err(err)) => panic!("ERROR: {}", err), 764 | /// Some(Ok(entry)) => entry, 765 | /// }; 766 | /// if is_hidden(&entry) { 767 | /// if entry.file_type().is_dir() { 768 | /// it.skip_current_dir(); 769 | /// } 770 | /// continue; 771 | /// } 772 | /// println!("{}", entry.path().display()); 773 | /// } 774 | /// ``` 775 | /// 776 | /// You may find it more convenient to use the [`filter_entry`] iterator 777 | /// adapter. (See its documentation for the same example functionality as 778 | /// above.) 779 | /// 780 | /// [`filter_entry`]: #method.filter_entry 781 | pub fn skip_current_dir(&mut self) { 782 | if !self.stack_list.is_empty() { 783 | self.pop(); 784 | } 785 | } 786 | 787 | /// Yields only entries which satisfy the given predicate and skips 788 | /// descending into directories that do not satisfy the given predicate. 789 | /// 790 | /// The predicate is applied to all entries. If the predicate is 791 | /// true, iteration carries on as normal. If the predicate is false, the 792 | /// entry is ignored and if it is a directory, it is not descended into. 793 | /// 794 | /// This is often more convenient to use than [`skip_current_dir`]. For 795 | /// example, to skip hidden files and directories efficiently on unix 796 | /// systems: 797 | /// 798 | /// ```no_run 799 | /// use walkdir::{DirEntry, WalkDir}; 800 | /// # use walkdir::Error; 801 | /// 802 | /// fn is_hidden(entry: &DirEntry) -> bool { 803 | /// entry.file_name() 804 | /// .to_str() 805 | /// .map(|s| s.starts_with(".")) 806 | /// .unwrap_or(false) 807 | /// } 808 | /// 809 | /// # fn try_main() -> Result<(), Error> { 810 | /// for entry in WalkDir::new("foo") 811 | /// .into_iter() 812 | /// .filter_entry(|e| !is_hidden(e)) { 813 | /// println!("{}", entry?.path().display()); 814 | /// } 815 | /// # Ok(()) 816 | /// # } 817 | /// ``` 818 | /// 819 | /// Note that the iterator will still yield errors for reading entries that 820 | /// may not satisfy the predicate. 821 | /// 822 | /// Note that entries skipped with [`min_depth`] and [`max_depth`] are not 823 | /// passed to this predicate. 824 | /// 825 | /// Note that if the iterator has `contents_first` enabled, then this 826 | /// method is no different than calling the standard `Iterator::filter` 827 | /// method (because directory entries are yielded after they've been 828 | /// descended into). 829 | /// 830 | /// [`skip_current_dir`]: #method.skip_current_dir 831 | /// [`min_depth`]: struct.WalkDir.html#method.min_depth 832 | /// [`max_depth`]: struct.WalkDir.html#method.max_depth 833 | pub fn filter_entry

(self, predicate: P) -> FilterEntry 834 | where 835 | P: FnMut(&DirEntry) -> bool, 836 | { 837 | FilterEntry { it: self, predicate } 838 | } 839 | 840 | fn handle_entry( 841 | &mut self, 842 | mut dent: DirEntry, 843 | ) -> Option> { 844 | if self.opts.follow_links && dent.file_type().is_symlink() { 845 | dent = itry!(self.follow(dent)); 846 | } 847 | let is_normal_dir = !dent.file_type().is_symlink() && dent.is_dir(); 848 | if is_normal_dir { 849 | if self.opts.same_file_system && dent.depth() > 0 { 850 | if itry!(self.is_same_file_system(&dent)) { 851 | itry!(self.push(&dent)); 852 | } 853 | } else { 854 | itry!(self.push(&dent)); 855 | } 856 | } else if dent.depth() == 0 857 | && dent.file_type().is_symlink() 858 | && self.opts.follow_root_links 859 | { 860 | // As a special case, if we are processing a root entry, then we 861 | // always follow it even if it's a symlink and follow_links is 862 | // false. We are careful to not let this change the semantics of 863 | // the DirEntry however. Namely, the DirEntry should still respect 864 | // the follow_links setting. When it's disabled, it should report 865 | // itself as a symlink. When it's enabled, it should always report 866 | // itself as the target. 867 | let md = itry!(fs::metadata(dent.path()).map_err(|err| { 868 | Error::from_path(dent.depth(), dent.path().to_path_buf(), err) 869 | })); 870 | if md.file_type().is_dir() { 871 | itry!(self.push(&dent)); 872 | } 873 | } 874 | if is_normal_dir && self.opts.contents_first { 875 | self.deferred_dirs.push(dent); 876 | None 877 | } else if self.skippable() { 878 | None 879 | } else { 880 | Some(Ok(dent)) 881 | } 882 | } 883 | 884 | fn get_deferred_dir(&mut self) -> Option { 885 | if self.opts.contents_first { 886 | if self.depth < self.deferred_dirs.len() { 887 | // Unwrap is safe here because we've guaranteed that 888 | // `self.deferred_dirs.len()` can never be less than 1 889 | let deferred: DirEntry = self 890 | .deferred_dirs 891 | .pop() 892 | .expect("BUG: deferred_dirs should be non-empty"); 893 | if !self.skippable() { 894 | return Some(deferred); 895 | } 896 | } 897 | } 898 | None 899 | } 900 | 901 | fn push(&mut self, dent: &DirEntry) -> Result<()> { 902 | // Make room for another open file descriptor if we've hit the max. 903 | let free = 904 | self.stack_list.len().checked_sub(self.oldest_opened).unwrap(); 905 | if free == self.opts.max_open { 906 | self.stack_list[self.oldest_opened].close(); 907 | } 908 | // Open a handle to reading the directory's entries. 909 | let rd = fs::read_dir(dent.path()).map_err(|err| { 910 | Some(Error::from_path(self.depth, dent.path().to_path_buf(), err)) 911 | }); 912 | let mut list = DirList::Opened { depth: self.depth, it: rd }; 913 | if let Some(ref mut cmp) = self.opts.sorter { 914 | let mut entries: Vec<_> = list.collect(); 915 | entries.sort_by(|a, b| match (a, b) { 916 | (&Ok(ref a), &Ok(ref b)) => cmp(a, b), 917 | (&Err(_), &Err(_)) => Ordering::Equal, 918 | (&Ok(_), &Err(_)) => Ordering::Greater, 919 | (&Err(_), &Ok(_)) => Ordering::Less, 920 | }); 921 | list = DirList::Closed(entries.into_iter()); 922 | } 923 | if self.opts.follow_links { 924 | let ancestor = Ancestor::new(&dent) 925 | .map_err(|err| Error::from_io(self.depth, err))?; 926 | self.stack_path.push(ancestor); 927 | } 928 | // We push this after stack_path since creating the Ancestor can fail. 929 | // If it fails, then we return the error and won't descend. 930 | self.stack_list.push(list); 931 | // If we had to close out a previous directory stream, then we need to 932 | // increment our index the oldest still-open stream. We do this only 933 | // after adding to our stack, in order to ensure that the oldest_opened 934 | // index remains valid. The worst that can happen is that an already 935 | // closed stream will be closed again, which is a no-op. 936 | // 937 | // We could move the close of the stream above into this if-body, but 938 | // then we would have more than the maximum number of file descriptors 939 | // open at a particular point in time. 940 | if free == self.opts.max_open { 941 | // Unwrap is safe here because self.oldest_opened is guaranteed to 942 | // never be greater than `self.stack_list.len()`, which implies 943 | // that the subtraction won't underflow and that adding 1 will 944 | // never overflow. 945 | self.oldest_opened = self.oldest_opened.checked_add(1).unwrap(); 946 | } 947 | Ok(()) 948 | } 949 | 950 | fn pop(&mut self) { 951 | self.stack_list.pop().expect("BUG: cannot pop from empty stack"); 952 | if self.opts.follow_links { 953 | self.stack_path.pop().expect("BUG: list/path stacks out of sync"); 954 | } 955 | // If everything in the stack is already closed, then there is 956 | // room for at least one more open descriptor and it will 957 | // always be at the top of the stack. 958 | self.oldest_opened = min(self.oldest_opened, self.stack_list.len()); 959 | } 960 | 961 | fn follow(&self, mut dent: DirEntry) -> Result { 962 | dent = 963 | DirEntry::from_path(self.depth, dent.path().to_path_buf(), true)?; 964 | // The only way a symlink can cause a loop is if it points 965 | // to a directory. Otherwise, it always points to a leaf 966 | // and we can omit any loop checks. 967 | if dent.is_dir() { 968 | self.check_loop(dent.path())?; 969 | } 970 | Ok(dent) 971 | } 972 | 973 | fn check_loop>(&self, child: P) -> Result<()> { 974 | let hchild = Handle::from_path(&child) 975 | .map_err(|err| Error::from_io(self.depth, err))?; 976 | for ancestor in self.stack_path.iter().rev() { 977 | let is_same = ancestor 978 | .is_same(&hchild) 979 | .map_err(|err| Error::from_io(self.depth, err))?; 980 | if is_same { 981 | return Err(Error::from_loop( 982 | self.depth, 983 | &ancestor.path, 984 | child.as_ref(), 985 | )); 986 | } 987 | } 988 | Ok(()) 989 | } 990 | 991 | fn is_same_file_system(&mut self, dent: &DirEntry) -> Result { 992 | let dent_device = util::device_num(dent.path()) 993 | .map_err(|err| Error::from_entry(dent, err))?; 994 | Ok(self 995 | .root_device 996 | .map(|d| d == dent_device) 997 | .expect("BUG: called is_same_file_system without root device")) 998 | } 999 | 1000 | fn skippable(&self) -> bool { 1001 | self.depth < self.opts.min_depth || self.depth > self.opts.max_depth 1002 | } 1003 | } 1004 | 1005 | impl iter::FusedIterator for IntoIter {} 1006 | 1007 | impl DirList { 1008 | fn close(&mut self) { 1009 | if let DirList::Opened { .. } = *self { 1010 | *self = DirList::Closed(self.collect::>().into_iter()); 1011 | } 1012 | } 1013 | } 1014 | 1015 | impl Iterator for DirList { 1016 | type Item = Result; 1017 | 1018 | #[inline(always)] 1019 | fn next(&mut self) -> Option> { 1020 | match *self { 1021 | DirList::Closed(ref mut it) => it.next(), 1022 | DirList::Opened { depth, ref mut it } => match *it { 1023 | Err(ref mut err) => err.take().map(Err), 1024 | Ok(ref mut rd) => rd.next().map(|r| match r { 1025 | Ok(r) => DirEntry::from_entry(depth + 1, &r), 1026 | Err(err) => Err(Error::from_io(depth + 1, err)), 1027 | }), 1028 | }, 1029 | } 1030 | } 1031 | } 1032 | 1033 | /// A recursive directory iterator that skips entries. 1034 | /// 1035 | /// Values of this type are created by calling [`.filter_entry()`] on an 1036 | /// `IntoIter`, which is formed by calling [`.into_iter()`] on a `WalkDir`. 1037 | /// 1038 | /// Directories that fail the predicate `P` are skipped. Namely, they are 1039 | /// never yielded and never descended into. 1040 | /// 1041 | /// Entries that are skipped with the [`min_depth`] and [`max_depth`] options 1042 | /// are not passed through this filter. 1043 | /// 1044 | /// If opening a handle to a directory resulted in an error, then it is yielded 1045 | /// and no corresponding call to the predicate is made. 1046 | /// 1047 | /// Type parameter `I` refers to the underlying iterator and `P` refers to the 1048 | /// predicate, which is usually `FnMut(&DirEntry) -> bool`. 1049 | /// 1050 | /// [`.filter_entry()`]: struct.IntoIter.html#method.filter_entry 1051 | /// [`.into_iter()`]: struct.WalkDir.html#into_iter.v 1052 | /// [`min_depth`]: struct.WalkDir.html#method.min_depth 1053 | /// [`max_depth`]: struct.WalkDir.html#method.max_depth 1054 | #[derive(Debug)] 1055 | pub struct FilterEntry { 1056 | it: I, 1057 | predicate: P, 1058 | } 1059 | 1060 | impl

Iterator for FilterEntry 1061 | where 1062 | P: FnMut(&DirEntry) -> bool, 1063 | { 1064 | type Item = Result; 1065 | 1066 | /// Advances the iterator and returns the next value. 1067 | /// 1068 | /// # Errors 1069 | /// 1070 | /// If the iterator fails to retrieve the next value, this method returns 1071 | /// an error value. The error will be wrapped in an `Option::Some`. 1072 | fn next(&mut self) -> Option> { 1073 | loop { 1074 | let dent = match self.it.next() { 1075 | None => return None, 1076 | Some(result) => itry!(result), 1077 | }; 1078 | if !(self.predicate)(&dent) { 1079 | if dent.is_dir() { 1080 | self.it.skip_current_dir(); 1081 | } 1082 | continue; 1083 | } 1084 | return Some(Ok(dent)); 1085 | } 1086 | } 1087 | } 1088 | 1089 | impl

iter::FusedIterator for FilterEntry where 1090 | P: FnMut(&DirEntry) -> bool 1091 | { 1092 | } 1093 | 1094 | impl

FilterEntry 1095 | where 1096 | P: FnMut(&DirEntry) -> bool, 1097 | { 1098 | /// Yields only entries which satisfy the given predicate and skips 1099 | /// descending into directories that do not satisfy the given predicate. 1100 | /// 1101 | /// The predicate is applied to all entries. If the predicate is 1102 | /// true, iteration carries on as normal. If the predicate is false, the 1103 | /// entry is ignored and if it is a directory, it is not descended into. 1104 | /// 1105 | /// This is often more convenient to use than [`skip_current_dir`]. For 1106 | /// example, to skip hidden files and directories efficiently on unix 1107 | /// systems: 1108 | /// 1109 | /// ```no_run 1110 | /// use walkdir::{DirEntry, WalkDir}; 1111 | /// # use walkdir::Error; 1112 | /// 1113 | /// fn is_hidden(entry: &DirEntry) -> bool { 1114 | /// entry.file_name() 1115 | /// .to_str() 1116 | /// .map(|s| s.starts_with(".")) 1117 | /// .unwrap_or(false) 1118 | /// } 1119 | /// 1120 | /// # fn try_main() -> Result<(), Error> { 1121 | /// for entry in WalkDir::new("foo") 1122 | /// .into_iter() 1123 | /// .filter_entry(|e| !is_hidden(e)) { 1124 | /// println!("{}", entry?.path().display()); 1125 | /// } 1126 | /// # Ok(()) 1127 | /// # } 1128 | /// ``` 1129 | /// 1130 | /// Note that the iterator will still yield errors for reading entries that 1131 | /// may not satisfy the predicate. 1132 | /// 1133 | /// Note that entries skipped with [`min_depth`] and [`max_depth`] are not 1134 | /// passed to this predicate. 1135 | /// 1136 | /// Note that if the iterator has `contents_first` enabled, then this 1137 | /// method is no different than calling the standard `Iterator::filter` 1138 | /// method (because directory entries are yielded after they've been 1139 | /// descended into). 1140 | /// 1141 | /// [`skip_current_dir`]: #method.skip_current_dir 1142 | /// [`min_depth`]: struct.WalkDir.html#method.min_depth 1143 | /// [`max_depth`]: struct.WalkDir.html#method.max_depth 1144 | pub fn filter_entry(self, predicate: P) -> FilterEntry { 1145 | FilterEntry { it: self, predicate } 1146 | } 1147 | 1148 | /// Skips the current directory. 1149 | /// 1150 | /// This causes the iterator to stop traversing the contents of the least 1151 | /// recently yielded directory. This means any remaining entries in that 1152 | /// directory will be skipped (including sub-directories). 1153 | /// 1154 | /// Note that the ergonomics of this method are questionable since it 1155 | /// borrows the iterator mutably. Namely, you must write out the looping 1156 | /// condition manually. For example, to skip hidden entries efficiently on 1157 | /// unix systems: 1158 | /// 1159 | /// ```no_run 1160 | /// use walkdir::{DirEntry, WalkDir}; 1161 | /// 1162 | /// fn is_hidden(entry: &DirEntry) -> bool { 1163 | /// entry.file_name() 1164 | /// .to_str() 1165 | /// .map(|s| s.starts_with(".")) 1166 | /// .unwrap_or(false) 1167 | /// } 1168 | /// 1169 | /// let mut it = WalkDir::new("foo").into_iter(); 1170 | /// loop { 1171 | /// let entry = match it.next() { 1172 | /// None => break, 1173 | /// Some(Err(err)) => panic!("ERROR: {}", err), 1174 | /// Some(Ok(entry)) => entry, 1175 | /// }; 1176 | /// if is_hidden(&entry) { 1177 | /// if entry.file_type().is_dir() { 1178 | /// it.skip_current_dir(); 1179 | /// } 1180 | /// continue; 1181 | /// } 1182 | /// println!("{}", entry.path().display()); 1183 | /// } 1184 | /// ``` 1185 | /// 1186 | /// You may find it more convenient to use the [`filter_entry`] iterator 1187 | /// adapter. (See its documentation for the same example functionality as 1188 | /// above.) 1189 | /// 1190 | /// [`filter_entry`]: #method.filter_entry 1191 | pub fn skip_current_dir(&mut self) { 1192 | self.it.skip_current_dir(); 1193 | } 1194 | } 1195 | --------------------------------------------------------------------------------