Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optionally invoke actions on the top-level directory #11

Merged
merged 3 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![commit-test](https://github.com/bmacnaughton/action-walk/workflows/commit-test/badge.svg)

Minimal utility to walk directory trees performing actions on each directory
Framework to walk directory trees performing actions on each directory
entry. `action-walk` has no external production dependencies and has only one
strong opinion - don't presume anything about why the directory tree is being
walked.
Expand All @@ -11,9 +11,15 @@ No presumptions means that this does little more than walk the tree. There
are two options to facilitate implementing your code on top of `action-walk`.
If the boolean option `stat` is truthy `action-walk` will execute `fs.stat`
on the entry and pass that to you action handler. If the option `own` is
present `action-walk` will pass that to the action functions in a context
present `action-walk` will pass it to the action functions in a context
object.

There is one additional option `includeTopLevel`. By default, `action-walk` does
not call the action functions on the directory passed to `action-walk`; it just
starts walking that directory. If `includeTopLevel` is truthy, `action-walk` will
call the directory action function on the top level directory. This likely should
have been the default but it's not as it's a breaking change.

### usage

`action-walk` should run on any version of node that supports the `node:` prefix
Expand Down Expand Up @@ -43,7 +49,7 @@ function fileAction (path, context) {
own.total += stat.size;
}

const own = {total: 0, skipDirs: ['node_modules']};
const own = {total: 0, skipDirs: ['node_modules', '.git']};
const options = {
dirAction,
fileAction,
Expand All @@ -57,7 +63,7 @@ walk('.', options)
});

// executed in the action-walk package root it will print something like
// total bytes in "." 14778
// total bytes in "." 109558
```

see `test/basics.test.js` or `bin/walk.js` for other examples.
Expand All @@ -75,12 +81,13 @@ recurse into the directory.
- `stat` - if `'lstat'` call `fs.lstat` on the entry and add it to the action context as
the `stat` property. if otherwise truthy use `fs.stat`.
- `own` - add this to the action context. it is your context for the action functions.
- `includeTopLevel` - if truthy, the first call to `dirAction` will be for the the directory argument. if falsey, the first call to `dirAction` will be for the first entry in the directory.

It's possible to call `walk()` with no options but probably not useful unless
all you're wanting to do is seed the disk cache with directory entries. The
action functions are where task-specific work is done.

Each of the action function (`dirAction`, `fileAction`, `linkAction`, `otherAction`) is
Each of the action functions (`dirAction`, `fileAction`, `linkAction`, `otherAction`) is
called with two arguments:
- `filepath` for the entry starting with `directory`, e.g., if `directory` is `test` and
the entry is `basics.test.js` then `filepath` will be `test/basics.test.js`.
Expand Down
78 changes: 50 additions & 28 deletions action-walk.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,67 @@ async function walk (dir, options = {}) {
otherAction = async (filepath, ctx) => options.otherAction(filepath, ctx);
}

async function dispatchAction (dir, dirent) {
let entry = path.join(dir, dirent.name);
// path.join refuses to start a path with '.'
if ((dir === '.' && entry) || dir.startsWith(`.${sep}`)) {
entry = `.${sep}${entry}`;
}
const ctx = { dirent, stack };
if (options.own) {
ctx.own = options.own;
}
if (stat) {
ctx.stat = await fsp[stat](entry);
}
if (dirent.isDirectory()) {
if (await dirAction(entry, ctx) !== 'skip') {
await walker(entry);
}
} else if (dirent.isFile()) {
fileAction && await fileAction(entry, ctx);
} else if (dirent.isSymbolicLink()) {
if (linkAction) {
await linkAction(entry, ctx);
} else {
fileAction && await fileAction(entry, ctx);
}
} else {
otherAction && await otherAction(entry, ctx);
}
}

//
// walk through a directory tree calling user functions for each entry.
//
async function walker (dir) {
stack.push(path.basename(dir));
for await (const dirent of await fsp.opendir(dir)) {
let entry = path.join(dir, dirent.name);
// path.join refuses to start a path with '.'
if (dir === '.' || dir.startsWith(`.${sep}`)) {
entry = `.${sep}${entry}`;
}
const ctx = {dirent, stack};
if (options.own) {
ctx.own = options.own;
}
if (stat) {
ctx.stat = await fsp[stat](entry);
}
if (dirent.isDirectory()) {
if (await dirAction(entry, ctx) !== 'skip') {
await walker(entry);
}
} else if (dirent.isFile()) {
fileAction && await fileAction(entry, ctx);
} else if (dirent.isSymbolicLink()) {
if (linkAction) {
await linkAction(entry, ctx);
} else {
fileAction && await fileAction(entry, ctx);
}
} else {
otherAction && await otherAction(entry, ctx);
}
await dispatchAction(dir, dirent);
}
stack.pop();
return undefined;
}

return walker(dir);
// do first level dir here. we fake the top level dir as '' if it is '.'
// because the user didn't specify a full path and we don't want the
// resolved path to show up in the results.
if (options.includeTopLevel) {
let name = dir;
if (dir === '.') {
dir = '';
} else {
const p = path.parse(dir);
name = p.base;
}
const dirent = {
name,
isDirectory: () => true,
}
return dispatchAction(dir, dirent);
} else {
return walker(dir);
}
}

module.exports = walk;
13 changes: 10 additions & 3 deletions bin/walk.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ const walk = require('..');
/* eslint-disable no-console */

const args = process.argv.slice(2);
const options = {};
for (let i = 0; i < args.length; i++) {
if (args[i] === '-t' || args[i] === '--include-top-level') {
args.splice(i, 1);
options.includeTopLevel = true;
}
}

if (args.length < 1) {
console.log('usage: action-walk directory');
console.log('usage: action-walk [--include-top-level] directory');
process.exit(1);
}
const dir = args[0];

const own = {total: 0};
const options = {dirAction, fileAction, own, stat: 'lstat'};
Object.assign(options, {dirAction, fileAction, own, stat: 'lstat'});

async function main () {
return walk(dir, options);

}

main()
Expand Down
Loading