filehound 2.0.0-beta.2 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -11
- package/dist/arrays.d.ts +1 -0
- package/dist/arrays.js +4 -0
- package/dist/file.d.ts +108 -0
- package/dist/file.js +184 -0
- package/dist/filehound.d.ts +322 -0
- package/dist/filehound.js +443 -0
- package/dist/files.d.ts +4 -0
- package/dist/files.js +37 -0
- package/dist/filetype.d.ts +10 -0
- package/dist/filetype.js +9 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/matchers.d.ts +297 -0
- package/dist/matchers.js +389 -0
- package/dist/walker/async.d.ts +4 -0
- package/dist/walker/async.js +237 -0
- package/dist/walker/heap.d.ts +7 -0
- package/dist/walker/heap.js +49 -0
- package/dist/walker/sync.d.ts +4 -0
- package/dist/walker/sync.js +66 -0
- package/dist/walker/walk.d.ts +32 -0
- package/dist/walker/walk.js +70 -0
- package/package.json +44 -48
- package/.github/stale.yml +0 -42
- package/.travis.yml +0 -8
- package/.vscode/launch.json +0 -21
- package/.vscode/settings.json +0 -3
- package/CONTRIBUTING.md +0 -8
- package/_config.yml +0 -1
- package/coverage/coverage.raw.json +0 -1
- package/coverage/lcov-report/arrays.ts.html +0 -98
- package/coverage/lcov-report/base.css +0 -212
- package/coverage/lcov-report/filehound.ts.html +0 -2078
- package/coverage/lcov-report/files.ts.html +0 -260
- package/coverage/lcov-report/functions.ts.html +0 -122
- package/coverage/lcov-report/index.html +0 -132
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -1
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -158
- package/coverage/lcov.info +0 -378
- package/dist/src/arrays.js +0 -15
- package/dist/src/arrays.js.map +0 -1
- package/dist/src/asyncWalker.js +0 -59
- package/dist/src/asyncWalker.js.map +0 -1
- package/dist/src/bind.js +0 -15
- package/dist/src/bind.js.map +0 -1
- package/dist/src/filehound.js +0 -597
- package/dist/src/filehound.js.map +0 -1
- package/dist/src/files.js +0 -60
- package/dist/src/files.js.map +0 -1
- package/dist/src/matcher.js +0 -51
- package/dist/src/matcher.js.map +0 -1
- package/dist/src/syncWalker.js +0 -47
- package/dist/src/syncWalker.js.map +0 -1
- package/dist/src/walkAsync.js +0 -33
- package/dist/src/walkAsync.js.map +0 -1
- package/dist/src/walkSync.js +0 -21
- package/dist/src/walkSync.js.map +0 -1
- package/dist/src/walker/walk.js +0 -68
- package/dist/src/walker/walk.js.map +0 -1
- package/dist/src/walker/walkAsync.js +0 -33
- package/dist/src/walker/walkAsync.js.map +0 -1
- package/dist/src/walker/walkSync.js +0 -21
- package/dist/src/walker/walkSync.js.map +0 -1
- package/dist/test/filehound.js +0 -872
- package/dist/test/filehound.js.map +0 -1
- package/dist/test/files.js +0 -29
- package/dist/test/files.js.map +0 -1
- package/docconfig/jsdoc.json +0 -26
- package/docconfig/template/LICENSE +0 -61
- package/docconfig/template/README.md +0 -86
- package/docconfig/template/package.json +0 -22
- package/docconfig/template/publish.js +0 -676
- package/docconfig/template/static/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Bold-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Italic-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Light-webfont.svg +0 -1831
- package/docconfig/template/static/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
- package/docconfig/template/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Regular-webfont.svg +0 -1831
- package/docconfig/template/static/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.ttf +0 -0
- package/docconfig/template/static/fonts/OpenSans-Semibold-webfont.woff +0 -0
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
- package/docconfig/template/static/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
- package/docconfig/template/static/scripts/linenumber.js +0 -25
- package/docconfig/template/static/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docconfig/template/static/scripts/prettify/lang-css.js +0 -2
- package/docconfig/template/static/scripts/prettify/prettify.js +0 -28
- package/docconfig/template/static/styles/jsdoc-default.css +0 -605
- package/docconfig/template/static/styles/prettify-jsdoc.css +0 -111
- package/docconfig/template/static/styles/prettify-tomorrow.css +0 -132
- package/docconfig/template/tmpl/augments.tmpl +0 -10
- package/docconfig/template/tmpl/container.tmpl +0 -183
- package/docconfig/template/tmpl/details.tmpl +0 -143
- package/docconfig/template/tmpl/example.tmpl +0 -2
- package/docconfig/template/tmpl/examples.tmpl +0 -13
- package/docconfig/template/tmpl/exceptions.tmpl +0 -32
- package/docconfig/template/tmpl/layout.tmpl +0 -47
- package/docconfig/template/tmpl/mainpage.tmpl +0 -10
- package/docconfig/template/tmpl/members.tmpl +0 -38
- package/docconfig/template/tmpl/method.tmpl +0 -105
- package/docconfig/template/tmpl/params.tmpl +0 -124
- package/docconfig/template/tmpl/properties.tmpl +0 -108
- package/docconfig/template/tmpl/returns.tmpl +0 -19
- package/docconfig/template/tmpl/source.tmpl +0 -8
- package/docconfig/template/tmpl/tutorial.tmpl +0 -19
- package/docconfig/template/tmpl/type.tmpl +0 -7
- package/docs/FileHound.html +0 -3079
- package/docs/FileHound_modified.html +0 -245
- package/docs/_config.yml +0 -1
- package/docs/filehound.js.html +0 -697
- package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
- package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
- package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
- package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
- package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
- package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
- package/docs/global.html +0 -288
- package/docs/index.html +0 -154
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/scripts/prettify/lang-css.js +0 -2
- package/docs/scripts/prettify/prettify.js +0 -28
- package/docs/styles/jsdoc-default.css +0 -605
- package/docs/styles/prettify-jsdoc.css +0 -111
- package/docs/styles/prettify-tomorrow.css +0 -132
- package/docs.md +0 -164
- package/index.js +0 -1
- package/issue_template.md +0 -6
- package/test/filehound.ts +0 -1076
- package/test/files.ts +0 -29
- package/test/fixtures/custom/failed.txt +0 -0
- package/test/fixtures/custom/passed.txt +0 -0
- package/test/fixtures/deeplyNested/c.json +0 -0
- package/test/fixtures/deeplyNested/d.json +0 -0
- package/test/fixtures/deeplyNested/mydir/e.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/f.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/mydir3/mydir4/failed.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/mydir3/z.json +0 -0
- package/test/fixtures/deeplyNested/mydir/mydir2/y.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/c.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/d.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/e.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/f.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4/.mydir5/.keep +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4/failed.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/z.json +0 -0
- package/test/fixtures/deeplyNestedWithHiddenDir/mydir/mydir2/y.json +0 -0
- package/test/fixtures/ext/Dummy.java +0 -0
- package/test/fixtures/ext/Dumpy.go +0 -0
- package/test/fixtures/ext/dummy.json +0 -0
- package/test/fixtures/ext/dummy.txt +0 -0
- package/test/fixtures/justFiles/a.json +0 -0
- package/test/fixtures/justFiles/b.json +0 -0
- package/test/fixtures/justFiles/dummy.txt +0 -0
- package/test/fixtures/mixed/a.json +0 -0
- package/test/fixtures/mixed/aabbcc.json +0 -0
- package/test/fixtures/mixed/ab.json +0 -0
- package/test/fixtures/mixed/z.json +0 -0
- package/test/fixtures/nested/.hidden1/bad.txt +0 -0
- package/test/fixtures/nested/c.json +0 -0
- package/test/fixtures/nested/d.json +0 -0
- package/test/fixtures/nested/mydir/e.json +0 -0
- package/test/fixtures/sizes/10b.txt +0 -0
- package/test/fixtures/sizes/1b.txt +0 -0
- package/test/fixtures/sizes/1k.txt +0 -0
- package/test/fixtures/sizes/2k.txt +0 -0
- package/test/fixtures/visibility/.hidden/.hidden.json +0 -0
- package/test/fixtures/visibility/.hidden/visible.json +0 -3
- package/test/fixtures/visibility/.hidden.json +0 -3
- package/test/fixtures/visibility/visible/.hidden.json +0 -3
- package/test/fixtures/visibility/visible.json +0 -3
- package/test/mocha.opts +0 -4
- package/tsconfig.json +0 -16
- package/tslint.json +0 -44
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { from } from './arrays.js';
|
|
4
|
+
import { reducePaths } from './files.js';
|
|
5
|
+
import { matchAll } from './matchers.js';
|
|
6
|
+
import { FileType } from './filetype.js';
|
|
7
|
+
import { walkAsync } from './walker/async.js';
|
|
8
|
+
import { walkSync } from './walker/sync.js';
|
|
9
|
+
const fileTypes = Object.values(FileType);
|
|
10
|
+
/**
|
|
11
|
+
* A fluent query for searching the file system.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam T - the type of each match: a path, or a {@link FileWithStats}
|
|
14
|
+
* when {@link FileHound.includeFileStats} is set
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Configure a query by chaining methods, then run it with {@link FileHound.find}
|
|
18
|
+
* or {@link FileHound.findSync}. Once a search has started the query is locked
|
|
19
|
+
* and can no longer be modified.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { FileHound, ext } from 'filehound';
|
|
24
|
+
*
|
|
25
|
+
* const files = await FileHound.create()
|
|
26
|
+
* .paths('/tmp')
|
|
27
|
+
* .match(ext('json'))
|
|
28
|
+
* .depth(1)
|
|
29
|
+
* .find();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class FileHound extends EventEmitter {
|
|
33
|
+
#searchPaths;
|
|
34
|
+
#maxDepth;
|
|
35
|
+
#types;
|
|
36
|
+
#matchExpression;
|
|
37
|
+
#ignoreHiddenFiles = false;
|
|
38
|
+
#ignoreHiddenDirectories = false;
|
|
39
|
+
#ignoreSymlinks = false;
|
|
40
|
+
#includeFileStats = false;
|
|
41
|
+
#queryLocked = false;
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
this.#searchPaths = [process.cwd()];
|
|
45
|
+
this.#types = [FileType.REGULAR];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Static factory method to create an instance of FileHound
|
|
49
|
+
*
|
|
50
|
+
* @returns a new FileHound instance
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { FileHound } from 'filehound';
|
|
54
|
+
*
|
|
55
|
+
* const filehound = FileHound.create();
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
static create() {
|
|
59
|
+
return new FileHound();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Executes one or more FileHound queries and combines their results
|
|
63
|
+
*
|
|
64
|
+
* @param args - the FileHound instances to execute
|
|
65
|
+
* @returns a Promise of all matches. If the Promise fulfils,
|
|
66
|
+
* the fulfilment value is an array of all files matched by any of the queries
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { FileHound } from 'filehound';
|
|
70
|
+
*
|
|
71
|
+
* const files = await FileHound.any(fh1, fh2);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
static async any(...args) {
|
|
75
|
+
const pending = args.map(fh => fh.find());
|
|
76
|
+
const files = await Promise.all(pending);
|
|
77
|
+
return files.flat();
|
|
78
|
+
}
|
|
79
|
+
paths(...args) {
|
|
80
|
+
this.#throwIfQueryLocked();
|
|
81
|
+
this.#searchPaths = [...new Set(from(args))].map(path.normalize);
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Specifies the criteria that files must satisfy to be included in the
|
|
86
|
+
* results. Defaults to matching all files.
|
|
87
|
+
*
|
|
88
|
+
* @param query - the predicate used to test each file
|
|
89
|
+
* @returns the FileHound instance
|
|
90
|
+
* @throws `Error`
|
|
91
|
+
* Thrown if the query is locked because a search has started.
|
|
92
|
+
* @throws `TypeError`
|
|
93
|
+
* Thrown if the query is not a valid matcher.
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* import { FileHound, ext } from 'filehound';
|
|
97
|
+
*
|
|
98
|
+
* const files = await FileHound.create()
|
|
99
|
+
* .match(ext('json'))
|
|
100
|
+
* .find();
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
match(query) {
|
|
104
|
+
this.#throwIfQueryLocked();
|
|
105
|
+
if (query === null || query === undefined || typeof query !== 'object' || typeof query.test !== 'function' || typeof query.testSync !== 'function') {
|
|
106
|
+
throw new TypeError('Match expression must be a valid matcher');
|
|
107
|
+
}
|
|
108
|
+
this.#matchExpression = query;
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Specify the directory search depth. If set to zero, recursive searching
|
|
113
|
+
* will be disabled. Directories at the limit are not searched, but are
|
|
114
|
+
* still returned when {@link FileHound.type} includes directories.
|
|
115
|
+
*
|
|
116
|
+
* @param depth - the maximum search depth (a non-negative integer)
|
|
117
|
+
* @returns the FileHound instance
|
|
118
|
+
* @throws `Error`
|
|
119
|
+
* Thrown if the query is locked because a search has started.
|
|
120
|
+
* @throws `RangeError`
|
|
121
|
+
* Thrown if depth is negative or not an integer.
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* import { FileHound } from 'filehound';
|
|
125
|
+
*
|
|
126
|
+
* const files = await FileHound.create()
|
|
127
|
+
* .depth(0)
|
|
128
|
+
* .find(); // array of file names only in the current directory
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
depth(depth) {
|
|
132
|
+
this.#throwIfQueryLocked();
|
|
133
|
+
if (depth < 0 || !Number.isSafeInteger(depth)) {
|
|
134
|
+
throw new RangeError('Depth must be a positive integer');
|
|
135
|
+
}
|
|
136
|
+
this.#maxDepth = depth;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Specifies the types of file system entry to return. Defaults to
|
|
141
|
+
* regular files only.
|
|
142
|
+
*
|
|
143
|
+
* @param types - one or more file types
|
|
144
|
+
* @returns the FileHound instance
|
|
145
|
+
* @throws `Error`
|
|
146
|
+
* Thrown if the query is locked because a search has started.
|
|
147
|
+
* @throws `RangeError`
|
|
148
|
+
* Thrown if no file types are given.
|
|
149
|
+
* @throws `TypeError`
|
|
150
|
+
* Thrown if an invalid file type is given.
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { FileHound, FileType } from 'filehound';
|
|
154
|
+
*
|
|
155
|
+
* const directories = await FileHound.create()
|
|
156
|
+
* .type(FileType.DIRECTORY)
|
|
157
|
+
* .find();
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
type(...types) {
|
|
161
|
+
this.#throwIfQueryLocked();
|
|
162
|
+
this.#validateTypeArguments(types);
|
|
163
|
+
this.#types = types;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Excludes hidden files (names beginning with a dot) from the results
|
|
168
|
+
*
|
|
169
|
+
* @returns the FileHound instance
|
|
170
|
+
* @throws `Error`
|
|
171
|
+
* Thrown if the query is locked because a search has started.
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* import { FileHound } from 'filehound';
|
|
175
|
+
*
|
|
176
|
+
* const files = await FileHound.create()
|
|
177
|
+
* .ignoreHiddenFiles()
|
|
178
|
+
* .find();
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
ignoreHiddenFiles() {
|
|
182
|
+
this.#throwIfQueryLocked();
|
|
183
|
+
this.#ignoreHiddenFiles = true;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Excludes hidden directories (names beginning with a dot), and their
|
|
188
|
+
* contents, from the search
|
|
189
|
+
*
|
|
190
|
+
* @returns the FileHound instance
|
|
191
|
+
* @throws `Error`
|
|
192
|
+
* Thrown if the query is locked because a search has started.
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* import { FileHound } from 'filehound';
|
|
196
|
+
*
|
|
197
|
+
* const files = await FileHound.create()
|
|
198
|
+
* .ignoreHiddenDirectories()
|
|
199
|
+
* .find();
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
ignoreHiddenDirectories() {
|
|
203
|
+
this.#throwIfQueryLocked();
|
|
204
|
+
this.#ignoreHiddenDirectories = true;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Excludes symbolic links from the search. Linked files are not matched
|
|
209
|
+
* and linked directories are not searched.
|
|
210
|
+
*
|
|
211
|
+
* @remarks
|
|
212
|
+
* By default, symbolic links are followed: a linked file is matched as its
|
|
213
|
+
* target and a linked directory is searched. Broken links, and links back to
|
|
214
|
+
* a directory already being searched, are always skipped.
|
|
215
|
+
*
|
|
216
|
+
* @returns the FileHound instance
|
|
217
|
+
* @throws `Error`
|
|
218
|
+
* Thrown if the query is locked because a search has started.
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* import { FileHound } from 'filehound';
|
|
222
|
+
*
|
|
223
|
+
* const files = await FileHound.create()
|
|
224
|
+
* .ignoreSymlinks()
|
|
225
|
+
* .find();
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
ignoreSymlinks() {
|
|
229
|
+
this.#throwIfQueryLocked();
|
|
230
|
+
this.#ignoreSymlinks = true;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Returns each match with its stats, as `{ path, stats }`, in place of
|
|
235
|
+
* its path. Stats already fetched by a predicate are reused.
|
|
236
|
+
*
|
|
237
|
+
* @returns the FileHound instance, typed to return {@link FileWithStats} matches
|
|
238
|
+
* @throws `Error`
|
|
239
|
+
* Thrown if the query is locked because a search has started.
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* import { FileHound } from 'filehound';
|
|
243
|
+
*
|
|
244
|
+
* const files = await FileHound.create()
|
|
245
|
+
* .includeFileStats()
|
|
246
|
+
* .find();
|
|
247
|
+
*
|
|
248
|
+
* files.forEach(({ path, stats }) => console.log(path, stats.size));
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
includeFileStats() {
|
|
252
|
+
this.#throwIfQueryLocked();
|
|
253
|
+
this.#includeFileStats = true;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Asynchronously executes a file search. Once called, the query is
|
|
258
|
+
* locked and can no longer be modified.
|
|
259
|
+
*
|
|
260
|
+
* @param options - a signal to stop the search
|
|
261
|
+
* @returns a Promise of all matches. If the Promise fulfils,
|
|
262
|
+
* the fulfilment value is an array of all matching files
|
|
263
|
+
*
|
|
264
|
+
* @remarks
|
|
265
|
+
* Emits a `match` event for each matching file, an `error` event if the
|
|
266
|
+
* search fails or is aborted and an `end` event when the search completes.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* import { FileHound } from 'filehound';
|
|
271
|
+
*
|
|
272
|
+
* const filehound = FileHound.create();
|
|
273
|
+
* const files = await filehound.find();
|
|
274
|
+
*
|
|
275
|
+
* console.log(files);
|
|
276
|
+
* ```
|
|
277
|
+
* @example
|
|
278
|
+
* Giving up on a search after five seconds:
|
|
279
|
+
* ```ts
|
|
280
|
+
* const files = await FileHound.create()
|
|
281
|
+
* .paths('/')
|
|
282
|
+
* .find({ signal: AbortSignal.timeout(5000) });
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
async find(options = {}) {
|
|
286
|
+
this.#queryLocked = true;
|
|
287
|
+
const found = [];
|
|
288
|
+
try {
|
|
289
|
+
for await (const results of this.#getNextBatch(options.signal)) {
|
|
290
|
+
for (const result of results) {
|
|
291
|
+
found.push(result);
|
|
292
|
+
this.emit('match', result);
|
|
293
|
+
options.signal?.throwIfAborted();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return found;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
this.emit('error', error);
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
this.emit('end');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Asynchronously executes a file search, returning matches one at a time
|
|
308
|
+
* as they are found, in the same order as {@link FileHound.find}. Once
|
|
309
|
+
* called, the query is locked and can no longer be modified.
|
|
310
|
+
*
|
|
311
|
+
* @remarks
|
|
312
|
+
* The search runs ahead of the loop by a bounded amount, so a large search
|
|
313
|
+
* can be read without holding every match in memory. Breaking out of the
|
|
314
|
+
* loop stops the search.
|
|
315
|
+
*
|
|
316
|
+
* Emits a `match` event as each match is returned, an `error` event if the
|
|
317
|
+
* search fails or is aborted and an `end` event when the search finishes:
|
|
318
|
+
* when every match has been returned, the search fails or is aborted, or
|
|
319
|
+
* the loop is left early.
|
|
320
|
+
*
|
|
321
|
+
* @param options - a signal to stop the search
|
|
322
|
+
* @returns an async iterator of matches
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* import { FileHound, ext } from 'filehound';
|
|
326
|
+
*
|
|
327
|
+
* const query = FileHound.create()
|
|
328
|
+
* .paths('/tmp')
|
|
329
|
+
* .match(ext('json'));
|
|
330
|
+
*
|
|
331
|
+
* for await (const file of query.iterate()) {
|
|
332
|
+
* console.log(file);
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
iterate(options = {}) {
|
|
337
|
+
this.#queryLocked = true;
|
|
338
|
+
return this.#iterate(options.signal);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Synchronously executes a file search. Once called, the query is
|
|
342
|
+
* locked and can no longer be modified. No events are emitted.
|
|
343
|
+
*
|
|
344
|
+
* @returns an array of all matching files
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* import { FileHound } from 'filehound';
|
|
348
|
+
*
|
|
349
|
+
* const filehound = FileHound.create();
|
|
350
|
+
* const files = filehound.findSync();
|
|
351
|
+
* console.log(files);
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
findSync() {
|
|
355
|
+
this.#queryLocked = true;
|
|
356
|
+
return this.getPaths()
|
|
357
|
+
.flatMap(dir => this.#searchSync(dir))
|
|
358
|
+
.map(file => this.#toResultSync(file));
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Returns the paths that will be searched. When no depth is set, any path
|
|
362
|
+
* that is a subdirectory of another search path is removed, as it would
|
|
363
|
+
* otherwise be searched twice.
|
|
364
|
+
*
|
|
365
|
+
* @returns a copy of the search paths
|
|
366
|
+
*/
|
|
367
|
+
getPaths() {
|
|
368
|
+
const paths = this.#maxDepth !== undefined
|
|
369
|
+
? this.#searchPaths
|
|
370
|
+
: reducePaths(this.#searchPaths);
|
|
371
|
+
return [...paths];
|
|
372
|
+
}
|
|
373
|
+
#throwIfQueryLocked() {
|
|
374
|
+
if (this.#queryLocked) {
|
|
375
|
+
throw new Error('Cannot modify query after search has started');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
#validateTypeArguments(types) {
|
|
379
|
+
if (types.length === 0) {
|
|
380
|
+
throw new RangeError('At least one file type must be specified');
|
|
381
|
+
}
|
|
382
|
+
types.forEach((type) => {
|
|
383
|
+
if (!fileTypes.includes(type)) {
|
|
384
|
+
throw new TypeError(`Invalid file type: ${type}.`);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
#getMatchExpression() {
|
|
389
|
+
return this.#matchExpression ?? matchAll();
|
|
390
|
+
}
|
|
391
|
+
async #toResult(file) {
|
|
392
|
+
if (this.#includeFileStats) {
|
|
393
|
+
return { path: file.getName(), stats: await file.getStats() };
|
|
394
|
+
}
|
|
395
|
+
return file.getName();
|
|
396
|
+
}
|
|
397
|
+
#toResultSync(file) {
|
|
398
|
+
if (this.#includeFileStats) {
|
|
399
|
+
return { path: file.getName(), stats: file.getStatsSync() };
|
|
400
|
+
}
|
|
401
|
+
return file.getName();
|
|
402
|
+
}
|
|
403
|
+
#searchSync(dir) {
|
|
404
|
+
return walkSync(dir, this.#getMatchExpression(), this.#getOptions());
|
|
405
|
+
}
|
|
406
|
+
async *#iterate(signal) {
|
|
407
|
+
try {
|
|
408
|
+
for await (const results of this.#getNextBatch(signal)) {
|
|
409
|
+
for (const result of results) {
|
|
410
|
+
this.emit('match', result);
|
|
411
|
+
yield result;
|
|
412
|
+
signal?.throwIfAborted();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
this.emit('error', error);
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
finally {
|
|
421
|
+
this.emit('end');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async *#getNextBatch(signal) {
|
|
425
|
+
for (const dir of this.getPaths()) {
|
|
426
|
+
for await (const files of walkAsync(dir, this.#getMatchExpression(), this.#getOptions(signal))) {
|
|
427
|
+
yield this.#includeFileStats
|
|
428
|
+
? await Promise.all(files.map(file => this.#toResult(file)))
|
|
429
|
+
: files.map(file => file.getName());
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
#getOptions(signal) {
|
|
434
|
+
return {
|
|
435
|
+
ignoreHiddenDirectories: this.#ignoreHiddenDirectories,
|
|
436
|
+
ignoreHiddenFiles: this.#ignoreHiddenFiles,
|
|
437
|
+
ignoreSymlinks: this.#ignoreSymlinks,
|
|
438
|
+
maxDepth: this.#maxDepth,
|
|
439
|
+
fileTypes: this.#types,
|
|
440
|
+
signal
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function findSubDirectories(paths: string[]): string[];
|
|
2
|
+
export declare function notSubDirectory(subDirs: string[]): (s: string) => boolean;
|
|
3
|
+
export declare function isSubDirectory(base: string, candidate: string): boolean;
|
|
4
|
+
export declare function reducePaths(searchPaths: string[]): string[];
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
// dirname is a fixed point at a root (`/`, `C:\`) and at `.`
|
|
3
|
+
function hasParent(parent) {
|
|
4
|
+
return path.dirname(parent) !== parent;
|
|
5
|
+
}
|
|
6
|
+
function getParent(dir) {
|
|
7
|
+
return path.dirname(dir);
|
|
8
|
+
}
|
|
9
|
+
function getSubDirectories(base, allPaths) {
|
|
10
|
+
return allPaths.filter((candidate) => {
|
|
11
|
+
return base !== candidate && isSubDirectory(base, candidate);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function findSubDirectories(paths) {
|
|
15
|
+
return paths.flatMap(p => getSubDirectories(p, paths));
|
|
16
|
+
}
|
|
17
|
+
export function notSubDirectory(subDirs) {
|
|
18
|
+
return (p) => !subDirs.includes(p);
|
|
19
|
+
}
|
|
20
|
+
export function isSubDirectory(base, candidate) {
|
|
21
|
+
let parent = candidate;
|
|
22
|
+
while (hasParent(parent)) {
|
|
23
|
+
if (base === parent) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
parent = getParent(parent);
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
export function reducePaths(searchPaths) {
|
|
31
|
+
if (searchPaths.length === 1) {
|
|
32
|
+
return searchPaths;
|
|
33
|
+
}
|
|
34
|
+
const sortedPaths = searchPaths.toSorted();
|
|
35
|
+
const subDirs = findSubDirectories(sortedPaths);
|
|
36
|
+
return sortedPaths.filter(notSubDirectory(subDirs));
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The types of file system entry that a search can return. The values are
|
|
3
|
+
* plain strings, so `'directory'` can be passed in place of `FileType.DIRECTORY`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const FileType: {
|
|
6
|
+
/** Regular files only: not sockets, FIFOs or devices */
|
|
7
|
+
readonly REGULAR: "regular";
|
|
8
|
+
readonly DIRECTORY: "directory";
|
|
9
|
+
};
|
|
10
|
+
export type FileType = typeof FileType[keyof typeof FileType];
|
package/dist/filetype.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The types of file system entry that a search can return. The values are
|
|
3
|
+
* plain strings, so `'directory'` can be passed in place of `FileType.DIRECTORY`.
|
|
4
|
+
*/
|
|
5
|
+
export const FileType = {
|
|
6
|
+
/** Regular files only: not sockets, FIFOs or devices */
|
|
7
|
+
REGULAR: 'regular',
|
|
8
|
+
DIRECTORY: 'directory'
|
|
9
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { FileHound, type FileWithStats, type SearchOptions } from './filehound.js';
|
|
2
|
+
export { FileType } from './filetype.js';
|
|
3
|
+
export type { FileView } from './file.js';
|
|
4
|
+
export { type FilePredicate, accessed, applyFilter, applyFilterSync, changed, discard, ext, glob, isEmpty, like, matchAll, modified, not, size, sizeEquals } from './matchers.js';
|
package/dist/index.js
ADDED