Dieses Repository beinhaltet HTML- und Javascript Code zur einer NotizenWebApp auf Basis von Web Storage. Zudem sind Mocha/Chai Tests im Browser enthalten. https://meinenotizen.netlify.app/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. 'use strict';
  2. const fs = require('fs');
  3. const { Readable } = require('stream');
  4. const sysPath = require('path');
  5. const picomatch = require('picomatch');
  6. const { promisify } = require('util');
  7. const [readdir, stat, lstat] = [promisify(fs.readdir), promisify(fs.stat), promisify(fs.lstat)];
  8. const supportsDirent = 'Dirent' in fs;
  9. /**
  10. * @typedef {Object} EntryInfo
  11. * @property {String} path
  12. * @property {String} fullPath
  13. * @property {fs.Stats=} stats
  14. * @property {fs.Dirent=} dirent
  15. * @property {String} basename
  16. */
  17. const isWindows = process.platform === 'win32';
  18. const supportsBigint = typeof BigInt === 'function';
  19. const BANG = '!';
  20. const NORMAL_FLOW_ERRORS = new Set(['ENOENT', 'EPERM', 'EACCES', 'ELOOP']);
  21. const STAT_OPTIONS_SUPPORT_LENGTH = 3;
  22. const FILE_TYPE = 'files';
  23. const DIR_TYPE = 'directories';
  24. const FILE_DIR_TYPE = 'files_directories';
  25. const EVERYTHING_TYPE = 'all';
  26. const FILE_TYPES = new Set([FILE_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE]);
  27. const DIR_TYPES = new Set([DIR_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE]);
  28. const ALL_TYPES = [FILE_TYPE, DIR_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE];
  29. const isNormalFlowError = errorCode => NORMAL_FLOW_ERRORS.has(errorCode);
  30. const checkBasename = f => f(entry.basename);
  31. const normalizeFilter = filter => {
  32. if (filter === undefined) return;
  33. if (typeof filter === 'function') return filter;
  34. if (typeof filter === 'string') {
  35. const glob = picomatch(filter.trim());
  36. return entry => glob(entry.basename);
  37. }
  38. if (Array.isArray(filter)) {
  39. const positive = [];
  40. const negative = [];
  41. for (const item of filter) {
  42. const trimmed = item.trim();
  43. if (trimmed.charAt(0) === BANG) {
  44. negative.push(picomatch(trimmed.slice(1)));
  45. } else {
  46. positive.push(picomatch(trimmed));
  47. }
  48. }
  49. if (negative.length > 0) {
  50. if (positive.length > 0) {
  51. return entry =>
  52. positive.some(f => f(entry.basename)) && !negative.some(f => f(entry.basename));
  53. } else {
  54. return entry => !negative.some(f => f(entry.basename));
  55. }
  56. } else {
  57. return entry => positive.some(f => f(entry.basename));
  58. }
  59. }
  60. };
  61. class ExploringDir {
  62. constructor(path, depth) {
  63. this.path = path;
  64. this.depth = depth;
  65. }
  66. }
  67. class ReaddirpStream extends Readable {
  68. static get defaultOptions() {
  69. return {
  70. root: '.',
  71. fileFilter: path => true,
  72. directoryFilter: path => true,
  73. type: 'files',
  74. lstat: false,
  75. depth: 2147483648,
  76. alwaysStat: false
  77. };
  78. }
  79. constructor(options = {}) {
  80. super({ objectMode: true, highWaterMark: 1, autoDestroy: true });
  81. const opts = Object.assign({}, ReaddirpStream.defaultOptions, options);
  82. const { root } = opts;
  83. this._fileFilter = normalizeFilter(opts.fileFilter);
  84. this._directoryFilter = normalizeFilter(opts.directoryFilter);
  85. this._statMethod = opts.lstat ? lstat : stat;
  86. this._statOpts = { bigint: isWindows };
  87. this._maxDepth = opts.depth;
  88. this._entryType = opts.type;
  89. this._root = sysPath.resolve(root);
  90. this._isDirent = !opts.alwaysStat && supportsDirent;
  91. this._statsProp = this._isDirent ? 'dirent' : 'stats';
  92. this._readdir_options = { encoding: 'utf8', withFileTypes: this._isDirent };
  93. // Launch stream with one parent, the root dir.
  94. /** @type Array<[string, number]> */
  95. this.parents = [new ExploringDir(root, 0)];
  96. this.filesToRead = 0;
  97. }
  98. async _read() {
  99. do {
  100. // If the stream was destroyed, we must not proceed.
  101. if (this.destroyed) return;
  102. const parent = this.parents.pop();
  103. if (!parent) {
  104. // ...we have files to process; but not directories.
  105. // hence, parent is undefined; and we cannot execute fs.readdir().
  106. // The files are being processed anywhere.
  107. break;
  108. }
  109. await this._exploreDirectory(parent);
  110. } while (!this.isPaused() && !this._isQueueEmpty());
  111. this._endStreamIfQueueIsEmpty();
  112. }
  113. async _exploreDirectory(parent) {
  114. /** @type Array<fs.Dirent|string> */
  115. let files = [];
  116. // To prevent race conditions, we increase counter while awaiting readdir.
  117. this.filesToRead++;
  118. try {
  119. files = await readdir(parent.path, this._readdir_options);
  120. } catch (error) {
  121. if (isNormalFlowError(error.code)) {
  122. this._handleError(error);
  123. } else {
  124. this._handleFatalError(error);
  125. }
  126. }
  127. this.filesToRead--;
  128. // If the stream was destroyed, after readdir is completed
  129. if (this.destroyed) return;
  130. this.filesToRead += files.length;
  131. const entries = await Promise.all(files.map(dirent => this._formatEntry(dirent, parent)));
  132. if (this.destroyed) return;
  133. for (let i = 0; i < entries.length; i++) {
  134. const entry = entries[i];
  135. this.filesToRead--;
  136. if (!entry) {
  137. continue;
  138. }
  139. if (this._isDirAndMatchesFilter(entry)) {
  140. this._pushNewParentIfLessThanMaxDepth(entry.fullPath, parent.depth + 1);
  141. this._emitPushIfUserWantsDir(entry);
  142. } else if (this._isFileAndMatchesFilter(entry)) {
  143. this._emitPushIfUserWantsFile(entry);
  144. }
  145. }
  146. }
  147. _isStatOptionsSupported() {
  148. return this._statMethod.length === STAT_OPTIONS_SUPPORT_LENGTH;
  149. }
  150. _stat(fullPath) {
  151. if (isWindows && this._isStatOptionsSupported()) {
  152. return this._statMethod(fullPath, this._statOpts);
  153. } else {
  154. return this._statMethod(fullPath);
  155. }
  156. }
  157. async _formatEntry(dirent, parent) {
  158. const basename = this._isDirent ? dirent.name : dirent;
  159. const fullPath = sysPath.resolve(sysPath.join(parent.path, basename));
  160. let stats;
  161. if (this._isDirent) {
  162. stats = dirent;
  163. } else {
  164. try {
  165. stats = await this._stat(fullPath);
  166. } catch (error) {
  167. if (isNormalFlowError(error.code)) {
  168. this._handleError(error);
  169. } else {
  170. this._handleFatalError(error);
  171. }
  172. return;
  173. }
  174. }
  175. const path = sysPath.relative(this._root, fullPath);
  176. /** @type {EntryInfo} */
  177. const entry = { path, fullPath, basename, [this._statsProp]: stats };
  178. return entry;
  179. }
  180. _isQueueEmpty() {
  181. return this.parents.length === 0 && this.filesToRead === 0 && this.readable;
  182. }
  183. _endStreamIfQueueIsEmpty() {
  184. if (this._isQueueEmpty()) {
  185. this.push(null);
  186. }
  187. }
  188. _pushNewParentIfLessThanMaxDepth(parentPath, depth) {
  189. if (depth <= this._maxDepth) {
  190. this.parents.push(new ExploringDir(parentPath, depth));
  191. return true;
  192. } else {
  193. return false;
  194. }
  195. }
  196. _isDirAndMatchesFilter(entry) {
  197. return entry[this._statsProp].isDirectory() && this._directoryFilter(entry);
  198. }
  199. _isFileAndMatchesFilter(entry) {
  200. const stats = entry[this._statsProp];
  201. const isFileType =
  202. (this._entryType === EVERYTHING_TYPE && !stats.isDirectory()) ||
  203. (stats.isFile() || stats.isSymbolicLink());
  204. return isFileType && this._fileFilter(entry);
  205. }
  206. _emitPushIfUserWantsDir(entry) {
  207. if (DIR_TYPES.has(this._entryType)) {
  208. // TODO: Understand why this happens.
  209. const fn = () => {
  210. this.push(entry);
  211. };
  212. if (this._isDirent) setImmediate(fn);
  213. else fn();
  214. }
  215. }
  216. _emitPushIfUserWantsFile(entry) {
  217. if (FILE_TYPES.has(this._entryType)) {
  218. this.push(entry);
  219. }
  220. }
  221. _handleError(error) {
  222. if (!this.destroyed) {
  223. this.emit('warn', error);
  224. }
  225. }
  226. _handleFatalError(error) {
  227. this.destroy(error);
  228. }
  229. }
  230. /**
  231. * @typedef {Object} ReaddirpArguments
  232. * @property {Function=} fileFilter
  233. * @property {Function=} directoryFilter
  234. * @property {String=} type
  235. * @property {Number=} depth
  236. * @property {String=} root
  237. * @property {Boolean=} lstat
  238. * @property {Boolean=} bigint
  239. */
  240. /**
  241. * Main function which ends up calling readdirRec and reads all files and directories in given root recursively.
  242. * @param {String} root Root directory
  243. * @param {ReaddirpArguments=} options Options to specify root (start directory), filters and recursion depth
  244. */
  245. const readdirp = (root, options = {}) => {
  246. let type = options['entryType'] || options.type;
  247. if (type === 'both') type = FILE_DIR_TYPE; // backwards-compatibility
  248. if (type) options.type = type;
  249. if (root == null || typeof root === 'undefined') {
  250. throw new Error('readdirp: root argument is required. Usage: readdirp(root, options)');
  251. } else if (typeof root !== 'string') {
  252. throw new Error(`readdirp: root argument must be a string. Usage: readdirp(root, options)`);
  253. } else if (type && !ALL_TYPES.includes(type)) {
  254. throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(', ')}`);
  255. }
  256. options.root = root;
  257. return new ReaddirpStream(options);
  258. };
  259. const readdirpPromise = (root, options = {}) => {
  260. return new Promise((resolve, reject) => {
  261. const files = [];
  262. readdirp(root, options)
  263. .on('data', entry => files.push(entry))
  264. .on('end', () => resolve(files))
  265. .on('error', error => reject(error));
  266. });
  267. };
  268. readdirp.promise = readdirpPromise;
  269. readdirp.ReaddirpStream = ReaddirpStream;
  270. readdirp.default = readdirp;
  271. module.exports = readdirp;