Skip to content

Commit

Permalink
feat(api)!: Expand APIs with new customizable options and enhanced fu…
Browse files Browse the repository at this point in the history
…nctionality (#6)

These changes introduces several enhancements and additions to the LSFND package, focusing on enhancing APIs usage by introducing some additional options, and refining type declarations to more robust and compatible with these changes and future changes.

Signed-off-by: Ryuu Mitsuki <dhefam31@gmail.com>
  • Loading branch information
mitsuki31 committed Apr 26, 2024
2 parents 0403f2f + d381781 commit a274276
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 63 deletions.
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
export * from './lsTypes';
export * from './lsfnd';
export type {
StringPath,
LsTypes,
LsTypesInterface,
LsTypesKeys,
LsTypesValues,
LsOptions,
ResolvedLsOptions,
DefaultLsOptions,
LsEntries,
LsResult
} from '../types';
131 changes: 94 additions & 37 deletions src/lsfnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,34 @@ import { isRegExp } from 'node:util';
import { URL } from 'node:url';
import { lsTypes } from './lsTypes';
import type {
StringPath,
LsEntries,
LsResult,
LsOptions,
ResolvedLsOptions,
DefaultLsOptions,
LsTypes
} from '../types';

type Unpack<A> = A extends Array<(infer U)> ? U : A;

/**
* An object containing all default values of {@link LsOptions `LsOptions`} type.
*
* @since 1.0.0
* @see {@link DefaultLsOptions}
* @see {@link LsOptions}
*/
export const defaultLsOptions: DefaultLsOptions = {
encoding: 'utf8',
recursive: false,
match: /.+/,
exclude: undefined,
rootDir: process.cwd(),
absolute: false,
basename: false
} as const;

/**
* Converts a file URL to a file path.
*
Expand Down Expand Up @@ -56,7 +76,7 @@ type Unpack<A> = A extends Array<(infer U)> ? U : A;
*
* @internal
*/
function fileUrlToPath(url: URL | string): string {
function fileUrlToPath(url: URL | StringPath): StringPath {
if ((url instanceof URL && url.protocol !== 'file:')
|| (typeof url === 'string' && !/^file:(\/\/?|\.\.?\/*)/.test(url))) {
throw new URIError('Invalid URL file scheme');
Expand Down Expand Up @@ -99,15 +119,39 @@ function checkType<N extends null | undefined>(
validTypes.forEach((validType: Unpack<(typeof validTypes)>) => {
if (!match && type === validType) match = true;
});

if (!match) {
throw new TypeError(
`Invalid 'type' value of ${type} ('${typeof type}'). Valid type is "${
`Invalid 'type' value of ${<string> type} ('${typeof type}'). Valid type is "${
joinAll(validTypes.sort(), ' | ')
}"`);
}
return;
}

/**
* Resolves the given `options` ({@link LsOptions}).
*
* @param options - An object represents the options to be resolved. Set to `null`
* or `undefined` to gets the default options.
* @returns A new object represents the resolved options. Returns the default
* options if the `options` parameter not specified or `null`.
*
* @since 1.0.0
* @internal
*/
function resolveOptions(options: LsOptions | null | undefined): ResolvedLsOptions {
return <ReturnType<(typeof resolveOptions)>> (!options ? defaultLsOptions : {
encoding: options?.encoding || defaultLsOptions.encoding,
recursive: options?.recursive || defaultLsOptions.recursive,
match: options?.match || defaultLsOptions.match,
exclude: options?.exclude || defaultLsOptions.exclude,
rootDir: options?.rootDir || defaultLsOptions.rootDir,
absolute: options?.absolute || defaultLsOptions.absolute,
basename: options?.basename || defaultLsOptions.basename
});
}

/**
* Lists files and/or directories in a specified directory path, filtering by a
* regular expression pattern.
Expand Down Expand Up @@ -183,13 +227,12 @@ function checkType<N extends null | undefined>(
* @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback fs.readdir}
*/
export async function ls(
dirpath: string | URL,
dirpath: StringPath | URL,
options?: LsOptions | RegExp | undefined,
type?: LsTypes | undefined
): Promise<LsResult> {
let absdirpath: string;
let match: string | RegExp,
exclude: string | RegExp | undefined;
let absdirpath: StringPath,
reldirpath: StringPath;

if (dirpath instanceof URL) {
if (dirpath.protocol !== 'file:') {
Expand All @@ -204,33 +247,35 @@ export async function ls(
dirpath = fileUrlToPath(dirpath);
}
} else {
throw new Error('Unknown type, expected a string or an URL object');
throw new TypeError('Unknown type, expected a string or an URL object');
}

// Resolve its absolute path
absdirpath = path.isAbsolute(<string> dirpath)
? <string> dirpath
: path.posix.resolve(<string> dirpath);

if (isRegExp(options)) {
match = options;
exclude = undefined;
options = { encoding: 'utf8', recursive: false };
} else if (typeof options === 'undefined' || options === null) {
options = { encoding: 'utf8', recursive: false };
match = /.+/;
} else if (options && typeof options === 'object' && !Array.isArray(options)) {
match = (typeof options!.match === 'string')
? new RegExp(options!.match)
: (isRegExp(options!.match) ? options!.match : /.+/);
exclude = (typeof options!.exclude === 'string')
? new RegExp(options!.exclude)
: (isRegExp(options!.exclude) ? options!.exclude : undefined);
// Store the regex value of `options` to temporary variable for `match` option
const temp: RegExp = new RegExp(options.source) || options;
options = <LsOptions> resolveOptions(null); // Use the default options
(<LsOptions> options)!.match = temp; // Reassign the `match` field
} else if (!options || (typeof options === 'object' && !Array.isArray(options))) {
// Resolve the options, even it is not specified
options = <LsOptions> resolveOptions(options);
} else {
throw new TypeError('Unknown type of "options": '
throw new TypeError("Unknown type of 'options': "
+ (Array.isArray(options) ? 'array' : typeof options));
}

// Check and resolve the `rootDir` option
if (options.rootDir && (options.rootDir instanceof URL
|| (typeof options.rootDir === 'string' && /^[a-zA-Z]+:/.test(options.rootDir))
)) {
options.rootDir = fileUrlToPath(options.rootDir);
}

// Resolve the absolute and relative of the dirpath argument
absdirpath = path.isAbsolute(<StringPath> dirpath)
? <StringPath> dirpath
: path.posix.resolve(<StringPath> dirpath);
reldirpath = path.relative(options.rootDir! || process.cwd(), absdirpath);;

// Check the type argument
checkType(type!, [ ...Object.values(lsTypes), 0, null, undefined ]);

Expand All @@ -244,8 +289,8 @@ export async function ls(

// Filter the entries
result = await Promise.all(
entries.map(async function (entry: string): Promise<(string | null)> {
entry = path.join(<string> dirpath, entry);
entries.map(async function (entry: StringPath): Promise<(StringPath | null)> {
entry = path.join(absdirpath, entry);
const stats: fs.Stats = await fs.promises.stat(entry);
let resultType: boolean = false;

Expand All @@ -261,18 +306,30 @@ export async function ls(
default: resultType = (stats.isFile() || stats.isDirectory());
}

return (
return ((
resultType && (
(<RegExp> match).test(entry)
&& (exclude ? !(<RegExp> exclude).test(entry) : true)
(<RegExp> options.match).test(entry)
&& (options.exclude ? !(<RegExp> options.exclude).test(entry) : true)
)
)
? (
// *** High priority
(options.absolute && (options.basename || !options.basename))
? entry // already an absolute path
// *** Medium priority
: (!options.absolute && options.basename)
? path.basename(entry) // get its basename
// *** Low priority
// convert back to the relative path
: path.join(reldirpath, path.relative(absdirpath, entry))
)
) ? entry : null;
: null
)
})
).then(function (results: Array<string | null>): LsEntries {
).then(function (results: (Unpack<LsEntries> | null)[]): LsEntries {
return <LsEntries> results.filter(
function (entry: Unpack<(typeof results)>): boolean {
// Remove any null entries
return !!entry!;
return !!entry!; // Remove any null entries
}
);
});
Expand Down Expand Up @@ -349,7 +406,7 @@ export async function ls(
* @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback fs.readdir}
*/
export async function lsFiles(
dirpath: string | URL,
dirpath: StringPath | URL,
options?: LsOptions | RegExp | undefined
): Promise<LsResult> {
return ls(dirpath, options, lsTypes.LS_F);
Expand Down Expand Up @@ -420,7 +477,7 @@ export async function lsFiles(
* @see {@link https://nodejs.org/api/fs.html#fsreaddirpath-options-callback fs.readdir}
*/
export async function lsDirs(
dirpath: string | URL,
dirpath: StringPath | URL,
options?: LsOptions | RegExp | undefined
): Promise<LsResult> {
return ls(dirpath, options, lsTypes.LS_D);
Expand Down
6 changes: 3 additions & 3 deletions test/lsfnd.spec.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ const rootDirPosix = path.posix.resolve('..');
console.log(`\n\x1b[1m${path.basename(__filename)}:\x1b[0m`);

it('test `ls` function by listing this file directory', async () => {
const results = await ls(__dirname, {}, 0);
const results = await ls(__dirname, { absolute: true }, 0);
const expected = [ 'lib', 'lsfnd.spec.cjs', 'lsfnd.spec.mjs' ]
.map((e) => path.join(__dirname, e));
deepEq(results, expected);
}, false);

it('test `lsFiles` function by listing this file directory', async () => {
const results = await lsFiles(__dirname);
const results = await lsFiles(__dirname, { absolute: true });
const expected = [ 'lsfnd.spec.cjs', 'lsfnd.spec.mjs' ]
.map((e) => path.join(__dirname, e));
deepEq(results, expected);
}, false);

it('test `lsDirs` function by listing this file directory', async () => {
const results = await lsDirs(__dirname);
const results = await lsDirs(__dirname, { absolute: true });
const expected = [ 'lib' ].map((e) => path.join(__dirname, e));
deepEq(results, expected);
}, false);
Expand Down
6 changes: 3 additions & 3 deletions test/lsfnd.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ const rootDirPosix = path.posix.resolve('..');
console.log(`\n\x1b[1m${path.basename(__filename)}:\x1b[0m`);

it('test `ls` function by listing this file directory', async () => {
const results = await ls(__dirname, {}, 0);
const results = await ls(__dirname, { absolute: true }, 0);
const expected = [ 'lib', 'lsfnd.spec.cjs', 'lsfnd.spec.mjs' ]
.map((e) => path.join(__dirname, e));
deepEq(results, expected);
}, false);

it('test `lsFiles` function by listing this file directory', async () => {
const results = await lsFiles(__dirname);
const results = await lsFiles(__dirname, { absolute: true });
const expected = [ 'lsfnd.spec.cjs', 'lsfnd.spec.mjs' ]
.map((e) => path.join(__dirname, e));
deepEq(results, expected);
}, false);

it('test `lsDirs` function by listing this file directory', async () => {
const results = await lsDirs(__dirname);
const results = await lsDirs(__dirname, { absolute: true });
const expected = [ 'lib' ].map((e) => path.join(__dirname, e));
deepEq(results, expected);
}, false);
Expand Down
Loading

0 comments on commit a274276

Please sign in to comment.