BurntSushi / walkdir

Rust library for walking directories recursively.
The Unlicense
1.21k stars 106 forks source link

Example code to skip hidden files breaks `WalkDir::new(".")` #154

Open kpcyrd opened 2 years ago

kpcyrd commented 2 years ago

This is currently recommended to skip hidden directories in the docs:

use walkdir::{DirEntry, WalkDir};

fn is_hidden(entry: &DirEntry) -> bool {
    entry.file_name()
         .to_str()
         .map(|s| s.starts_with("."))
         .unwrap_or(false)
}

for entry in WalkDir::new("foo")
                     .into_iter()
                     .filter_entry(|e| !is_hidden(e)) {
    println!("{}", entry?.path().display());
}

This works well for foo, but breaks if . is provided instead (playground):

use walkdir::{WalkDir, DirEntry}; // 2.3.2

fn matches(entry: &DirEntry) -> bool {
    let hidden = entry.file_name()
         .to_str()
         .map(|s| s.starts_with("."))
         .unwrap_or(false);

    if hidden {
        println!("Skipping because hidden: {:?}", entry);
    }

    !hidden
}

fn walk(path: &str) {
    println!("Walking: {:?}", path);
    let walker = WalkDir::new(path).into_iter();
    for entry in walker.filter_entry(matches) {
        println!("{:?}", entry);
    }
}

fn main() {
    walk(".");
    walk("/etc");
}

Output:

``` Walking: "." Skipping because hidden: DirEntry(".") Walking: "/etc" Ok(DirEntry("/etc")) Ok(DirEntry("/etc/issue.net")) Ok(DirEntry("/etc/bindresvport.blacklist")) Ok(DirEntry("/etc/rc1.d")) Ok(DirEntry("/etc/hostname")) Ok(DirEntry("/etc/xattr.conf")) Ok(DirEntry("/etc/resolv.conf")) Ok(DirEntry("/etc/pam.conf")) Ok(DirEntry("/etc/mke2fs.conf")) Ok(DirEntry("/etc/e2scrub.conf")) Ok(DirEntry("/etc/update-motd.d")) Ok(DirEntry("/etc/update-motd.d/50-motd-news")) Ok(DirEntry("/etc/update-motd.d/10-help-text")) Ok(DirEntry("/etc/update-motd.d/00-header")) Ok(DirEntry("/etc/update-motd.d/60-unminimize")) Ok(DirEntry("/etc/terminfo")) Ok(DirEntry("/etc/terminfo/README")) Ok(DirEntry("/etc/alternatives")) Ok(DirEntry("/etc/alternatives/w")) Ok(DirEntry("/etc/alternatives/README")) Ok(DirEntry("/etc/alternatives/pager")) Ok(DirEntry("/etc/alternatives/awk")) Ok(DirEntry("/etc/alternatives/nawk")) Ok(DirEntry("/etc/alternatives/rmt")) Ok(DirEntry("/etc/alternatives/lzma")) Ok(DirEntry("/etc/alternatives/rsh")) Ok(DirEntry("/etc/alternatives/cpp")) Ok(DirEntry("/etc/alternatives/fakeroot")) Ok(DirEntry("/etc/alternatives/lzfgrep")) Ok(DirEntry("/etc/alternatives/cc")) Ok(DirEntry("/etc/alternatives/lzcat")) Ok(DirEntry("/etc/alternatives/c++")) Ok(DirEntry("/etc/alternatives/lzegrep")) Ok(DirEntry("/etc/alternatives/c99")) Ok(DirEntry("/etc/alternatives/lzgrep")) Ok(DirEntry("/etc/alternatives/unlzma")) Ok(DirEntry("/etc/alternatives/lzmore")) Ok(DirEntry("/etc/alternatives/lzless")) Ok(DirEntry("/etc/alternatives/lzdiff")) Ok(DirEntry("/etc/alternatives/rcp")) Ok(DirEntry("/etc/alternatives/c89")) Ok(DirEntry("/etc/alternatives/rlogin")) Ok(DirEntry("/etc/alternatives/lzcmp")) Ok(DirEntry("/etc/alternatives/pinentry")) Ok(DirEntry("/etc/ld.so.cache")) Ok(DirEntry("/etc/networks")) Ok(DirEntry("/etc/profile")) Ok(DirEntry("/etc/debconf.conf")) Ok(DirEntry("/etc/security")) Ok(DirEntry("/etc/security/limits.conf")) Ok(DirEntry("/etc/security/access.conf")) Ok(DirEntry("/etc/security/sepermit.conf")) Ok(DirEntry("/etc/security/faillock.conf")) Ok(DirEntry("/etc/security/opasswd")) Ok(DirEntry("/etc/security/namespace.init")) Ok(DirEntry("/etc/security/namespace.d")) Ok(DirEntry("/etc/security/limits.d")) Ok(DirEntry("/etc/security/pam_env.conf")) Ok(DirEntry("/etc/security/group.conf")) Ok(DirEntry("/etc/security/time.conf")) Ok(DirEntry("/etc/security/namespace.conf")) Skipping because hidden: DirEntry("/etc/.pwd.lock") Ok(DirEntry("/etc/gai.conf")) Ok(DirEntry("/etc/dpkg")) Ok(DirEntry("/etc/dpkg/dpkg.cfg")) Ok(DirEntry("/etc/dpkg/origins")) Ok(DirEntry("/etc/dpkg/origins/ubuntu")) Ok(DirEntry("/etc/dpkg/origins/default")) Ok(DirEntry("/etc/dpkg/origins/debian")) Ok(DirEntry("/etc/dpkg/dpkg.cfg.d")) Ok(DirEntry("/etc/dpkg/dpkg.cfg.d/excludes")) Ok(DirEntry("/etc/dpkg/dpkg.cfg.d/docker-apt-speedup")) Ok(DirEntry("/etc/dpkg/dpkg.cfg.d/pkg-config-hook-config")) Ok(DirEntry("/etc/dpkg/shlibs.override")) Ok(DirEntry("/etc/dpkg/shlibs.default")) Ok(DirEntry("/etc/rc3.d")) Ok(DirEntry("/etc/fstab")) Ok(DirEntry("/etc/gshadow")) Ok(DirEntry("/etc/sysctl.conf")) Ok(DirEntry("/etc/rc2.d")) Ok(DirEntry("/etc/selinux")) Ok(DirEntry("/etc/selinux/semanage.conf")) Ok(DirEntry("/etc/ld.so.conf.d")) Ok(DirEntry("/etc/ld.so.conf.d/libc.conf")) Ok(DirEntry("/etc/ld.so.conf.d/x86_64-linux-gnu.conf")) Ok(DirEntry("/etc/ld.so.conf.d/fakeroot-x86_64-linux-gnu.conf")) Ok(DirEntry("/etc/os-release")) Ok(DirEntry("/etc/libaudit.conf")) Ok(DirEntry("/etc/login.defs")) Ok(DirEntry("/etc/skel")) Skipping because hidden: DirEntry("/etc/skel/.bash_logout") Skipping because hidden: DirEntry("/etc/skel/.profile") Skipping because hidden: DirEntry("/etc/skel/.bashrc") Ok(DirEntry("/etc/shells")) Ok(DirEntry("/etc/rc4.d")) Ok(DirEntry("/etc/cron.d")) [...] ```

I'm wondering if walkdir::FilterEntry could be extended with a method to make the initial path skip the filter. Alternatively is_hidden could be enhanced to check for . and .. since filter_entry should never try these unless the path was the initial folder.

The example code in the docs should be updated to include this change.

Thanks!

gifnksm commented 1 year ago

For reference, using entry.path().file_name() instead of entry.file_name() seems to work.

fn is_hidden(entry: &walkdir::DirEntry) -> bool {
    entry
        .path()
        .file_name()
        .and_then(|file_name| file_name.to_str().map(|s| s.starts_with('.')))
        .unwrap_or(false)
}

Path::new(".").file_name() and Path::new("..").file_name() return None.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=264e8981f52264894f80691bbb0969d4

In this case, walkdir::DirEntry::file_name returns the entire path, which is incorrectly determined to be a hidden file.

https://github.com/BurntSushi/walkdir/blob/abf3a15887758e0af54ebca827c7b6f8b311cb45/src/dent.rs#L166-L168