update
This commit is contained in:
246
ts/tiny-readdir/index.ts
Executable file
246
ts/tiny-readdir/index.ts
Executable file
@ -0,0 +1,246 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {isFunction, makeCounterPromise} from './utils.js';
|
||||
import type {Options, ResultDirectory, ResultDirectories, Result} from './types.js';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
//TODO: Streamline the type of dirnmaps
|
||||
|
||||
const readdir = ( rootPath: string, options?: Options ): Promise<Result> => {
|
||||
|
||||
const followSymlinks = options?.followSymlinks ?? false;
|
||||
const maxDepth = options?.depth ?? Infinity;
|
||||
const maxPaths = options?.limit ?? Infinity;
|
||||
const ignore = options?.ignore ?? (() => false);
|
||||
const isIgnored = isFunction ( ignore ) ? ignore : ( targetPath: string ) => ignore.test ( targetPath );
|
||||
const signal = options?.signal ?? { aborted: false };
|
||||
const directories: string[] = [];
|
||||
const directoriesNames: Set<string> = new Set ();
|
||||
const directoriesNamesToPaths: Record<string, string[]> = {};
|
||||
const files: string[] = [];
|
||||
const filesNames: Set<string> = new Set ();
|
||||
const filesNamesToPaths: Record<string, string[]> = {};
|
||||
const symlinks: string[] = [];
|
||||
const symlinksNames: Set<string> = new Set ();
|
||||
const symlinksNamesToPaths: Record<string, string[]> = {};
|
||||
const map: ResultDirectories = {};
|
||||
const visited = new Set<string> ();
|
||||
const resultEmpty: Result = { directories: [], directoriesNames: new Set (), directoriesNamesToPaths: {}, files: [], filesNames: new Set (), filesNamesToPaths: {}, symlinks: [], symlinksNames: new Set (), symlinksNamesToPaths: {}, map: {} };
|
||||
const result: Result = { directories, directoriesNames, directoriesNamesToPaths, files, filesNames, filesNamesToPaths, symlinks, symlinksNames, symlinksNamesToPaths, map };
|
||||
const {promise, increment, decrement} = makeCounterPromise ();
|
||||
|
||||
let foundPaths = 0;
|
||||
|
||||
const handleDirectory = ( dirmap: ResultDirectory, subPath: string, name: string, depth: number ): void => {
|
||||
|
||||
if ( visited.has ( subPath ) ) return;
|
||||
|
||||
if ( foundPaths >= maxPaths ) return;
|
||||
|
||||
foundPaths += 1;
|
||||
dirmap.directories.push ( subPath );
|
||||
dirmap.directoriesNames.add ( name );
|
||||
// dirmap.directoriesNamesToPaths.propertyIsEnumerable(name) || ( dirmap.directoriesNamesToPaths[name] = [] );
|
||||
// dirmap.directoriesNamesToPaths[name].push ( subPath );
|
||||
directories.push ( subPath );
|
||||
directoriesNames.add ( name );
|
||||
directoriesNamesToPaths.propertyIsEnumerable(name) || ( directoriesNamesToPaths[name] = [] );
|
||||
directoriesNamesToPaths[name].push ( subPath );
|
||||
visited.add ( subPath );
|
||||
|
||||
if ( depth >= maxDepth ) return;
|
||||
|
||||
if ( foundPaths >= maxPaths ) return;
|
||||
|
||||
populateResultFromPath ( subPath, depth + 1 );
|
||||
|
||||
};
|
||||
|
||||
const handleFile = ( dirmap: ResultDirectory, subPath: string, name: string ): void => {
|
||||
|
||||
if ( visited.has ( subPath ) ) return;
|
||||
|
||||
if ( foundPaths >= maxPaths ) return;
|
||||
|
||||
foundPaths += 1;
|
||||
dirmap.files.push ( subPath );
|
||||
dirmap.filesNames.add ( name );
|
||||
// dirmap.filesNamesToPaths.propertyIsEnumerable(name) || ( dirmap.filesNamesToPaths[name] = [] );
|
||||
// dirmap.filesNamesToPaths[name].push ( subPath );
|
||||
files.push ( subPath );
|
||||
filesNames.add ( name );
|
||||
filesNamesToPaths.propertyIsEnumerable(name) || ( filesNamesToPaths[name] = [] );
|
||||
filesNamesToPaths[name].push ( subPath );
|
||||
visited.add ( subPath );
|
||||
|
||||
};
|
||||
|
||||
const handleSymlink = ( dirmap: ResultDirectory, subPath: string, name: string, depth: number ): void => {
|
||||
|
||||
if ( visited.has ( subPath ) ) return;
|
||||
|
||||
if ( foundPaths >= maxPaths ) return;
|
||||
|
||||
foundPaths += 1;
|
||||
dirmap.symlinks.push ( subPath );
|
||||
dirmap.symlinksNames.add ( name );
|
||||
// dirmap.symlinksNamesToPaths.propertyIsEnumerable(name) || ( dirmap.symlinksNamesToPaths[name] = [] );
|
||||
// dirmap.symlinksNamesToPaths[name].push ( subPath );
|
||||
symlinks.push ( subPath );
|
||||
symlinksNames.add ( name );
|
||||
symlinksNamesToPaths.propertyIsEnumerable(name) || ( symlinksNamesToPaths[name] = [] );
|
||||
symlinksNamesToPaths[name].push ( subPath );
|
||||
visited.add ( subPath );
|
||||
|
||||
if ( !followSymlinks ) return;
|
||||
|
||||
if ( depth >= maxDepth ) return;
|
||||
|
||||
if ( foundPaths >= maxPaths ) return;
|
||||
|
||||
populateResultFromSymlink ( subPath, depth + 1 );
|
||||
|
||||
};
|
||||
|
||||
const handleStat = ( dirmap: ResultDirectory, rootPath: string, name: string, stat: fs.Stats, depth: number ): void => {
|
||||
|
||||
if ( signal.aborted ) return;
|
||||
|
||||
if ( isIgnored ( rootPath ) ) return;
|
||||
|
||||
if ( stat.isDirectory () ) {
|
||||
|
||||
handleDirectory ( dirmap, rootPath, name, depth );
|
||||
|
||||
} else if ( stat.isFile () ) {
|
||||
|
||||
handleFile ( dirmap, rootPath, name );
|
||||
|
||||
} else if ( stat.isSymbolicLink () ) {
|
||||
|
||||
handleSymlink ( dirmap, rootPath, name, depth );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handleDirent = ( dirmap: ResultDirectory, rootPath: string, dirent: fs.Dirent, depth: number ): void => {
|
||||
|
||||
if ( signal.aborted ) return;
|
||||
|
||||
const separator = ( rootPath === path.sep ) ? '' : path.sep;
|
||||
const name = dirent.name;
|
||||
const subPath = `${rootPath}${separator}${name}`;
|
||||
|
||||
if ( isIgnored ( subPath ) ) return;
|
||||
|
||||
if ( dirent.isDirectory () ) {
|
||||
|
||||
handleDirectory ( dirmap, subPath, name, depth );
|
||||
|
||||
} else if ( dirent.isFile () ) {
|
||||
|
||||
handleFile ( dirmap, subPath, name );
|
||||
|
||||
} else if ( dirent.isSymbolicLink () ) {
|
||||
|
||||
handleSymlink ( dirmap, subPath, name, depth );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handleDirents = ( dirmap: ResultDirectory, rootPath: string, dirents: fs.Dirent[], depth: number ): void => {
|
||||
|
||||
for ( let i = 0, l = dirents.length; i < l; i++ ) {
|
||||
|
||||
handleDirent ( dirmap, rootPath, dirents[i], depth );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const populateResultFromPath = ( rootPath: string, depth: number ): void => {
|
||||
|
||||
if ( signal.aborted ) return;
|
||||
|
||||
if ( depth > maxDepth ) return;
|
||||
|
||||
if ( foundPaths >= maxPaths ) return;
|
||||
|
||||
increment ();
|
||||
|
||||
fs.readdir ( rootPath, { withFileTypes: true }, ( error, dirents ) => {
|
||||
|
||||
if ( error ) return decrement ();
|
||||
|
||||
if ( signal.aborted ) return decrement ();
|
||||
|
||||
if ( !dirents.length ) return decrement ();
|
||||
|
||||
const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set (), directoriesNamesToPaths: {}, files: [], filesNames: new Set (), filesNamesToPaths: {}, symlinks: [], symlinksNames: new Set (), symlinksNamesToPaths: {} };
|
||||
|
||||
handleDirents ( dirmap, rootPath, dirents, depth );
|
||||
|
||||
decrement ();
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const populateResultFromSymlink = async ( rootPath: string, depth: number ): Promise<void> => {
|
||||
|
||||
increment ();
|
||||
|
||||
fs.realpath ( rootPath, ( error, realPath ) => {
|
||||
|
||||
if ( error ) return decrement ();
|
||||
|
||||
if ( signal.aborted ) return decrement ();
|
||||
|
||||
fs.stat ( realPath, async ( error, stat ) => {
|
||||
|
||||
if ( error ) return decrement ();
|
||||
|
||||
if ( signal.aborted ) return decrement ();
|
||||
|
||||
const name = path.basename ( realPath );
|
||||
const dirmap = map[rootPath] = { directories: [], directoriesNames: new Set (), directoriesNamesToPaths: {}, files: [], filesNames: new Set (), filesNamesToPaths: {}, symlinks: [], symlinksNames: new Set (), symlinksNamesToPaths: {} };
|
||||
|
||||
handleStat ( dirmap, realPath, name, stat, depth );
|
||||
|
||||
decrement ();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const getResult = async ( rootPath: string, depth: number = 1 ): Promise<Result> => {
|
||||
|
||||
rootPath = path.normalize ( rootPath );
|
||||
|
||||
visited.add ( rootPath );
|
||||
|
||||
populateResultFromPath ( rootPath, depth );
|
||||
|
||||
await promise;
|
||||
|
||||
if ( signal.aborted ) return resultEmpty;
|
||||
|
||||
return result;
|
||||
|
||||
};
|
||||
|
||||
return getResult ( rootPath );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default readdir;
|
21
ts/tiny-readdir/license
Normal file
21
ts/tiny-readdir/license
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020-present Fabio Spampinato
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
38
ts/tiny-readdir/types.ts
Normal file
38
ts/tiny-readdir/types.ts
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
/* HELPERS */
|
||||
|
||||
type Callback = () => void;
|
||||
|
||||
/* MAIN */
|
||||
|
||||
type Options = {
|
||||
depth?: number,
|
||||
limit?: number,
|
||||
followSymlinks?: boolean,
|
||||
ignore?: (( targetPath: string ) => boolean) | RegExp,
|
||||
signal?: { aborted: boolean }
|
||||
};
|
||||
|
||||
type ResultDirectory = {
|
||||
directories: string[],
|
||||
directoriesNames: Set<string>,
|
||||
directoriesNamesToPaths: Record<string, string[]>,
|
||||
files: string[],
|
||||
filesNames: Set<string>,
|
||||
filesNamesToPaths: Record<string, string[]>,
|
||||
symlinks: string[],
|
||||
symlinksNames: Set<string>,
|
||||
symlinksNamesToPaths: Record<string, string[]>
|
||||
};
|
||||
|
||||
type ResultDirectories = {
|
||||
[path: string]: ResultDirectory
|
||||
};
|
||||
|
||||
type Result = ResultDirectory & {
|
||||
map: ResultDirectories
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export type {Callback, Options, ResultDirectory, ResultDirectories, Result};
|
43
ts/tiny-readdir/utils.ts
Normal file
43
ts/tiny-readdir/utils.ts
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import makeNakedPromise from '../promise-make-naked/index.js';
|
||||
import type {Callback} from './types.js';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const isFunction = ( value: unknown ): value is Function => {
|
||||
|
||||
return ( typeof value === 'function' );
|
||||
|
||||
};
|
||||
|
||||
const makeCounterPromise = (): { promise: Promise<void>, increment: Callback, decrement: Callback } => {
|
||||
|
||||
const {promise, resolve} = makeNakedPromise<void> ();
|
||||
|
||||
let counter = 0;
|
||||
|
||||
const increment = (): void => {
|
||||
|
||||
counter += 1;
|
||||
|
||||
};
|
||||
|
||||
const decrement = (): void => {
|
||||
|
||||
counter -= 1;
|
||||
|
||||
if ( counter ) return;
|
||||
|
||||
resolve ();
|
||||
|
||||
};
|
||||
|
||||
return { promise, increment, decrement };
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export {isFunction, makeCounterPromise};
|
Reference in New Issue
Block a user