diff --git a/README.md b/README.md index 8fa1579..8718322 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ 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); @@ -10,8 +13,16 @@ function walkFunc(err, pathname, dirent) { } ``` -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. +Where + +- `err` is a failure to lstat a file or directory +- `pathname` may be relative +- `dirent` is an `fs.Dirent` that has + - `dirent.name` + - `dirent.isFile()` + - `dirent.isDirectory()` + - `dirent.isSymbolicLink()` + - etc # Examples diff --git a/bin/walk.js b/bin/walk.js index 9cabda7..a506554 100644 --- a/bin/walk.js +++ b/bin/walk.js @@ -1,16 +1,27 @@ -import Walk from "../index.js"; import path from "path"; +//import { walk } from "../index.js"; + +import Walk from "../index.js"; +var walk = Walk.create({ + sort: function (ents) { + return ents.filter(function (ent) { + return !ent.name.startsWith("."); + }); + }, +}); var rootpath = process.argv[2] || "."; -Walk.walk(rootpath, async function (err, pathname, dirent) { +walk(rootpath, async function (err, pathname, dirent) { if (err) { throw err; } + /* if (dirent.name.startsWith(".")) { return false; } + */ var entType; if (dirent.isDirectory()) { diff --git a/create.js b/create.js index a47630f..e550194 100644 --- a/create.js +++ b/create.js @@ -1,10 +1,9 @@ // a port of Go's filepath.Walk import { promises as fs } from "fs"; -import { skipDir } from "./walk.js"; +import Walk from "./walk.js"; import path from "path"; -var Walk = {}; var _withFileTypes = { withFileTypes: true }; var _noopts = {}; @@ -12,97 +11,64 @@ 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; + if (!opts) { + opts = _noopts; + } + + // a port of Go's filepath.Walk + const _walk = async (pathname, walkFunc, _dirent) => { + let err; + + // special case of the very first run + if (!_dirent) { + _dirent = pathname; } - // 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)); + // the first run, or if false === withFileTypes + if ("string" === typeof _dirent) { + let _name = path.basename(path.resolve(pathname)); + _dirent = await fs.lstat(pathname).catch(pass); + if (_dirent instanceof Error) { + err = _dirent; + } else { + _dirent.name = _name; + } } - /* similar to function in main walk loop */ - var uerr = await walker(err, root, stat).then(pass).catch(pass); - if (skipOrThrow(uerr) || err) { + // run the user-supplied function and either skip, bail, or continue + err = await walkFunc(err, pathname, _dirent).catch(pass); + if (false === err || Walk.skipDir === err) { + return; + } + if (err instanceof Error) { + throw err; + } + + // "walk does not follow symbolic links" + if (!_dirent || !_dirent.isDirectory()) { return; } - if (false === opts.withFileTypes) { - stat = stat.name; + // lightweight dirents or full lstat + let _readdirOpts; + if (!opts.withFileStats) { + _readdirOpts = _withFileTypes; + } + + // TODO check if the error is "not a directory" + // (and thus allow false === opts.withFileTypes) + let result = await fs.readdir(pathname, _readdirOpts).catch(pass); + if (result instanceof Error) { + return walkFunc(result, pathname, _dirent); } if (opts.sort) { - stat = (opts.sort([stat]) || [])[0]; + result = opts.sort(result); } - - if (stat && stat.isDirectory()) { - return _walkHelper(root, stat, walker, opts); + for (let entity of result) { + await _walk(path.join(pathname, entity.name || entity), walkFunc, entity); } - /* 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; }; diff --git a/index.js b/index.js index 6361d57..50f7f40 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,6 @@ import Walk from "./walk.js"; +import Walk2 from "./create.js"; + +Walk.create = Walk2.create; export default Walk; diff --git a/package.json b/package.json index 0acccd0..46498e9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "main": "index.js", "type": "module", "files": [ - "walk.js" + "walk.js", + "create.js" ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/snippet.js b/snippet.js new file mode 100644 index 0000000..872043c --- /dev/null +++ b/snippet.js @@ -0,0 +1,55 @@ +// ECMAScript 2021 +// (or Vanilla JS) +import { promises as fs } from "fs"; +// or let fs = require("fs").promises; +import path from "path"; +// or let path = require("path"); + +// a port of Go's filepath.Walk +async function walk(pathname, walkFunc, _dirent) { + let err; + function pass(e) { + return e; + } + + // special case of the very first run + if (!_dirent) { + let _name = path.basename(path.resolve(pathname)); + _dirent = await fs.lstat(pathname).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, pathname, _dirent).catch(pass); + if (false === err) { + return; + } + if (err instanceof Error) { + throw err; + } + + // "walk does not follow symbolic links" + if (!_dirent.isDirectory()) { + return; + } + let result = await fs.readdir(pathname, { withFileTypes: true }).catch(pass); + if (result instanceof Error) { + return walkFunc(result, pathname, _dirent); + } + for (let dirent of result) { + await walk(path.join(pathname, dirent.name), walkFunc, dirent); + } +} + +walk("./", function (err, pathname, dirent) { + if (dirent.name.startsWith(".")) { + return Promise.resolve(false); + } + + console.log(path.resolve(pathname)); + return Promise.resolve(true); +}); diff --git a/walk.js b/walk.js index 5402f57..6ad60c2 100644 --- a/walk.js +++ b/walk.js @@ -6,13 +6,13 @@ const skipDir = new Error("skip this directory"); const pass = (err) => err; // a port of Go's filepath.Walk -const walk = async (root, walkFunc, _dirent) => { +const walk = async (pathname, 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); + let _name = path.basename(path.resolve(pathname)); + _dirent = await fs.lstat(pathname).catch(pass); if (_dirent instanceof Error) { err = _dirent; } else { @@ -21,7 +21,7 @@ const walk = async (root, walkFunc, _dirent) => { } // run the user-supplied function and either skip, bail, or continue - err = await walkFunc(err, root, _dirent).catch(pass); + err = await walkFunc(err, pathname, _dirent).catch(pass); if (false === err || skipDir === err) { return; } @@ -33,12 +33,12 @@ const walk = async (root, walkFunc, _dirent) => { if (!_dirent.isDirectory()) { return; } - let result = await fs.readdir(root, _withFileTypes).catch(pass); + let result = await fs.readdir(pathname, _withFileTypes).catch(pass); if (result instanceof Error) { - return walkFunc(result, root, _dirent); + return walkFunc(result, pathname, _dirent); } for (let dirent of result) { - await walk(path.join(root, dirent.name), walkFunc, dirent); + await walk(path.join(pathname, dirent.name), walkFunc, dirent); } };