commit 3a125de3a5692c1afe4f20bdbd878f572530bae8 Author: AJ ONeal Date: Tue Dec 8 17:28:11 2020 -0700 initial commit diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..56128ac --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "env": { + "es2021": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "rules": {} +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..de753c5 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "printWidth": 100 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..3625c3b --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# 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`). + +```js +await Walk.walk(rootpath, walkFunc); +``` + +# Example + +```js +import Walk from "walk"; + +Walk.walk("./", function (err, pathname, dirent) { + if (err) { + throw err; + } + + // ignore dot files + if (dirent.name.startsWith(".")) { + return Walk.skipDir; + } +}); +``` + +# API Documentation + +`Walk.walk` walks `rootpath` (inclusive) and calls `walkFunc` for each file system entry. + +It can be used with Promises: + +```js +Walk.walk(rootpath, promiseWalker).then(doMore); +``` + +Or with async / await: + +```js +await Walk.walk(rootpath, asyncWalker); +``` + +The behavior should exactly match Go's +[`filepath.Walk`](https://golang.org/pkg/path/filepath/#Walk) with 3 exceptions: + +- uses JavaScript Promises/async/await +- receives `dirent` rather than `lstat` (for performance) + + + +## walkFunc + +Handles each directory entry + +```js +function walker(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; +} +``` + + diff --git a/bin/walk.mjs b/bin/walk.mjs new file mode 100644 index 0000000..39c57c0 --- /dev/null +++ b/bin/walk.mjs @@ -0,0 +1,24 @@ +import Walk from "../walk.mjs"; +import path from "path"; + +var rootpath = process.argv[2] || "."; + +Walk.walk(rootpath, async function (err, pathname, dirent) { + if (err) { + throw err; + } + + var entType; + if (dirent.isDirectory()) { + entType = " dir"; + } else if (dirent.isFile()) { + entType = "file"; + } else if (dirent.isSymbolicLink()) { + entType = "link"; + } else { + entType = "----"; + } + console.info("[%s] %s", entType, path.dirname(pathname), dirent.name); +}).catch(function (err) { + console.error(err.stack); +}); diff --git a/create.mjs b/create.mjs new file mode 100644 index 0000000..9dd945e --- /dev/null +++ b/create.mjs @@ -0,0 +1,106 @@ +// 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; diff --git a/package.json b/package.json new file mode 100644 index 0000000..007b146 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "walk", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "MIT" +} diff --git a/walk.mjs b/walk.mjs new file mode 100644 index 0000000..52fe01b --- /dev/null +++ b/walk.mjs @@ -0,0 +1,88 @@ +// 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;