v1.0.0: fs.walk for node
This commit is contained in:
parent
3a125de3a5
commit
618d145a22
70
README.md
70
README.md
|
@ -1,42 +1,90 @@
|
||||||
# Walk.js (@root/walk)
|
# Walk.js (@root/walk)
|
||||||
|
|
||||||
A port of Go's [`filepath.Walk`](https://golang.org/pkg/path/filepath/#Walk) for Node.js v10+ (which introduced `fs.readdir` `withFileTypes`).
|
Walk a directory recursively and handle each entity (files, directories, symlnks, etc).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await Walk.walk(rootpath, walkFunc);
|
await Walk.walk(pathname, walkFunc);
|
||||||
|
|
||||||
|
function walkFunc(err, pathname, dirent) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Example
|
This is a port of Go's [`filepath.Walk`](https://golang.org/pkg/path/filepath/#Walk)
|
||||||
|
for Node.js v10+ (which introduced `fs.readdir` `withFileTypes`) and ES 2021.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
You can use this with Node v12+ using Vanilla JS or ES2021.
|
||||||
|
|
||||||
|
## ES 2021 Modules
|
||||||
|
|
||||||
|
`@root/walk` can be used with async/await or Promises.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import Walk from "walk";
|
import { walk } from "@root/walk";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
Walk.walk("./", function (err, pathname, dirent) {
|
await walk("./", async (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return false to skip a directory
|
||||||
|
// (ex: skipping "dot files")
|
||||||
|
if (dirent.isDirectory() && dirent.name.startsWith(".")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fs.Dirent is a slimmed-down, faster version of fs.Stat
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Done");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Vanilla JS (ES5)
|
||||||
|
|
||||||
|
```js
|
||||||
|
var Walk = require("@root/walk");
|
||||||
|
var path = require("path");
|
||||||
|
|
||||||
|
Walk.walk("./", function walkFunc(err, pathname, dirent) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore dot files
|
if (dirent.isDirectory() && dirent.name.startsWith(".")) {
|
||||||
if (dirent.name.startsWith(".")) {
|
return Promise.resolve(false);
|
||||||
return Walk.skipDir;
|
|
||||||
}
|
}
|
||||||
|
console.log("name:", dirent.name, "in", path.dirname(pathname));
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}).then(function () {
|
||||||
|
console.log("Done");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
# API Documentation
|
# API Documentation
|
||||||
|
|
||||||
`Walk.walk` walks `rootpath` (inclusive) and calls `walkFunc` for each file system entry.
|
`Walk.walk` walks `pathname` (inclusive) and calls `walkFunc` for each file system entry.
|
||||||
|
|
||||||
It can be used with Promises:
|
It can be used with Promises:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Walk.walk(rootpath, promiseWalker).then(doMore);
|
Walk.walk(pathname, promiseWalker).then(doMore);
|
||||||
```
|
```
|
||||||
|
|
||||||
Or with async / await:
|
Or with async / await:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await Walk.walk(rootpath, asyncWalker);
|
await Walk.walk(pathname, asyncWalker);
|
||||||
```
|
```
|
||||||
|
|
||||||
The behavior should exactly match Go's
|
The behavior should exactly match Go's
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Walk from "../walk.mjs";
|
import Walk from "../index.js";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
var rootpath = process.argv[2] || ".";
|
var rootpath = process.argv[2] || ".";
|
||||||
|
@ -8,6 +8,10 @@ Walk.walk(rootpath, async function (err, pathname, dirent) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dirent.name.startsWith(".")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var entType;
|
var entType;
|
||||||
if (dirent.isDirectory()) {
|
if (dirent.isDirectory()) {
|
||||||
entType = " dir";
|
entType = " dir";
|
||||||
|
@ -18,7 +22,7 @@ Walk.walk(rootpath, async function (err, pathname, dirent) {
|
||||||
} else {
|
} else {
|
||||||
entType = "----";
|
entType = "----";
|
||||||
}
|
}
|
||||||
console.info("[%s] %s", entType, path.dirname(pathname), dirent.name);
|
console.info("[%s] %s", entType, path.dirname(path.resolve(pathname)), dirent.name);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
});
|
});
|
|
@ -0,0 +1,110 @@
|
||||||
|
// a port of Go's filepath.Walk
|
||||||
|
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import { skipDir } from "./walk.js";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
var Walk = {};
|
||||||
|
var _withFileTypes = { withFileTypes: true };
|
||||||
|
var _noopts = {};
|
||||||
|
|
||||||
|
function pass(err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipOrThrow(err) {
|
||||||
|
if (!(err instanceof Error)) {
|
||||||
|
// go
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (false === err || skipDir === err) {
|
||||||
|
// skip
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// throw
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
Walk.skipDir = skipDir;
|
||||||
|
|
||||||
|
Walk.create = function (opts) {
|
||||||
|
async function _walk(root, walker) {
|
||||||
|
if (!opts) {
|
||||||
|
opts = _noopts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case of the very first item, root
|
||||||
|
var err;
|
||||||
|
var stat = await fs.lstat(root).catch(function (e) {
|
||||||
|
err = e;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (stat) {
|
||||||
|
stat.name = path.basename(path.resolve(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* similar to function in main walk loop */
|
||||||
|
var uerr = await walker(err, root, stat).then(pass).catch(pass);
|
||||||
|
if (skipOrThrow(uerr) || err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === opts.withFileTypes) {
|
||||||
|
stat = stat.name;
|
||||||
|
}
|
||||||
|
if (opts.sort) {
|
||||||
|
stat = (opts.sort([stat]) || [])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat && stat.isDirectory()) {
|
||||||
|
return _walkHelper(root, stat, walker, opts);
|
||||||
|
}
|
||||||
|
/* end */
|
||||||
|
|
||||||
|
async function _walkHelper(root, prevEnt, walker, opts) {
|
||||||
|
var err;
|
||||||
|
var _readdirOpts;
|
||||||
|
if (false !== opts.withFileTypes) {
|
||||||
|
_readdirOpts = _withFileTypes;
|
||||||
|
}
|
||||||
|
var dirents = await fs.readdir(root, _readdirOpts).catch(function (e) {
|
||||||
|
err = e;
|
||||||
|
});
|
||||||
|
if (err) {
|
||||||
|
return walker(err, root, prevEnt);
|
||||||
|
}
|
||||||
|
if (opts.sort) {
|
||||||
|
dirents = opts.sort(dirents);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirent;
|
||||||
|
var pathname;
|
||||||
|
for (dirent of dirents) {
|
||||||
|
if ("string" === typeof dirent) {
|
||||||
|
pathname = path.join(root, dirent);
|
||||||
|
dirent = await fs.lstat(path.join(root)).catch(function (e) {
|
||||||
|
err = e;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pathname = path.join(root, dirent.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* main inner loop */
|
||||||
|
err = await walker(err, pathname, dirent).then(pass).catch(pass);
|
||||||
|
if (skipOrThrow(err)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "walk does not follow symbolic links"
|
||||||
|
if (dirent.isDirectory()) {
|
||||||
|
await _walkHelper(path.join(root, dirent.name), dirent, walker, opts);
|
||||||
|
}
|
||||||
|
/* end */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _walk;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Walk;
|
106
create.mjs
106
create.mjs
|
@ -1,106 +0,0 @@
|
||||||
// a port of Go's filepath.Walk
|
|
||||||
|
|
||||||
import { promises as fs } from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
var Walk = {};
|
|
||||||
var _withFileTypes = { withFileTypes: true };
|
|
||||||
var _skipDir = new Error("skip this directory");
|
|
||||||
var _noopts = {};
|
|
||||||
|
|
||||||
function pass(err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
function skipOrThrow(err) {
|
|
||||||
if (!(err instanceof Error)) {
|
|
||||||
// go
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_skipDir === err) {
|
|
||||||
// skip
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// throw
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Walk.skipDir = _skipDir;
|
|
||||||
|
|
||||||
Walk.walk = async function _walk(root, walker, opts) {
|
|
||||||
if (!opts) {
|
|
||||||
opts = _noopts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case of the very first item, root
|
|
||||||
var err;
|
|
||||||
var stat = await fs.lstat(root).catch(function (e) {
|
|
||||||
err = e;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
if (stat) {
|
|
||||||
stat.name = path.basename(path.resolve(root));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* similar to function in main walk loop */
|
|
||||||
var uerr = await walker(err, root, stat).then(pass).catch(pass);
|
|
||||||
if (skipOrThrow(uerr) || err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === opts.withFileTypes) {
|
|
||||||
stat = stat.name;
|
|
||||||
}
|
|
||||||
if (opts.sort) {
|
|
||||||
stat = (opts.sort([stat]) || [])[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stat && stat.isDirectory()) {
|
|
||||||
return _walkHelper(root, stat, walker, opts);
|
|
||||||
}
|
|
||||||
/* end */
|
|
||||||
};
|
|
||||||
|
|
||||||
async function _walkHelper(root, prevEnt, walker, opts) {
|
|
||||||
var err;
|
|
||||||
var _readdirOpts;
|
|
||||||
if (false !== opts.withFileTypes) {
|
|
||||||
_readdirOpts = _withFileTypes;
|
|
||||||
}
|
|
||||||
var dirents = await fs.readdir(root, _readdirOpts).catch(function (e) {
|
|
||||||
err = e;
|
|
||||||
});
|
|
||||||
if (err) {
|
|
||||||
return walker(err, root, prevEnt);
|
|
||||||
}
|
|
||||||
if (opts.sort) {
|
|
||||||
dirents = opts.sort(dirents);
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirent;
|
|
||||||
var pathname;
|
|
||||||
for (dirent of dirents) {
|
|
||||||
if ("string" === typeof dirent) {
|
|
||||||
pathname = path.join(root, dirent);
|
|
||||||
dirent = await fs.lstat(path.join(root)).catch(function (e) {
|
|
||||||
err = e;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
pathname = path.join(root, dirent.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* main inner loop */
|
|
||||||
err = await walker(err, pathname, dirent).then(pass).catch(pass);
|
|
||||||
if (skipOrThrow(err)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "walk does not follow symbolic links"
|
|
||||||
if (dirent.isDirectory()) {
|
|
||||||
await _walkHelper(path.join(root, dirent.name), dirent, walker, opts);
|
|
||||||
}
|
|
||||||
/* end */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Walk;
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "@root/walk",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
23
package.json
23
package.json
|
@ -1,11 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "walk",
|
"name": "@root/walk",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "fs.walk for node (as a port of Go's filepath.Walk)",
|
||||||
|
"homepage": "https://git.rootprojects.org/root/walk.js",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"walk.js"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
"repository": {
|
||||||
"license": "MIT"
|
"type": "git",
|
||||||
|
"url": "https://git.rootprojects.org/root/walk.js.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"fs",
|
||||||
|
"walk",
|
||||||
|
"filepath",
|
||||||
|
"os"
|
||||||
|
],
|
||||||
|
"author": "AJ ONeal <aj@therootcompany.com> (https://rootprojects.org/)",
|
||||||
|
"license": "MPL-2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const _withFileTypes = { withFileTypes: true };
|
||||||
|
const skipDir = new Error("skip this directory");
|
||||||
|
const pass = (err) => err;
|
||||||
|
|
||||||
|
// a port of Go's filepath.Walk
|
||||||
|
const walk = async (root, walkFunc, _dirent) => {
|
||||||
|
let err;
|
||||||
|
|
||||||
|
// special case of the very first run
|
||||||
|
if (!_dirent) {
|
||||||
|
let _name = path.basename(path.resolve(root));
|
||||||
|
_dirent = await fs.lstat(root).catch(pass);
|
||||||
|
if (_dirent instanceof Error) {
|
||||||
|
err = _dirent;
|
||||||
|
} else {
|
||||||
|
_dirent.name = _name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the user-supplied function and either skip, bail, or continue
|
||||||
|
err = await walkFunc(err, root, _dirent).catch(pass);
|
||||||
|
if (false === err || skipDir === err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err instanceof Error) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "walk does not follow symbolic links"
|
||||||
|
if (!_dirent.isDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let result = await fs.readdir(root, _withFileTypes).catch(pass);
|
||||||
|
if (result instanceof Error) {
|
||||||
|
return walkFunc(result, root, _dirent);
|
||||||
|
}
|
||||||
|
for (let dirent of result) {
|
||||||
|
await walk(path.join(root, dirent.name), walkFunc, dirent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
walk,
|
||||||
|
skipDir,
|
||||||
|
};
|
88
walk.mjs
88
walk.mjs
|
@ -1,88 +0,0 @@
|
||||||
// a port of Go's filepath.Walk
|
|
||||||
|
|
||||||
import { promises as fs } from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
var Walk = {};
|
|
||||||
var _withFileTypes = { withFileTypes: true };
|
|
||||||
var _skipDir = new Error("skip this directory");
|
|
||||||
|
|
||||||
function pass(err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
function skipOrThrow(err) {
|
|
||||||
if (!(err instanceof Error)) {
|
|
||||||
// go
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_skipDir === err) {
|
|
||||||
// skip
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// throw
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
Walk.skipDir = _skipDir;
|
|
||||||
|
|
||||||
Walk.walk = async function _walk(root, walker) {
|
|
||||||
// Special case of the very first item, root
|
|
||||||
var err;
|
|
||||||
var stat = await fs.lstat(root).catch(function (e) {
|
|
||||||
err = e;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
stat.name = path.basename(path.resolve(root));
|
|
||||||
|
|
||||||
/* similar to function in main walk loop */
|
|
||||||
var uerr = await walker(err, root, stat).then(pass).catch(pass);
|
|
||||||
if (skipOrThrow(uerr) || err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
return _walkHelper(root, stat, walker);
|
|
||||||
}
|
|
||||||
/* end */
|
|
||||||
};
|
|
||||||
|
|
||||||
async function _walkHelper(root, prevEnt, walker) {
|
|
||||||
var err;
|
|
||||||
var dirents = await fs.readdir(root, _withFileTypes).catch(function (e) {
|
|
||||||
err = e;
|
|
||||||
});
|
|
||||||
if (err) {
|
|
||||||
return walker(err, root, prevEnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirent;
|
|
||||||
var pathname;
|
|
||||||
var _name;
|
|
||||||
for (dirent of dirents) {
|
|
||||||
if ("string" === typeof dirent) {
|
|
||||||
_name = dirent;
|
|
||||||
pathname = path.join(root, _name);
|
|
||||||
dirent = await fs.lstat(pathname).catch(function (e) {
|
|
||||||
err = e;
|
|
||||||
});
|
|
||||||
dirent.name = _name;
|
|
||||||
} else {
|
|
||||||
pathname = path.join(root, dirent.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* main inner loop */
|
|
||||||
err = await walker(err, pathname, dirent).then(pass).catch(pass);
|
|
||||||
if (skipOrThrow(err)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "walk does not follow symbolic links"
|
|
||||||
if (dirent.isDirectory()) {
|
|
||||||
await _walkHelper(path.join(root, dirent.name), dirent, walker);
|
|
||||||
}
|
|
||||||
/* end */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Walk;
|
|
Loading…
Reference in New Issue