mirror of
https://github.com/BurntSushi/walkdir.git
synced 2025-09-28 06:00:32 +00:00
Lots of polish. Docs. Refactoring. Simplifying.
This commit is contained in:
parent
4918e08926
commit
daf1ee1f24
9
.travis.yml
Normal file
9
.travis.yml
Normal file
@ -0,0 +1,9 @@
|
||||
language: rust
|
||||
rust:
|
||||
- 1.3.0
|
||||
- beta
|
||||
- nightly
|
||||
script:
|
||||
- cargo build --verbose
|
||||
- cargo test --verbose
|
||||
- cargo doc
|
3
COPYING
Normal file
3
COPYING
Normal file
@ -0,0 +1,3 @@
|
||||
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||
|
||||
You may use this code under the terms of either license.
|
@ -8,7 +8,7 @@ homepage = "https://github.com/BurntSushi/walkdir"
|
||||
repository = "https://github.com/BurntSushi/walkdir"
|
||||
readme = "README.md"
|
||||
keywords = ["directory", "recursive", "walk", "iterator"]
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "Unlicense/MIT"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.1"
|
||||
|
201
LICENSE-APACHE
201
LICENSE-APACHE
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
40
LICENSE-MIT
40
LICENSE-MIT
@ -1,25 +1,21 @@
|
||||
Copyright (c) 2014 The Rust Project Developers
|
||||
The MIT License (MIT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
Copyright (c) 2015 Andrew Gallant
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
137
README.md
137
README.md
@ -1,6 +1,139 @@
|
||||
walkdir
|
||||
=======
|
||||
A cross platform Rust library for efficiently walking a directory recursively.
|
||||
Comes with support for following symbolic links, controlling the number of file
|
||||
descriptors and efficient mechanisms for pruning the entries in the directory
|
||||
tree.
|
||||
|
||||
A Rust library for efficiently walking a directory recursively.
|
||||
[](https://travis-ci.org/BurntSushi/walkdir)
|
||||
[](https://crates.io/crates/walkdir)
|
||||
|
||||
This is a work in progress and is (hopefully) destined for `std::fs`.
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
### Documentation
|
||||
|
||||
[http://burntsushi.net/rustdoc/walkdir/](http://burntsushi.net/rustdoc/walkdir/)
|
||||
|
||||
### Usage
|
||||
|
||||
To use this crate, add `walkdir` as a dependency to your project's
|
||||
`Cargo.toml`:
|
||||
|
||||
```
|
||||
[dependencies]
|
||||
walkdir = "0.1"
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
The following code recursively iterates over the directory given and prints
|
||||
the path for each entry:
|
||||
|
||||
```rust,no_run
|
||||
use walkdir::WalkDir;
|
||||
|
||||
for entry in WalkDir::new("foo") {
|
||||
let entry = entry.unwrap();
|
||||
println!("{}", entry.path().display());
|
||||
}
|
||||
```
|
||||
|
||||
Or, if you'd like to iterate over all entries and ignore any errors that may
|
||||
arise, use `filter_map`. (e.g., This code below will silently skip directories
|
||||
that the owner of the running process does not have permission to access.)
|
||||
|
||||
```rust,no_run
|
||||
use walkdir::WalkDir;
|
||||
|
||||
for entry in WalkDir::new("foo").into_iter().filter_map(|e| e.ok()) {
|
||||
println!("{}", entry.path().display());
|
||||
}
|
||||
```
|
||||
|
||||
### Example: follow symbolic links
|
||||
|
||||
The same code as above, except `follow_links` is enabled:
|
||||
|
||||
```rust,no_run
|
||||
use walkdir::WalkDir;
|
||||
|
||||
for entry in WalkDir::new("foo").follow_links(true) {
|
||||
let entry = entry.unwrap();
|
||||
println!("{}", entry.path().display());
|
||||
}
|
||||
```
|
||||
|
||||
### Example: skip hidden files and directories efficiently on unix
|
||||
|
||||
This uses the `filter_entry` iterator adapter to avoid yielding hidden files
|
||||
and directories efficiently:
|
||||
|
||||
```rust,no_run
|
||||
use walkdir::{DirEntry, WalkDir, WalkDirIterator};
|
||||
|
||||
fn is_hidden(entry: &DirEntry) -> bool {
|
||||
entry.file_name()
|
||||
.to_str()
|
||||
.map(|s| s.starts_with("."))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
let walker = WalkDir::new("foo").into_iter();
|
||||
for entry in walker.filter_entry(|e| !is_hidden(e)) {
|
||||
let entry = entry.unwrap();
|
||||
println!("{}", entry.path().display());
|
||||
}
|
||||
```
|
||||
|
||||
### Motivation
|
||||
|
||||
`std::fs` has an unstable `walk_dir` implementation that needed some design
|
||||
work. I started off on that task, but it quickly became apparent that walking
|
||||
a directory recursively is quite complex and may not be a good fit for `std`
|
||||
right away.
|
||||
|
||||
This should at least resolve most or all of the issues reported here (and then
|
||||
some):
|
||||
|
||||
* https://github.com/rust-lang/rust/issues/27707
|
||||
* https://github.com/rust-lang/rust/issues/23715
|
||||
|
||||
### Performance
|
||||
|
||||
The short story is that performance is comparable with `find` and glibc's
|
||||
`nftw` on both a warm and cold file cache. In fact, I cannot observe any
|
||||
performance difference after running `find /`, `walkdir /` and `nftw /` on my
|
||||
local file system (SSD, ~3 million entries). More precisely, I am reasonably
|
||||
confident that this crate makes as few system calls and close to as few
|
||||
allocations as possible.
|
||||
|
||||
I haven't recorded any benchmarks, but here are some things you can try with a
|
||||
local checkout of `walkdir`:
|
||||
|
||||
```
|
||||
# The directory you want to recursively walk:
|
||||
DIR=$HOME
|
||||
|
||||
# If you want to observe perf on a cold file cache, run this before *each*
|
||||
# command:
|
||||
sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
|
||||
|
||||
# To warm the caches
|
||||
find $HOME
|
||||
|
||||
# Test speed of `find` on warm cache:
|
||||
time find $HOME
|
||||
|
||||
# Compile and test speed of `walkdir` crate:
|
||||
cargo build --release --example walkdir
|
||||
time ./target/release/examples/walkdir $DIR
|
||||
|
||||
# Compile and test speed of glibc's `nftw`:
|
||||
gcc -O3 -o nftw ./compare/nftw.c
|
||||
time ./nftw $DIR
|
||||
|
||||
# For shits and giggles, test speed of Python's (2 or 3) os.walk:
|
||||
time python ./compare/walk.py $DIR
|
||||
```
|
||||
|
||||
On my system, the performance of `walkdir`, `find` and `nftw` is comparable.
|
||||
|
24
UNLICENSE
Normal file
24
UNLICENSE
Normal file
@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
18
appveyor.yml
Normal file
18
appveyor.yml
Normal file
@ -0,0 +1,18 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
install:
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe"
|
||||
- rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
||||
- SET PATH=%PATH%;C:\MinGW\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo build --verbose
|
||||
- cargo test --verbose
|
25
compare/nftw.c
Normal file
25
compare/nftw.c
Normal file
@ -0,0 +1,25 @@
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include <ftw.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static int
|
||||
display_info(const char *fpath, const struct stat *sb,
|
||||
int tflag, struct FTW *ftwbuf)
|
||||
{
|
||||
printf("%s\n", fpath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int flags = FTW_PHYS;
|
||||
if (nftw((argc < 2) ? "." : argv[1], display_info, 20, flags) == -1) {
|
||||
perror("nftw");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
10
compare/walk.py
Normal file
10
compare/walk.py
Normal file
@ -0,0 +1,10 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(sys.argv[1]):
|
||||
for n in dirnames:
|
||||
print(os.path.join(dirpath, n))
|
||||
for n in filenames:
|
||||
print(os.path.join(dirpath, n))
|
@ -2,6 +2,8 @@ extern crate docopt;
|
||||
extern crate rustc_serialize;
|
||||
extern crate walkdir;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
use docopt::Docopt;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@ -12,11 +14,11 @@ Usage:
|
||||
Options:
|
||||
-h, --help
|
||||
-L, --follow-links Follow symlinks.
|
||||
-d, --depth Traverse contents of directories first.
|
||||
--min-depth NUM Minimum depth.
|
||||
--max-depth NUM Maximum depth.
|
||||
-n, --fd-max NUM Maximum open file descriptors. [default: 32]
|
||||
--tree Show output as a tree.
|
||||
-q, --ignore-errors Ignore errors.
|
||||
";
|
||||
|
||||
#[derive(Debug, RustcDecodable)]
|
||||
@ -27,37 +29,50 @@ struct Args {
|
||||
flag_min_depth: Option<usize>,
|
||||
flag_max_depth: Option<usize>,
|
||||
flag_fd_max: usize,
|
||||
flag_depth: bool,
|
||||
flag_tree: bool,
|
||||
flag_ignore_errors: bool,
|
||||
}
|
||||
|
||||
macro_rules! wout { ($($tt:tt)*) => { {writeln!($($tt)*)}.unwrap() } }
|
||||
|
||||
fn main() {
|
||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
let mind = args.flag_min_depth.unwrap_or(0);
|
||||
let maxd = args.flag_max_depth.unwrap_or(::std::usize::MAX);
|
||||
let mut it = WalkDir::new(args.arg_dir.unwrap_or(".".to_owned()))
|
||||
.max_open(args.flag_fd_max)
|
||||
.follow_links(args.flag_follow_links)
|
||||
.contents_first(args.flag_depth)
|
||||
.min_depth(mind)
|
||||
.max_depth(maxd)
|
||||
.into_iter();
|
||||
let it = WalkDir::new(args.arg_dir.clone().unwrap_or(".".to_owned()))
|
||||
.max_open(args.flag_fd_max)
|
||||
.follow_links(args.flag_follow_links)
|
||||
.min_depth(mind)
|
||||
.max_depth(maxd)
|
||||
.into_iter();
|
||||
let mut out = io::BufWriter::new(io::stdout());
|
||||
let mut eout = io::stderr();
|
||||
if args.flag_tree {
|
||||
loop {
|
||||
let dent = match it.next() {
|
||||
None => break,
|
||||
Some(Err(err)) => { println!("ERROR: {}", err); continue }
|
||||
Some(Ok(dent)) => dent,
|
||||
};
|
||||
let name = dent.file_name().into_string().unwrap();
|
||||
println!("{}{}", indent(it.depth()), name);
|
||||
for dent in it {
|
||||
match dent {
|
||||
Err(err) => {
|
||||
out.flush().unwrap();
|
||||
wout!(eout, "ERROR: {}", err);
|
||||
}
|
||||
Ok(dent) => {
|
||||
let name = dent.file_name().to_string_lossy();
|
||||
wout!(out, "{}{}", indent(dent.depth()), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if args.flag_ignore_errors {
|
||||
for dent in it.filter_map(|e| e.ok()) {
|
||||
wout!(out, "{}", dent.path().display());
|
||||
}
|
||||
} else {
|
||||
for dent in it {
|
||||
match dent {
|
||||
Ok(dent) => println!("{}", dent.path().display()),
|
||||
Err(err) => println!("ERROR: {}", err),
|
||||
Err(err) => {
|
||||
out.flush().unwrap();
|
||||
wout!(eout, "ERROR: {}", err);
|
||||
}
|
||||
Ok(dent) => wout!(out, "{}", dent.path().display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1075
src/lib.rs
1075
src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
// Below are platform specific functions for testing the equality of two
|
||||
// files. Namely, we want to know whether the two paths points to precisely
|
||||
// the same underlying file object.
|
||||
// Below are platform specific functions for testing the equality of two files.
|
||||
// Namely, we want to know whether two paths points to precisely the same
|
||||
// underlying file object.
|
||||
//
|
||||
// In our particular use case, the paths should only be directories. If we're
|
||||
// assuming that directories cannot be hard linked, then it seems like equality
|
||||
@ -11,7 +11,8 @@ use std::path::Path;
|
||||
//
|
||||
// I'd also note that other popular libraries (Java's NIO and Boost) expose
|
||||
// a function like `is_same_file` whose implementation is similar. (i.e., check
|
||||
// dev/inode on Unix and check `nFileIndex{High,Low}` on Windows.)
|
||||
// dev/inode on Unix and check `nFileIndex{High,Low}` on Windows.) So this may
|
||||
// be a candidate for extracting into a separate crate.
|
||||
//
|
||||
// ---AG
|
||||
|
||||
@ -119,9 +120,9 @@ where P: AsRef<Path>, Q: AsRef<Path> {
|
||||
s.as_os_str().encode_wide().chain(Some(0)).collect()
|
||||
}
|
||||
|
||||
// For correctness, it is critical that both file handles remain open
|
||||
// while their attributes are checked for equality. In particular,
|
||||
// the file index numbers are not guaranteed to remain stable over time.
|
||||
// For correctness, it is critical that both file handles remain open while
|
||||
// their attributes are checked for equality. In particular, the file index
|
||||
// numbers are not guaranteed to remain stable over time.
|
||||
//
|
||||
// See the docs and remarks on MSDN:
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx
|
||||
@ -133,8 +134,8 @@ where P: AsRef<Path>, Q: AsRef<Path> {
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx
|
||||
//
|
||||
// It seems straight-forward enough to modify this code to use
|
||||
// `FILE_ID_INFO` when available (minimum Windows Server 2012), but
|
||||
// I don't have access to such Windows machines.
|
||||
// `FILE_ID_INFO` when available (minimum Windows Server 2012), but I don't
|
||||
// have access to such Windows machines.
|
||||
//
|
||||
// Two notes.
|
||||
//
|
||||
@ -144,7 +145,7 @@ where P: AsRef<Path>, Q: AsRef<Path> {
|
||||
// `nFileIndex{Low,High}` are not unique.
|
||||
//
|
||||
// 2. LLVM has a bug where they fetch the id of a file and continue to use
|
||||
// it even after the file has been closed, so that uniqueness is no
|
||||
// it even after the handle has been closed, so that uniqueness is no
|
||||
// longer guaranteed (when `nFileIndex{Low,High}` are unique).
|
||||
// bug report: http://lists.llvm.org/pipermail/llvm-bugs/2014-December/037218.html
|
||||
//
|
||||
@ -156,7 +157,8 @@ where P: AsRef<Path>, Q: AsRef<Path> {
|
||||
// In the case where this code is erroneous, two files will be reported
|
||||
// as equivalent when they are in fact distinct. This will cause the loop
|
||||
// detection code to report a false positive, which will prevent descending
|
||||
// into the offending directory.
|
||||
// into the offending directory. As far as failure modes goes, this isn't
|
||||
// that bad.
|
||||
let h1 = try!(open_read_attr(&p1));
|
||||
let h2 = try!(open_read_attr(&p2));
|
||||
let i1 = try!(file_info(&h1));
|
||||
|
141
src/tests.rs
141
src/tests.rs
@ -1,7 +1,6 @@
|
||||
#![allow(dead_code, unused_imports)]
|
||||
#![cfg_attr(windows, allow(dead_code, unused_imports))]
|
||||
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -9,7 +8,7 @@ use std::path::{Path, PathBuf};
|
||||
use quickcheck::{Arbitrary, Gen, QuickCheck, StdGen};
|
||||
use rand::{self, Rng};
|
||||
|
||||
use super::{DirEntry, WalkDir, WalkDirError, WalkDirIter};
|
||||
use super::{DirEntry, WalkDir, WalkDirIterator, Iter, Error, ErrorInner};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
enum Tree {
|
||||
@ -19,15 +18,11 @@ enum Tree {
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
fn from_walk<P: AsRef<Path>>(p: P) -> io::Result<Tree> {
|
||||
Tree::from_walk_with(p, |wd| wd)
|
||||
}
|
||||
|
||||
fn from_walk_with<P, F>(
|
||||
p: P,
|
||||
f: F,
|
||||
) -> io::Result<Tree>
|
||||
where P: AsRef<Path>, F: FnOnce(WalkDir<P>) -> WalkDir<P> {
|
||||
where P: AsRef<Path>, F: FnOnce(WalkDir) -> WalkDir {
|
||||
let mut stack = vec![Tree::Dir(p.as_ref().to_path_buf(), vec![])];
|
||||
let it: WalkEventIter = f(WalkDir::new(p)).into();
|
||||
for ev in it {
|
||||
@ -43,7 +38,7 @@ impl Tree {
|
||||
stack.push(Tree::Dir(pb(dent.file_name()), vec![]));
|
||||
}
|
||||
WalkEvent::File(dent) => {
|
||||
let node = if try!(dent.file_type()).is_symlink() {
|
||||
let node = if dent.file_type().is_symlink() {
|
||||
let src = try!(fs::read_link(dent.path()));
|
||||
let dst = pb(dent.file_name());
|
||||
Tree::Symlink(src, dst)
|
||||
@ -222,42 +217,6 @@ impl Arbitrary for Tree {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl fmt::Debug for Tree {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn rep(c: char, n: usize) -> String {
|
||||
::std::iter::repeat(c).take(n).collect()
|
||||
}
|
||||
|
||||
fn fmt(
|
||||
f: &mut fmt::Formatter,
|
||||
tree: &Tree,
|
||||
depth: usize,
|
||||
) -> fmt::Result {
|
||||
match *tree {
|
||||
Tree::File(ref pb) => {
|
||||
writeln!(f, "{}{}", rep(' ', 2 * depth), pb.display())
|
||||
}
|
||||
Tree::Symlink(ref src, ref dst) => {
|
||||
writeln!(f, "{}{} -> {}",
|
||||
rep(' ', 2 * depth),
|
||||
dst.display(), src.display())
|
||||
}
|
||||
Tree::Dir(ref pb, ref children) => {
|
||||
try!(writeln!(f, "{}{}",
|
||||
rep(' ', 2 * depth), pb.display()));
|
||||
for c in children {
|
||||
try!(fmt(f, c, depth + 1));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt(f, self, 0)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
enum WalkEvent {
|
||||
Dir(DirEntry),
|
||||
@ -267,12 +226,12 @@ enum WalkEvent {
|
||||
|
||||
struct WalkEventIter {
|
||||
depth: usize,
|
||||
it: WalkDirIter,
|
||||
next: Option<Result<DirEntry, WalkDirError>>,
|
||||
it: Iter,
|
||||
next: Option<Result<DirEntry, Error>>,
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>> From<WalkDir<P>> for WalkEventIter {
|
||||
fn from(it: WalkDir<P>) -> WalkEventIter {
|
||||
impl From<WalkDir> for WalkEventIter {
|
||||
fn from(it: WalkDir) -> WalkEventIter {
|
||||
WalkEventIter { depth: 0, it: it.into_iter(), next: None }
|
||||
}
|
||||
}
|
||||
@ -282,26 +241,26 @@ impl Iterator for WalkEventIter {
|
||||
|
||||
fn next(&mut self) -> Option<io::Result<WalkEvent>> {
|
||||
let dent = self.next.take().or_else(|| self.it.next());
|
||||
if self.it.depth() < self.depth {
|
||||
let depth = match dent {
|
||||
None => 0,
|
||||
Some(Ok(ref dent)) => dent.depth(),
|
||||
Some(Err(ref err)) => err.depth(),
|
||||
};
|
||||
if depth < self.depth {
|
||||
self.depth -= 1;
|
||||
self.next = dent;
|
||||
return Some(Ok(WalkEvent::Exit));
|
||||
}
|
||||
self.depth = self.it.depth();
|
||||
self.depth = depth;
|
||||
match dent {
|
||||
None => None,
|
||||
Some(Err(err)) => Some(Err(From::from(err))),
|
||||
Some(Ok(dent)) => {
|
||||
match dent.file_type() {
|
||||
Err(err) => Some(Err(err)),
|
||||
Ok(ty) => {
|
||||
if ty.is_dir() {
|
||||
self.depth += 1;
|
||||
Some(Ok(WalkEvent::Dir(dent)))
|
||||
} else {
|
||||
Some(Ok(WalkEvent::File(dent)))
|
||||
}
|
||||
}
|
||||
if dent.file_type().is_dir() {
|
||||
self.depth += 1;
|
||||
Some(Ok(WalkEvent::Dir(dent)))
|
||||
} else {
|
||||
Some(Ok(WalkEvent::File(dent)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,10 +270,6 @@ impl Iterator for WalkEventIter {
|
||||
struct TempDir(PathBuf);
|
||||
|
||||
impl TempDir {
|
||||
fn join(&self, path: &str) -> PathBuf {
|
||||
(&*self.0).join(path)
|
||||
}
|
||||
|
||||
fn path<'a>(&'a self) -> &'a Path {
|
||||
&self.0
|
||||
}
|
||||
@ -335,11 +290,11 @@ fn tmpdir() -> TempDir {
|
||||
}
|
||||
|
||||
fn dir_setup_with<F>(t: &Tree, f: F) -> (TempDir, Tree)
|
||||
where F: FnOnce(WalkDir<&Path>) -> WalkDir<&Path> {
|
||||
where F: FnOnce(WalkDir) -> WalkDir {
|
||||
let tmp = tmpdir();
|
||||
t.create_in(tmp.path()).unwrap();
|
||||
let got = Tree::from_walk_with(tmp.path(), f).unwrap();
|
||||
(tmp, got.unwrap_singleton())
|
||||
(tmp, got.unwrap_singleton().unwrap_singleton())
|
||||
}
|
||||
|
||||
fn dir_setup(t: &Tree) -> (TempDir, Tree) {
|
||||
@ -366,6 +321,10 @@ fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
symlink(src, dst)
|
||||
}
|
||||
|
||||
// TODO: Figure out how to do symlinks on windows.
|
||||
// Windows differentiates dir and file symlinks.
|
||||
// We may need to tweak the `Tree` data type to
|
||||
// split links into dir/file.
|
||||
#[cfg(windows)]
|
||||
fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
_src: P,
|
||||
@ -498,10 +457,10 @@ fn walk_dir_sym_detect_loop() {
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
match got {
|
||||
Ok(x) => panic!("expected loop error, got no error: {:?}", x),
|
||||
Err(WalkDirError::Io { .. }) => {
|
||||
Err(Error { inner: ErrorInner::Io { .. }, .. }) => {
|
||||
panic!("expected loop error, got generic IO error");
|
||||
}
|
||||
Err(WalkDirError::Loop { .. }) => {}
|
||||
Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -517,10 +476,10 @@ fn walk_dir_sym_infinite() {
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
match got {
|
||||
Ok(x) => panic!("expected IO error, got no error: {:?}", x),
|
||||
Err(WalkDirError::Loop { .. }) => {
|
||||
Err(Error { inner: ErrorInner::Loop { .. }, .. }) => {
|
||||
panic!("expected IO error, but got loop error");
|
||||
}
|
||||
Err(WalkDirError::Io { .. }) => {}
|
||||
Err(Error { inner: ErrorInner::Io { .. }, .. }) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,7 +495,7 @@ fn walk_dir_min_depth_2() {
|
||||
let exp = td("foo", vec![tf("bar"), tf("baz")]);
|
||||
let tmp = tmpdir();
|
||||
exp.create_in(tmp.path()).unwrap();
|
||||
let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(1))
|
||||
let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(2))
|
||||
.unwrap().unwrap_dir();
|
||||
assert_tree_eq!(exp, td("foo", got));
|
||||
}
|
||||
@ -550,7 +509,7 @@ fn walk_dir_min_depth_3() {
|
||||
]);
|
||||
let tmp = tmpdir();
|
||||
exp.create_in(tmp.path()).unwrap();
|
||||
let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(2))
|
||||
let got = Tree::from_walk_with(tmp.path(), |wd| wd.min_depth(3))
|
||||
.unwrap().unwrap_dir();
|
||||
assert_eq!(vec![tf("xyz")], got);
|
||||
}
|
||||
@ -558,14 +517,14 @@ fn walk_dir_min_depth_3() {
|
||||
#[test]
|
||||
fn walk_dir_max_depth_1() {
|
||||
let exp = td("foo", vec![tf("bar")]);
|
||||
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(0));
|
||||
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
|
||||
assert_tree_eq!(td("foo", vec![]), got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_dir_max_depth_2() {
|
||||
let exp = td("foo", vec![tf("bar"), tf("baz")]);
|
||||
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(0));
|
||||
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
|
||||
assert_tree_eq!(td("foo", vec![]), got);
|
||||
}
|
||||
|
||||
@ -581,7 +540,7 @@ fn walk_dir_max_depth_3() {
|
||||
td("abc", vec![]),
|
||||
tf("baz"),
|
||||
]);
|
||||
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(1));
|
||||
let (_tmp, got) = dir_setup_with(&exp, |wd| wd.max_depth(2));
|
||||
assert_tree_eq!(exp_trimmed, got);
|
||||
}
|
||||
|
||||
@ -595,7 +554,7 @@ fn walk_dir_min_max_depth() {
|
||||
let tmp = tmpdir();
|
||||
exp.create_in(tmp.path()).unwrap();
|
||||
let got = Tree::from_walk_with(tmp.path(),
|
||||
|wd| wd.min_depth(1).max_depth(1))
|
||||
|wd| wd.min_depth(2).max_depth(2))
|
||||
.unwrap().unwrap_dir();
|
||||
assert_tree_eq!(
|
||||
td("foo", vec![tf("bar"), td("abc", vec![]), tf("baz")]),
|
||||
@ -612,13 +571,13 @@ fn walk_dir_skip() {
|
||||
let tmp = tmpdir();
|
||||
exp.create_in(tmp.path()).unwrap();
|
||||
let mut got = vec![];
|
||||
let mut it = WalkDir::new(tmp.path()).into_iter();
|
||||
let mut it = WalkDir::new(tmp.path()).min_depth(1).into_iter();
|
||||
loop {
|
||||
let dent = match it.next().map(|x| x.unwrap()) {
|
||||
None => break,
|
||||
Some(dent) => dent,
|
||||
};
|
||||
let name = dent.file_name().into_string().unwrap();
|
||||
let name = dent.file_name().to_str().unwrap().to_owned();
|
||||
if name == "abc" {
|
||||
it.skip_current_dir();
|
||||
}
|
||||
@ -628,6 +587,30 @@ fn walk_dir_skip() {
|
||||
assert_eq!(got, vec!["abc", "bar", "baz", "foo"]); // missing xyz!
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_dir_filter() {
|
||||
let exp = td("foo", vec![
|
||||
tf("bar"),
|
||||
td("abc", vec![tf("fit")]),
|
||||
tf("faz"),
|
||||
]);
|
||||
let tmp = tmpdir();
|
||||
let tmp_path = tmp.path().to_path_buf();
|
||||
exp.create_in(tmp.path()).unwrap();
|
||||
let it = WalkDir::new(tmp.path()).min_depth(1)
|
||||
.into_iter()
|
||||
.filter_entry(move |d| {
|
||||
let n = d.file_name().to_string_lossy().into_owned();
|
||||
!d.file_type().is_dir()
|
||||
|| n.starts_with("f")
|
||||
|| d.path() == &*tmp_path
|
||||
});
|
||||
let mut got = it.map(|d| d.unwrap().file_name().to_str().unwrap().into())
|
||||
.collect::<Vec<String>>();
|
||||
got.sort();
|
||||
assert_eq!(got, vec!["bar", "faz", "foo"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qc_roundtrip() {
|
||||
fn p(exp: Tree) -> bool {
|
||||
|
Loading…
x
Reference in New Issue
Block a user