v1.0.0: fs.walk for node

This commit is contained in:
AJ ONeal 2020-12-09 04:10:56 -07:00
parent 3a125de3a5
commit 618d145a22
9 changed files with 250 additions and 211 deletions

View File

@ -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

View File

@ -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);
}); });

110
create.js Normal file
View File

@ -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;

View File

@ -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;

3
index.js Normal file
View File

@ -0,0 +1,3 @@
import Walk from "./walk.js";
export default Walk;

5
package-lock.json generated Normal file
View File

@ -0,0 +1,5 @@
{
"name": "@root/walk",
"version": "1.0.0",
"lockfileVersion": 1
}

View File

@ -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"
} }

48
walk.js Normal file
View File

@ -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,
};

View File

@ -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;