filehound 2.0.0-beta.2 → 2.0.0

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