226 lines
5.7 KiB
Markdown
226 lines
5.7 KiB
Markdown
# [Walk.js](https://git.rootprojects.org/root/walk.js) (@root/walk)
|
|
|
|
Walk a directory recursively and handle each entity (files, directories, symlnks, etc).
|
|
|
|
(a port of Go's [`filepath.Walk`](https://golang.org/pkg/path/filepath/#Walk)
|
|
using Node.js v10+'s `fs.readdir`'s `withFileTypes` and ES 2021)
|
|
|
|
```js
|
|
await Walk.walk(pathname, walkFunc);
|
|
|
|
async function walkFunc(err, pathname, dirent) {
|
|
// err is failure to lstat a file or directory
|
|
// pathname is relative path, including the file or folder name
|
|
// dirent = { name, isDirectory(), isFile(), isSymbolicLink(), ... }
|
|
|
|
if (err) {
|
|
return false;
|
|
}
|
|
console.log(pathname);
|
|
}
|
|
```
|
|
|
|
# Table of Contents
|
|
|
|
- Install
|
|
- Usage
|
|
- CommonJS
|
|
- ES Modules
|
|
- API
|
|
- Walk.walk
|
|
- walkFunc
|
|
- Example: filter dotfiles
|
|
- Walk.create
|
|
- withFileStats
|
|
- sort (and filter)
|
|
- [Node walk in <50 Lines of Code](https://therootcompany.com/blog/fs-walk-for-node-js/)
|
|
- License (MPL-2.0)
|
|
|
|
# Install
|
|
|
|
```bash
|
|
npm install --save @root/walk
|
|
```
|
|
|
|
# Usage
|
|
|
|
You can use this with Node v12+ using Vanilla JS (CommonJS) or ES2021 (ES Modules).
|
|
|
|
## CommonJS (Vanilla JS / ES5)
|
|
|
|
```js
|
|
var Walk = require("@root/walk");
|
|
var path = require("path");
|
|
|
|
Walk.walk("./", walkFunc).then(function () {
|
|
console.log("Done");
|
|
});
|
|
|
|
// walkFunc must be async, or return a Promise
|
|
function walkFunc(err, pathname, dirent) {
|
|
if (err) {
|
|
// throw an error to stop walking
|
|
// (or return to ignore and keep going)
|
|
console.warn("fs stat error for %s: %s", pathname, err.message);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// return false to skip a directory
|
|
// (ex: skipping "dot file" directories)
|
|
if (dirent.isDirectory() && dirent.name.startsWith(".")) {
|
|
return Promise.resolve(false);
|
|
}
|
|
|
|
// fs.Dirent is a slimmed-down, faster version of fs.Stats
|
|
console.log("name:", dirent.name, "in", path.dirname(pathname));
|
|
// (only one of these will be true)
|
|
console.log("is file?", dirent.isFile());
|
|
console.log("is link?", dirent.isSymbolicLink());
|
|
|
|
return Promise.resolve();
|
|
}
|
|
```
|
|
|
|
## ECMAScript 2021 (ES Modules)
|
|
|
|
`@root/walk` can be used with async/await or Promises.
|
|
|
|
```js
|
|
import { walk } from "@root/walk";
|
|
import path from "path";
|
|
|
|
const walkFunc = async (err, pathname, dirent) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
if (dirent.isDirectory() && dirent.name.startsWith(".")) {
|
|
return false;
|
|
}
|
|
|
|
console.log("name:", dirent.name, "in", path.dirname(pathname));
|
|
};
|
|
|
|
await walk("./", walkFunc);
|
|
|
|
console.log("Done");
|
|
```
|
|
|
|
# API Documentation
|
|
|
|
## Walk.walk(pathname, walkFunc)
|
|
|
|
`Walk.walk` walks `pathname` (inclusive) and calls `walkFunc` for each file system entity.
|
|
|
|
It can be used with Promises:
|
|
|
|
```js
|
|
Walk.walk(pathname, promiseWalker).then(doMore);
|
|
```
|
|
|
|
Or with async / await:
|
|
|
|
```js
|
|
await Walk.walk(pathname, asyncWalker);
|
|
```
|
|
|
|
The behavior should exactly match Go's
|
|
[`filepath.Walk`](https://golang.org/pkg/path/filepath/#Walk) with a few exceptions:
|
|
|
|
- uses JavaScript Promises/async/await
|
|
- receives `dirent` rather than `lstat` (for performance, see `withFileStats`)
|
|
- optional parameters to change stat behavior and sort order
|
|
|
|
### walkFunc
|
|
|
|
Handles each directory entry
|
|
|
|
```js
|
|
async function walkFunc(err, pathname, dirent) {
|
|
// `err` is a file system stat error
|
|
// `pathname` is the full pathname, including the file name
|
|
// `dirent` is an fs.Dirent with a `name`, `isDirectory`, `isFile`, etc
|
|
return null;
|
|
}
|
|
```
|
|
|
|
## Walk.create(options)
|
|
|
|
Create a custom walker with these options:
|
|
|
|
- `withFileStats: true` walkFunc will receive fs.Stats[] from fs.lstat instead of fs.Dirent[]
|
|
- `sort: (entities) => entities.sort()` sort and/or filter entities before walking them
|
|
|
|
```js
|
|
const walk = Walk.create({
|
|
withFileStats: true,
|
|
sort: (entities) => entities.sort()),
|
|
});
|
|
```
|
|
|
|
## withFileStats
|
|
|
|
By default `walk` will use `fs.readdir(pathname, { withFileTypes: true })` which returns `fs.Dirent[]`,
|
|
which only has name and file type info, but is much faster when you don't need the complete `fs.Stats`.
|
|
|
|
Enable `withFileStats` to use get full `fs.Stats`. This will use `fs.readdir(pathname)` (returning `String[]`)
|
|
and then call `fs.lstat(pathname)` - including `mtime`, `birthtime`, `uid`, etc - right after.
|
|
|
|
```js
|
|
const walk = Walk.create({
|
|
withFileStats: true,
|
|
});
|
|
|
|
walk(".", async function (err, pathname, stat) {
|
|
console.log(stat.name, stat.uid, stat.birthtime, stat.isDirectory());
|
|
});
|
|
```
|
|
|
|
## sort (and filter)
|
|
|
|
Sometimes you want to give priority to walking certain directories first.
|
|
|
|
The `sort` option allows you to specify a funciton that modifies the `fs.Dirent[]` entities (default) or `String[]` filenames (`withFileStats: true`).
|
|
|
|
Since you must return the sorted array, you can also filter here if you'd prefer.
|
|
|
|
```js
|
|
const byNameWithoutDotFiles = (entities) => {
|
|
// sort by name
|
|
// filter dot files
|
|
return entities
|
|
.sort((a, b) => {
|
|
if (a.name > b.name) {
|
|
return 1;
|
|
}
|
|
if (a.name < b.name) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
})
|
|
.filter((ent) => !ent.name.startsWith("."));
|
|
};
|
|
|
|
const walk = Walk.create({ sort: byNameWithoutDotFiles });
|
|
|
|
walk(".", async function (err, pathname, stat) {
|
|
// each directories contents will be listed alphabetically
|
|
console.log(pathname);
|
|
});
|
|
```
|
|
|
|
Note: this gets the result of `fs.readdir()`. If `withFileStats` is `true` you will get a `String[]` of filenames - because this hapens BEFORE `fs.lstat()` is called - otherwise you will get `fs.Dirent[]`.
|
|
|
|
# node walk in 50 lines of code
|
|
|
|
If you're like me and you hate dependencies,
|
|
here's the bare minimum node fs walk function:
|
|
|
|
See [snippet.js](/snippet.js) or <https://therootcompany.com/blog/fs-walk-for-node-js/>.
|
|
|
|
# License
|
|
|
|
The main module, as published to NPM, is licensed the MPL-2.0.
|
|
|
|
The ~50 line snippet is licensed CC0-1.0 (Public Domain).
|