@lifaon/path
Version:
Useful tool to manage paths like the URL object
338 lines • 13.3 kB
JavaScript
import { getProcess } from '../.private/functions/get-process.js';
import { isObject } from '../.private/functions/is-object.js';
import { GENERIC_PATH_PLATFORM_CONFIG } from '../.private/platform-config/constants/generic-path-platform-config.constants.js';
import { POSIX_PATH_PLATFORM_CONFIG } from '../.private/platform-config/constants/posix-path-platform-config.constants.js';
import { WINDOWS_PATH_PLATFORM_CONFIG } from '../.private/platform-config/constants/windows-path-platform-config.constants.js';
import { getCurrentPathPlatformConfig } from '../.private/platform-config/functions/get-current-path-platform-config.js';
import { arePathSegmentsEquivalent } from '../.private/segments/functions/compare/are-path-segments-equivalent.js';
import { convertPathSegmentsToStringPath } from '../.private/segments/functions/convert/convert-path-segments-to-string-path.js';
import { convertStringPathToPathSegments } from '../.private/segments/functions/convert/convert-string-path-to-path-segments.js';
import { convertUncheckedPathSegmentsIntoPathSegments } from '../.private/segments/functions/convert/convert-unchecked-path-segments-into-path-segments.js';
import { getBasenameOfPathSegments } from '../.private/segments/functions/get/get-basename-of-path-segments.js';
import { getCommonBaseOfManyPathSegments } from '../.private/segments/functions/get/get-common-base-of-many-path-segments.js';
import { getDirnameOfPathSegments } from '../.private/segments/functions/get/get-dirname-of-path-segments.js';
import { getProcessPathSegments } from '../.private/segments/functions/get/get-process-path-segments.js';
import { getRelativePathSegments } from '../.private/segments/functions/get/get-relative-path-segments.js';
import { getStemAndExtOfPathSegments } from '../.private/segments/functions/get/get-stem-and-ext-of-path-segments.js';
import { isAbsolutePathSegments } from '../.private/segments/functions/is/is-absolute-path-segments.js';
import { isRootPathSegments } from '../.private/segments/functions/is/is-root-path-segments.js';
import { isSubPathOfPathSegments } from '../.private/segments/functions/is/is-sub-path-of-path-segments.js';
import { joinManyPathSegments } from '../.private/segments/functions/join/join-many-path-segments.js';
import { makePathSegmentsAsAbsolute } from '../.private/segments/functions/make/make-path-segments-as-absolute.js';
import { makePathSegmentsAsRelative } from '../.private/segments/functions/make/make-path-segments-as-relative.js';
import { resolvePathSegmentsWithOptionalRoot } from '../.private/segments/functions/resolve/resolve-path-segments-with-optional-root.js';
import { isPath } from './functions/is-path.js';
let BYPASS_PATH_CONSTRUCT = false;
function createPathFromPathInstance(instance, segments = instance.segments, config = instance.config) {
BYPASS_PATH_CONSTRUCT = true;
const path = new Path(segments, config);
BYPASS_PATH_CONSTRUCT = false;
return path;
}
/* CLASS */
/**
* A class to manipulate Paths.
*/
export class Path {
static get posix() {
return POSIX_PATH_PLATFORM_CONFIG;
}
static get windows() {
return WINDOWS_PATH_PLATFORM_CONFIG;
}
static get generic() {
return GENERIC_PATH_PLATFORM_CONFIG;
}
static get currentPlatform() {
return getCurrentPathPlatformConfig();
}
/**
* Returns the current process working directory as a `Path`.
*/
static process(config) {
return new Path(getProcess().cwd(), config);
}
/**
* If `path` is a `Path`, returns `path`,
* else creates a `Path` from `path`.
* => useful if you want to accept many types as the `path` input of a function without sacrificing performances
*/
static of(path, config) {
return isPath(path) ? path : new Path(path, config);
}
segments;
config;
constructor(path, config) {
if (BYPASS_PATH_CONSTRUCT) {
this.segments = path;
this.config = config;
}
else {
if (config === undefined) {
this.config = isPath(path) ? path.config : GENERIC_PATH_PLATFORM_CONFIG;
}
else if (isObject(config)) {
this.config = Object.isFrozen(config) ? config : Object.freeze({ ...config });
}
else {
throw new TypeError('Expected IPathPlatformConfig or void as second input.');
}
if (typeof path === 'string') {
this.segments = convertStringPathToPathSegments(path, this.config);
}
else if (Array.isArray(path)) {
this.segments = convertUncheckedPathSegmentsIntoPathSegments(path, this.config);
}
else if (isPath(path)) {
this.segments = path.segments.slice();
}
else {
throw new TypeError('Expected string, string[] or Path as first input.');
}
}
}
/* IS */
/**
* Returns `true` if this Path is absolute.
*/
isAbsolute() {
return isAbsolutePathSegments(this.segments, this.config);
}
/**
* Returns `true` if this Path is a pure root (ex: `c:` or `/`).
*/
isRoot() {
return isRootPathSegments(this.segments, this.config);
}
/**
* Returns true if this Path is a sub-path of `path` (after normalization).
*
* @example:
* `new Path('a/b/').isSubPathOf('a/')` => `true`
*/
isSubPathOf(parentPath) {
return isSubPathOfPathSegments(this.segments, Path.of(parentPath, this.config).segments);
}
/* COMPARISON */
/**
* Returns `true` if this Path is equal to `path` (after normalization).
*
* @example:
* `new Path('a/b/').equals('a/c/../b')` => `true`
*/
equals(path) {
return arePathSegmentsEquivalent(this.segments, Path.of(path, this.config).segments);
}
/* GET */
/**
* Returns the parent directory's Path of this Path.
* If this operation cannot be performed (ex: this Path is a "root"), the function throws.
*
* @example:
* `new Path('a/b').dirname()` => `./a`
* `new Path('c:/').dirname()` => throws
*/
dirname() {
const dirname = this.dirnameOptional();
if (dirname === null) {
throw new Error("This path doesn't accept '.dirname()'.");
}
else {
return dirname;
}
}
/**
* Like `.dirname()`, but returns `null` instead of throwing.
*
* @see dirname
*/
dirnameOptional() {
const dirname = getDirnameOfPathSegments(this.segments);
return dirname === null ? null : createPathFromPathInstance(this, dirname);
}
/**
* Returns the basename of this Path:
* - if `ext` is provided, `ext` is removed from the basename
* - the function throws if the basename is special (ex: relative or root) and `allowedSpecialSegments` doesn't include it
*
* @param ext - default: `''`
* @param allowedSpecialSegments - default: `new Set()`
*
* @example:
* `new Path('/a/b').basename()` => 'b'
* `new Path('/').basename()` => throws
*/
basename(ext, allowedSpecialSegments) {
const basename = this.basenameOptional(ext, allowedSpecialSegments);
if (basename === null) {
throw new Error("This path doesn't accept '.basename(...)'.");
}
else {
return basename;
}
}
/**
* Like `.basename(...)`, but returns `null` instead of throwing.
*
* @see basename
*/
basenameOptional(ext, allowedSpecialSegments) {
return getBasenameOfPathSegments(this.segments, ext, allowedSpecialSegments === undefined
? this.config
: {
...this.config,
allowedSpecialSegments: new Set(allowedSpecialSegments),
});
}
/**
* Returns a tuple composed of the stem and the extension of the basename of this Path.
* If this operation cannot be performed (ex: this Path is a "root"), the function throws.
*/
stemAndExt() {
const stemAndExt = this.stemAndExtOptional();
if (stemAndExt === null) {
throw new Error("This path doesn't accept '.stemAndExt(...)'.");
}
else {
return stemAndExt;
}
}
/**
* Like `.stemAndExt(...)`, but returns `null` instead of throwing.
*
* @see stemAndExt
*/
stemAndExtOptional() {
return getStemAndExtOfPathSegments(this.segments, this.config);
}
/**
* Returns the common base between this Path, and each `paths`:
* - if no common base are found, the function throws.
*
* @example:
* `new Path('a/b/').commonBase('a/c')` => `./a`
* `new Path('/a/b/').commonBase('d/e')` => throws
*/
commonBase(...paths) {
const commonBase = this.commonBaseOptional(...paths);
if (commonBase === null) {
throw new Error('These paths have no common bases.');
}
else {
return commonBase;
}
}
/**
* Like `.commonBase(...)`, but returns `null` instead of throwing.
* @see commonBase
*/
commonBaseOptional(...paths) {
const commonBase = getCommonBaseOfManyPathSegments([
this.segments,
...paths.map((path) => Path.of(path, this.config).segments),
]);
return commonBase === null ? null : createPathFromPathInstance(this, commonBase);
}
/**
* Returns the relative Path from this Path to `path` (after normalization)
* - the function throw if it's not possible to reach `path` from this Path.
*
* @example:
* `new Path('a/b/').relative('a/d')` => `../d`
* `new Path('a/b/').relative('/a/d')` => throws
*/
relative(path) {
const relativePath = this.relativeOptional(path);
if (relativePath === null) {
throw new Error('Cannot reach `path` from this Path.');
}
else {
return relativePath;
}
}
/**
* Like `.relative(...)`, but returns `null` instead of throwing.
* @see relative
*/
relativeOptional(path) {
const relativePath = getRelativePathSegments(this.segments, Path.of(path, this.config).segments, this.config);
return relativePath === null ? null : createPathFromPathInstance(this, relativePath);
}
/* MISC */
/**
* Returns a new Path composed of this Path followed by 'paths'
* - equivalent of path.join() of NodeJS
*
* @example:
* - `new Path('./a').concat('b')` => `./a/b`
*/
concat(...paths) {
return createPathFromPathInstance(this, joinManyPathSegments([
this.segments,
...paths.map((path) => Path.of(path, this.config).segments),
], this.config));
}
/**
* Returns a new absolute Path from this Path:
* - if this Path is absolute, this function returns a cloned path,
* - else it appends `root` before this Path
*
* @param root - default: `process.cwd()`
*/
resolve(root) {
return createPathFromPathInstance(this, resolvePathSegmentsWithOptionalRoot(this.segments, root === undefined ? undefined : Path.of(root, this.config).segments, this.config));
}
/**
* Clones the path. Kind of new Path(this, config) but faster.
*/
clone(config) {
return createPathFromPathInstance(this, this.segments.slice(), config);
}
/* CONVERT */
/**
* Forces this Path to be converted to an absolute Path IF it is not already absolute.
*
* @param root - default: `process.cwd()`
*/
makeAbsolute(root) {
const _root = root === undefined
? getProcessPathSegments(this.config)
: Path.of(root, this.config).segments;
return createPathFromPathInstance(this, makePathSegmentsAsAbsolute(this.segments, _root[0], this.config));
}
/**
* Forces this Path to be converted to a relative path IF it is not already relative.
* => it replaces Path's first segment (the root) with '.'
*/
makeRelative() {
return createPathFromPathInstance(this, makePathSegmentsAsRelative(this.segments, this.config));
}
/* TO */
/**
* Returns the concatenated string of the different segments of this Path, separated by `separa
* tor`.
* @param separator - default: `config.separator`
*/
toString(separator) {
let config;
if (separator === undefined) {
config = this.config;
}
else if (typeof separator === 'string') {
config = separator === this.config.separator ? this.config : { ...this.config, separator };
}
else {
throw new TypeError('Expected string as separator');
}
return convertPathSegmentsToStringPath(this.segments, config);
}
/**
* Returns a 'file://' url having this Path as pathname
*/
toURL() {
const url = new URL('file://');
url.pathname = this.toString('/');
return url;
}
}
//# sourceMappingURL=path.class.js.map