Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Expand APIs with new customizable options and enhanced functionality #6

Merged
merged 7 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading