Skip to content

Commit

Permalink
feat(api): Add new several API options
Browse files Browse the repository at this point in the history
Here is the list new options that just being added in this change:
  - absolute?: { boolean } - Whether to return absolute paths for all entries.
  - basename? { boolean } - Whether to makes the returned entries only have their file and/or directories names.
  - rootDir?: { StringPath } - A string path represents the root directory to resolve the relative paths.

They all were have their own priority tier, in the list above their priority are sorted from the highest (top) to the lowest (bottom). So, if you specified the `rootDir` but you also enabled the `absolute` option, then -- as a result, -- the `absolute` will be used instead (it is because the `absolute` option have the highest priority) and the `rootDir` value will be ignored.

Please note, that the `basename` option are implicitly includes any directory names while listing a directory. If you want include the filenames only, then combining it with the `lsTypes.LS_F` is a good way if you are using `ls` function, or combine this option with `lsFiles` function for more flexible.
  • Loading branch information
mitsuki31 committed Apr 25, 2024
1 parent 17d99bb commit f578589
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 19 deletions.
40 changes: 28 additions & 12 deletions src/lsfnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const defaultLsOptions: DefaultLsOptions = {
match: /.+/,
exclude: undefined,
rootDir: process.cwd(),
absolute: false,
basename: false
} as const;

Expand Down Expand Up @@ -128,6 +129,7 @@ function resolveOptions(options: LsOptions | null | undefined): ResolvedLsOption
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
});
}
Expand Down Expand Up @@ -211,7 +213,8 @@ export async function ls(
options?: LsOptions | RegExp | undefined,
type?: LsTypes | undefined
): Promise<LsResult> {
let absdirpath: StringPath;
let absdirpath: StringPath,
reldirpath: StringPath;

if (dirpath instanceof URL) {
if (dirpath.protocol !== 'file:') {
Expand All @@ -229,24 +232,25 @@ export async function ls(
throw new TypeError('Unknown type, expected a string or an URL object');
}

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

if (isRegExp(options)) {
// 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(<Omit<(typeof options), 'RegExp'>> options);
options = <LsOptions> resolveOptions(options);
} else {
throw new TypeError("Unknown type of 'options': "
+ (Array.isArray(options) ? 'array' : typeof options));
}

// Resolve its absolute and relative path
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 @@ -261,7 +265,7 @@ export async function ls(
// Filter the entries
result = await Promise.all(
entries.map(async function (entry: StringPath): Promise<(StringPath | null)> {
entry = path.join(<StringPath> dirpath, entry);
entry = path.join(absdirpath, entry);
const stats: fs.Stats = await fs.promises.stat(entry);
let resultType: boolean = false;

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

return (
return ((
resultType && (
(<RegExp> options.match).test(entry)
&& (options.exclude ? !(<RegExp> options.exclude).test(entry) : true)
)
) ? entry : null;
)
? (
// *** 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))
)
: null
)
})
).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
119 changes: 112 additions & 7 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,34 +85,138 @@ export declare interface LsTypesInterface {

/**
* This interface defines the optional configuration options that can be passed
* to the `ls*` function. These options control the behavior of the file listing.
* to every `ls*` functions. These options control the behavior of the directory listing.
*
* @interface
* @since 0.1.0
*/
export declare interface LsOptions {
/**
* Specifies the character encoding to be used when reading a directory.
* @defaultValue `'utf8'`
* @since 0.1.0
*/
encoding?: BufferEncoding | undefined;
/**
* A boolean flag indicating whether to include subdirectories in the listing.
* @defaultValue `false`
* @since 0.1.0
*/
recursive?: boolean | undefined;
/**
* A regular expression or string pattern used to filter the listed entries.
* Only entries matching the pattern will be included in the results.
* @defaultValue `/.+/` (match all files)
* @since 0.1.0
*/
match?: RegExp | string | undefined;
/**
* A regular expression or string pattern used to exclude entries from the
* listing. Any entries matching this pattern will be filtered out of the
* results, even if they match the {@link match} pattern.
* @defaultValue `undefined`
* @since 0.1.0
*/
exclude?: RegExp | string | undefined;
/**
* A string path representing the root directory to resolve the results to
* relative paths.
*
* This option will be ignored if either one of the {@link absolute `absolute`}
* or {@link basename `basename`} option are enabled, this is due to their
* higher priority. This option have the lowest priority when compared with those
* options.
*
* @defaultValue `'.'` or `process.cwd()`
* @since 1.0.0
*/
rootDir?: StringPath | undefined;
/**
* Determines whether to return absolute paths for all entries.
*
* When enabled (i.e., set to `true`), each entry of the returned results
* will be an absolute path. Otherwise, paths will be relative to the directory
* specified in the {@link rootDir `rootDir`} field.
*
* Enabling this option are equivalent with the following code.
* Let's assume we want to list all files within a directory named `'foo'`:
*
* ```js
* const { resolve } = require('node:path');
* const { lsFiles } = require('lsfnd');
* // Or ESM:
* // import { resolve } from 'node:path';
* // import { lsFiles } from 'lsfnd';
*
* const absfiles = (await lsFiles('foo/')).map((entry) => resolve(entry));
* ```
*
* In previous release (prior to version 0.1.0) you can literally use an
* explicit method that makes the returned results as absolute paths entirely.
* That is by utilizing the `path.resolve` function, here is an example:
*
* ```js
* const absfiles = await lsFiles(path.resolve('foo/'));
* ```
*
* In the above code, the directory path is resolved to an absolute path before
* being passed to the {@link !lsfnd~lsFiles `lsFiles`} function. As a result,
* the function treats the specified directory path as a relative path and
* does not attempt to resolve it back to a relative path, thus returning
* absolute paths. This approach was considered unstable and problematic due
* to inconsistencies in the internal logic. Therefore, this option was
* introduced as a replacement and will default returns relative paths when
* this option are disabled (set to `false` or unspecified), they will relative
* to the path specified in the {@link rootDir `rootDir`} field. Refer to
* {@link rootDir `rootDir`} option for more details.
*
* @defaultValue `false`
* @since 1.0.0
* @see {@link rootDir}
*/
absolute?: boolean | undefined;
/**
* Whether to make the returned result paths only have their basenames, trimming any
* directories behind the path separator (i.e., `\\` in Windows, and `/` in POSIX).
*
* When set to `true`, the returned paths will only include the file and/or
* directory names itself. This can be useful if you need only the names while
* listing a directory.
*
* If you enabled both this option and the {@link absolute `absolute`} option,
* the `absolute` option will be treated instead due to its higher priority rather
* than this option's priority.
*
* > ### Note
* > Please note, that this option implicitly includes any directories on the
* > returned entries. If you want to only include the filenames, then
* > combine this option with {@link !lsTypes~lsTypes.LS_F `LS_F`} type if you
* > are using {@link !lsfnd~ls `ls`} function, or use this option with
* > {@link !lsfnd~lsFiles `lsFiles`} function for better flexibility.
*
* This option achieves the same functionality as the following code:
*
* ```js
* const path = require('node:path');
* // Or ESM:
* // import * as path from 'node:path';
*
* // Assume you have `results` variable contains all files paths
* // from listing a directory using `lsFiles` function
* const baseResults = results.map((res) => res.split(path.sep).pop());
* ```
*
* Or even a bit more simple like this:
* ```js
* // ...
* const baseResults = results.map((res) => path.basename(res));
* ```
*
* @defaultValue `false`
* @since 1.0.0
* @see {@link rootDir}
*/
basename?: boolean | undefined;
}

/**
Expand Down Expand Up @@ -141,12 +245,13 @@ export declare type ResolvedLsOptions = {
* @see {@link !lsfnd~defaultLsOptions defaultLsOptions}
*/
export declare interface DefaultLsOptions {
encoding: 'utf8';
recursive: false;
match: RegExp;
exclude: undefined;
rootDir: StringPath;
basename: false;
readonly encoding: 'utf8';
readonly recursive: false;
readonly match: RegExp;
readonly exclude: undefined;
readonly rootDir: StringPath;
readonly absolute: false;
readonly basename: false;
}

// ====== APIs ===== //
Expand Down

0 comments on commit f578589

Please sign in to comment.