@thingts/path
Version:
Type-safe, ergonomic package for working with paths in any javascript environment
376 lines (370 loc) • 14 kB
TypeScript
declare abstract class FilenameBase {
/** @private */
/** @hidden Implemented by subclasses to hold the normalized path string. */
protected abstract filename_: string;
/** @hidden Implemented by subclasses to create a new instance with the given filename. */
protected abstract withFilename(filename: string): this;
/** Returns the filename as a string. */
abstract toString(): string;
/** Returns true if this filename equals the other filename or string. */
abstract equals(other: string | FilenameBase): boolean;
/**
* Returns the extension of the filename including the leading dot, as a
* string. If the filename has no extension, returns an empty string.
*
* Note that if the filename starts with a dot (e.g. `.gitignore`),
* that dot is considered part of the stem. So `.gitignore` has
* extension `''` and `.gitignore.bak` has extension `.bak`
*/
get extension(): string;
/**
* Returns the stem of the filename, i.e. the part before the extension.
* If the filename has no extension, returns the entire filename
*
* Note that if the filename starts with a dot (e.g. `.gitignore`),
* that dot is considered part of the stem. So `.gitignore` and
* `.gitignore.bak` both have stem `.gitignore`
*/
get stem(): string;
/**
* Replace the filename stem, keeping the extension the same
*
* @returns A new {@link Filename} instance
*
* @example
* ```ts
* new Filename('index.ts').replaceStem('main') // 'main.ts' (Filename)
* ```
*/
replaceStem(newStem: string): this;
/**
* Replace the filename extensions, keeping the stem the same
*
* @returns A new {@link Filename} instance
*
* @example
* ```ts
* new Filename('index.ts').replaceExtension('.js') // 'index.js' (Filename)
* ```
*/
replaceExtension(newExt: string): this;
}
/**
* Represents a filename, without any path components, an provides methods
* to query and manipulate it.
*
* The class is immutable; all methods that modify the filename return a
* new instance.
*
* The constructor ensures that the filename does not contain any directory
* separators. If it does, an error is thrown.
*
* @example
* ```ts
* const file = new Filename('example.txt')
* console.log(file.stem) // 'example'
* ```
*/
declare class Filename extends FilenameBase {
protected filename_: string;
/**
* Create a {@link Filename} instance from a string or another {@link Filename}
*
* Throws an error if the provided name contains path separators
*
* @example
* ```ts
* new Filename('index.ts') // OK
* new Filename('demo/index.ts') // Throws Error
* ```
*/
constructor(filename: string | Filename);
/**
* Creates a new {@link Filename} by calling a callback function with the
* old filename as a string, and using the returned string as the new
* filename
*
* @returns A new {@link Filename} instance
*/
transform(fn: (filename: string) => string): this;
/**
* Returns true if the provided string is a valid filename (i.e. does not
* contain any path separators)
*/
static isFilenameString(filename: string): boolean;
toString(): string;
equals(other: string | Filename): boolean;
protected withFilename(filename: string): this;
}
declare abstract class PathBase extends FilenameBase {
/** @hidden Implemented by subclasses to hold the normalized path string. */
protected abstract path_: string;
/**
* Protected factory to construct a new instance of the current class, with
* the given path.
*
* Used by all mutation-like methods to return a new instance of the same
* class, allowing derived classes that inherit those methods to return new
* instances of themselves without needing to override them.
*
* The default implementation assumes the derived class's constructor takes
* a single string argument (the path). Derived classes with different
* constructor siguatures should override {@link newSelf}.
*/
protected newSelf(path: string | FilenameBase): this;
/**
* The filename component (last path segment) as a {@link Filename}.
*
* @example
* ```ts
* new AbsolutePath('/a/b/c.txt').filename // 'c.txt' (Filename)
* new RelativePath('a/b/c.txt').filename // 'c.txt' (Filename)
* ```
*/
get filename(): Filename;
/**
* The parent directory of this path.
*
* @returns A new path instance pointing to the parent.
* @example
* ```ts
* new AbsolutePath('/a/b/c.txt').parent // '/a/b' (AbsolutePath)
* new RelativePath('a/b/c.txt').parent // 'a/b' (RelativePath)
* ```
*/
get parent(): this;
/**
* Join additional path segments to this path.
*
* Accepts strings or path objects; `null` and `undefined` are ignored.
* The resulting path is normalized.
*
* @returns A new path instance with the segments appended
*
* @example
* ```ts
* const a1 = new AbsolutePath('/project/demo')
* const a2 = a1.join('demo1/src', 'index.js') // '/project/demo/demo1/src/index.js'
* a2 instanceof AbsolutePath // true
*
* const r1 = new RelativePath('demo')
* const r2 = r1.join('demo1/src', 'index.js') // 'demo/demo1/src/index.js'
* r2 instanceof RelativePath // true
* ```
*/
join(...segments: readonly (string | null | undefined | FilenameBase)[]): this;
/**
* Replace the filename (last segment).
*
* @returns A new path with the filename replaced.
*/
replaceFilename(newFilename: string | Filename): this;
/**
* Replace the filename stem, keeping the extension the same
*
* @param newStem - New stem to use (extension is preserved).
* @returns A new path with the stem replaced.
* @example
* ```ts
* new AbsolutePath('/a/b/c.txt').replaceStem('d') // '/a/b/d.txt' (AbsolutePath)
* new RelativePath('a/b/c.txt').replaceStem('d') // 'a/b/d.txt' (RelativePath)
* ```
*/
replaceStem(newStem: string): this;
/**
* Replace the filename extension, keeping the stem the same. The passed
* can include or omit the leading dot; if omitted, it will be added.
*
* @param newExt - New extension, e.g. `json` or `.json`
* @returns A new path with the extension replaced.
* @example
* ```ts
* new AbsolutePath('/a/b/c.txt').replaceExtension('json') // '/a/b/c.json' (AbsolutePath)
* new RelativePath('a/b/c.txt').replaceExtension('.json') // '/a/b/c.json' (RelativePath)
* ```
*/
replaceExtension(newExt: string): this;
/**
* Transform the filename via a callback.
*
* @param fn - Receives the current {@link Filename}, returns a new filename
* (string or {@link Filename}).
* @returns A new path with the transformed filename.
* @example
* ```ts
* p.transformFilename(f => f.replaceStem(f.stem + '.bak'))
* ```
*/
transformFilename(fn: (filename: Filename) => string | Filename): this;
/**
* Replace the parent directory while keeping the current filename.
*
* @param newParent - Parent directory as string or another `PathBase`.
* @returns A new path rooted at `newParent` with the same filename.
* @example
* ```ts
* new AbsolutePath('/old/file.txt').replaceParent('/new/dir') // '/new/dir/file.txt' (AbsolutePath)
* new RelativePath('old/file.txt').replaceParent('new/dir') // 'new/dir/file.txt' (RelativePath)
* ```
*/
replaceParent(newParent: string | PathBase): this;
/** Returns the path as string. */
toString(): string;
/** Returns true if this path equals the other path or string */
equals(other: string | PathBase): boolean;
protected get filename_(): string;
protected withFilename(filename: string | Filename): this;
}
/**
* Represents an relative filesystem path (i.e. a path that doesn't start at
* the root, i.e. doesn't have a leading separator) and is not, and provides
* methods for path manipulation and queries.
*
* {@link RelativePath} instances are normalized and immutable.
*
* {@link RelativePath} is similar to {@link AbsolutePath} but
* lacks methods that are only valid for absolute paths.
*
* @example
* ```ts
* const p1 = new Relative('demos')
* const p2 = p1.join('demo1/src', 'index.ts') // 'demos/demo1/src/index.ts'
* ```
*
*/
declare class RelativePath extends PathBase {
protected path_: string;
/**
* Create a new {@link RelativePath} from a string or another {@link RelativePath}.
*
* The path is normalized and guaranteed to be relative. Any trailing
* separator is removed.
*
* Throws an error if the provided path is absolute
*
* @example
* ```ts
* new AbsolutePath('project/demos') // OK
* new AbsolutePath('/project/demos') // Throws Error
* new AbsolutePath('project//src/../demos/') // normalized => project/demos
* ```
*/
constructor(relpath: string | RelativePath);
/**
* Checks whether a string is a relative path. (I.e., if it would be
* acceptable to the {@link RelativePath} constructor.)
*
* @param filepath - The string to check.
* @returns True if the string is an absolute path, otherwise false.
*/
static isRelativePathString(filepath: string): boolean;
}
/**
* Represents an absolute filesystem path (i.e. a path starting at the root, i.e.
* has a leading separator), and provides methods for path resolution,
* manipulation and queries.
*
* {@link AbsolutePath} instances are normalized and immutable.
*
* {@link AbsolutePath} has the same functionality as {@link RelativePath} but
* with additional methods that are only valid for absolute paths: {@link
* resolve}, {@link relativeTo}, and {@link descendsFrom}.
*
* Note that {@link AbsolutePath} provides pure path manipulation, it does
* not access the filesystem in any way. (If you want to work with the
* filesystem, you can use the
* [`@thingts/fs-path`](https://npm.com/package/@thingts/fs-path) library which
* extends {@link AbsolutePath} with filestem operations.)
*
* @example
* ```ts
* const p1 = new AbsolutePath('/project/demos')
* const p2 = p1.join('demo1/src', 'index.ts') // '/project/demos/demo1/src/index.ts'
* console.log(p2.descendsFrom(p1)) // true
* console.log(p2.relativeTo(p1)) // 'demo1/src/index.ts' (RelativePath)
* ```
*
*/
declare class AbsolutePath extends PathBase {
#private;
protected readonly path_: string;
/**
* Create a new {@link AbsolutePath} from a string or another {@link AbsolutePath}.
*
* The path is normalized and guaranteed to be absolute. Any trailing
* separator is removed.
*
* Throws an error if the provided path is not absolute.
*
* @example
* ```ts
* new AbsolutePath('/project/demos') // OK
* new AbsolutePath('project/demos') // Throws Error
* new AbsolutePath('/project//src/../demos/') // normalized => /project/demos
* ```
*/
constructor(path: string | AbsolutePath);
/**
* Resolve additional path segments against this absolute path.
*
* Accepts strings, {@link Filename}, {@link RelativePath}, or {@link AbsolutePath} objects.
* Null and undefined segments are ignored.
*
* Similar to join, except that if any segment is an {@link AbsolutePath} or string
* starting with a path separator, the current path is discarded and
* resolution starts from that segment.
*
* @returns A new {@link AbsolutePath} with the resolved path.
*
* @example
* ```ts
* const p1 = new AbsolutePath('/project/demos')
* const p2 = p1.resolve('demo1/src', 'index.ts') // '/project/demos/demo1/src/index.ts'
* const p3 = p1.resolve('/etc/config') // '/etc/config' (resets to absolute path)
* ```
*/
resolve(...segments: readonly (string | Filename | RelativePath | AbsolutePath | null | undefined)[]): this;
/**
* Compute the relative path from the given base path to this path.
*
* @param base - The base absolute path.
* @returns A {@link RelativePath} that goes from `base` to `this`.
*
* @example
* ```ts
* const p1 = new AbsolutePath('/project/demo')
* const p2 = new AbsolutePath('/project/demo/src/index.ts')
* const rel = p2.relativeTo(p1) // 'src/index.ts' (RelativePath)
* p1.join(rel).equals(p2) // true
* ```
*/
relativeTo(base: AbsolutePath): RelativePath;
/**
* Test whether this path is a descendant of the given ancestor path.
*
* @param ancestor - An `AbsolutePath` or string to check against.
* @param opts.includeSelf - If true, return true when the paths are identical.
* @returns True if this path descends from the ancestor, otherwise false.
*
* @example
* ```ts
* const p1 = new AbsolutePath('/project/demo')
* const p2 = new AbsolutePath('/project/demo/src/index.ts')
* console.log(p2.descendsFrom(p1)) // true
* console.log(p1.descendsFrom(p1)) // false
* console.log(p1.descendsFrom(p1, { includeSelf: true })) // true
* ```
*/
descendsFrom(ancestor: AbsolutePath | string, opts?: {
includeSelf?: boolean;
}): boolean;
/**
* Checks whether a string is an absolute path. (I.e., if it would be
* acceptable to the {@link AbsolutePath} constructor.)
*
* @param filepath - The string to check.
* @returns True if the string is an absolute path, otherwise false.
*/
static isAbsolutePathString(filepath: string): boolean;
}
export { AbsolutePath, Filename, RelativePath };