From 7238f906f5da9bfde30690839566581dd48da078 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <michael@minelli.me>
Date: Wed, 9 Aug 2023 12:32:06 +0200
Subject: [PATCH] Add RecursiveFilesStats

---
 helpers/recursiveFilesStats/README.md         | 148 ++++++++++++++++++
 .../RecursiveFilesStats.ts                    | 146 +++++++++++++++++
 2 files changed, 294 insertions(+)
 create mode 100644 helpers/recursiveFilesStats/README.md
 create mode 100644 helpers/recursiveFilesStats/RecursiveFilesStats.ts

diff --git a/helpers/recursiveFilesStats/README.md b/helpers/recursiveFilesStats/README.md
new file mode 100644
index 0000000..f2e98d4
--- /dev/null
+++ b/helpers/recursiveFilesStats/README.md
@@ -0,0 +1,148 @@
+Source: recursive-readdir-files
+===
+Modified for Dojo
+===
+
+## Usage
+
+```js
+import recursiveReaddirFiles from 'recursive-readdir-files';
+
+
+const files = await recursiveReaddirFiles(process.cwd(), {
+    ignored: /\/(node_modules|\.git)/
+});
+
+// `files` is an array
+console.log(files);
+// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
+// [
+//   {
+//     dev: 16777233,
+//     mode: 33188,
+//     nlink: 1,
+//     uid: 501,
+//     gid: 20,
+//     rdev: 0,
+//     blksize: 4096,
+//     ino: 145023089,
+//     size: 89,
+//     blocks: 8,
+//     atimeMs: 1649303678077.934,
+//     mtimeMs: 1649303676847.1777,
+//     ctimeMs: 1649303676847.1777,
+//     birthtimeMs: 1649301118132.6782,
+//     atime: 2022-04-07T03:54:38.078Z,
+//     mtime: 2022-04-07T03:54:36.847Z,
+//     ctime: 2022-04-07T03:54:36.847Z,
+//     birthtime: 2022-04-07T03:11:58.133Z,
+//     name: 'watch.ts',
+//     path: '/Users/xxx/watch.ts',
+//     ext: 'ts'
+//   },
+//   // ...
+// ]
+```
+
+Or
+
+```js
+recursiveReaddirFiles(process.cwd(), {
+    ignored: /\/(node_modules|\.git)/
+}, (filepath, state) => {
+    console.log(filepath);
+    // 👉 /Users/xxx/watch.ts
+    console.log(state.isFile());      // 👉 true
+    console.log(state.isDirectory()); // 👉 false
+    console.log(state);
+    // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
+    // {
+    //   dev: 16777233,
+    //   mode: 33188,
+    //   nlink: 1,
+    //   uid: 501,
+    //   gid: 20,
+    //   rdev: 0,
+    //   blksize: 4096,
+    //   ino: 145023089,
+    //   size: 89,
+    //   blocks: 8,
+    //   atimeMs: 1649303678077.934,
+    //   mtimeMs: 1649303676847.1777,
+    //   ctimeMs: 1649303676847.1777,
+    //   birthtimeMs: 1649301118132.6782,
+    //   atime: 2022-04-07T03:54:38.078Z,
+    //   mtime: 2022-04-07T03:54:36.847Z,
+    //   ctime: 2022-04-07T03:54:36.847Z,
+    //   birthtime: 2022-04-07T03:11:58.133Z,
+    //   name: 'watch.ts',
+    //   path: '/Users/xxx/watch.ts',
+    //   ext: 'ts'
+    // }
+})
+```
+
+## Options
+
+```ts
+export interface RecursiveReaddirFilesOptions {
+    /**
+     * Ignore files
+     * @example `/\/(node_modules|\.git)/`
+     */
+    ignored?: RegExp;
+    /**
+     * Specifies a list of `glob` patterns that match files to be included in compilation.
+     * @example `/(\.json)$/`
+     */
+    include?: RegExp;
+    /**
+     * Specifies a list of files to be excluded from compilation.
+     * @example `/(package\.json)$/`
+     */
+    exclude?: RegExp;
+    /** Provide filtering methods to filter data. */
+    filter?: (item: IFileDirStat) => boolean;
+    /** Do not give the absolute path but the relative one from the root folder */
+    replacePathByRelativeOne?: boolean;
+    /** Remove stats that are not necessary for transfert */
+    liteStats?: boolean;
+}
+```
+
+## Result
+
+```ts
+import fs from 'node:fs';
+
+
+export interface IFileDirStat extends Partial<fs.Stats> {
+    /**
+     * @example `/a/sum.jpg` => `sum.jpg`
+     */
+    name: string;
+    /**
+     * @example `/basic/src/utils/sum.ts`
+     */
+    path: string;
+    /**
+     * @example `/a/b.jpg` => `jpg`
+     */
+    ext?: string;
+}
+
+
+declare type Callback = (filepath: string, stat: IFileDirStat) => void;
+export default function recursiveReaddirFiles(rootPath: string, options?: RecursiveReaddirFilesOptions, callback?: Callback): Promise<IFileDirStat[]>;
+export { recursiveReaddirFiles };
+export declare const getStat: (filepath: string) => Promise<IFileDirStat>;
+/**
+ * Get ext
+ * @param {String} filePath `/a/b.jpg` => `jpg`
+ */
+export declare const getExt: (filePath: string) => string;
+```
+
+## License
+
+Licensed under the MIT License.
diff --git a/helpers/recursiveFilesStats/RecursiveFilesStats.ts b/helpers/recursiveFilesStats/RecursiveFilesStats.ts
new file mode 100644
index 0000000..5a97fda
--- /dev/null
+++ b/helpers/recursiveFilesStats/RecursiveFilesStats.ts
@@ -0,0 +1,146 @@
+import fs   from 'node:fs';
+import path from 'node:path';
+
+
+export interface RecursiveReaddirFilesOptions {
+    /**
+     * Ignore files
+     * @example `/\/(node_modules|\.git)/`
+     */
+    ignored?: RegExp;
+    /**
+     * Specifies a list of `glob` patterns that match files to be included in compilation.
+     * @example `/(\.json)$/`
+     */
+    include?: RegExp;
+    /**
+     * Specifies a list of files to be excluded from compilation.
+     * @example `/(package\.json)$/`
+     */
+    exclude?: RegExp;
+    /** Provide filtering methods to filter data. */
+    filter?: (item: IFileDirStat) => boolean;
+    /** Do not give the absolute path but the relative one from the root folder */
+    replacePathByRelativeOne?: boolean;
+    /** Remove stats that are not necessary for transfert */
+    liteStats?: boolean;
+}
+
+
+export interface IFileDirStat extends Partial<fs.Stats> {
+    /**
+     * @example `/a/sum.jpg` => `sum.jpg`
+     */
+    name: string;
+    /**
+     * @example `/basic/src/utils/sum.ts`
+     */
+    path: string;
+    /**
+     * @example `/a/b.jpg` => `jpg`
+     */
+    ext?: string;
+}
+
+
+type Callback = (filepath: string, stat: IFileDirStat) => void;
+
+
+class RecursiveFilesStats {
+    async explore(rootPath: string, options: RecursiveReaddirFilesOptions = {}, callback?: Callback): Promise<IFileDirStat[]> {
+        return this.getFiles(`${ path.resolve(rootPath) }/`, rootPath, options, [], callback);
+    }
+
+    private async getFiles(absoluteBasePath: string, rootPath: string, options: RecursiveReaddirFilesOptions = {}, files: IFileDirStat[] = [], callback?: Callback): Promise<IFileDirStat[]> {
+        const {
+                  ignored, include, exclude, filter
+              } = options;
+        const filesData = await fs.promises.readdir(rootPath);
+        const fileDir: IFileDirStat[] = filesData.map((file) => ({
+            name: file, path: path.join(rootPath, file)
+        })).filter((item) => {
+            if ( include && include.test(item.path) ) {
+                return true;
+            }
+            if ( exclude && exclude.test(item.path) ) {
+                return false;
+            }
+            if ( ignored ) {
+                return !ignored.test(item.path);
+            }
+            return true;
+        });
+        if ( callback ) {
+            fileDir.map(async (item: IFileDirStat) => {
+                const stat = await this.getStat(item.path, absoluteBasePath, options);
+                if ( stat.isDirectory!() ) {
+                    await this.getFiles(absoluteBasePath, item.path, options, [], callback);
+                }
+                callback(item.path, stat);
+            });
+        } else {
+            await Promise.all(fileDir.map(async (item: IFileDirStat) => {
+                const stat = await this.getStat(item.path, absoluteBasePath, options);
+                if ( stat.isDirectory!() ) {
+                    const arr = await this.getFiles(absoluteBasePath, item.path, options, []);
+                    files = files.concat(arr);
+                } else if ( stat.isFile!() ) {
+                    files.push(stat);
+                }
+            }));
+        }
+        return files.filter((item) => {
+            if ( filter && typeof filter === 'function' ) {
+                return filter(item);
+            }
+            return true;
+        });
+    }
+
+    private async getStat(filepath: string, absoluteRootPath: string, options: RecursiveReaddirFilesOptions): Promise<IFileDirStat> {
+        const stat = (await fs.promises.stat(filepath)) as IFileDirStat;
+        stat.ext = '';
+        if ( stat.isFile!() ) {
+            stat.ext = this.getExt(filepath);
+            stat.name = path.basename(filepath);
+            stat.path = path.resolve(filepath);
+        }
+
+        if ( options.replacePathByRelativeOne && stat.path ) {
+            stat.path = stat.path.replace(absoluteRootPath, '');
+        }
+
+        if ( options.liteStats ) {
+            delete stat.dev;
+            delete stat.nlink;
+            delete stat.uid;
+            delete stat.gid;
+            delete stat.rdev;
+            delete stat.blksize;
+            delete stat.ino;
+            delete stat.blocks;
+            delete stat.atimeMs;
+            delete stat.mtimeMs;
+            delete stat.ctimeMs;
+            delete stat.birthtimeMs;
+            delete stat.atime;
+            //delete stat.mtime;
+            delete stat.ctime;
+            //delete stat.birthtime;
+            //delete stat.mode;
+        }
+
+        return stat;
+    };
+
+    /**
+     * Get ext
+     * @param {String} filePath `/a/b.jpg` => `jpg`
+     */
+    private getExt(filePath: string): string {
+        return path.extname(filePath).replace(/^\./, '').toLowerCase();
+    }
+}
+
+
+export default new RecursiveFilesStats();
-- 
GitLab