@rushstack/lookup-by-path
Version:
Strongly typed trie data structure for path and URL-like strings.
301 lines • 11.2 kB
TypeScript
/**
* Readonly view of a node in the path trie used in LookupByPath
*
* @remarks
* This interface is used to facilitate parallel traversals for comparing two `LookupByPath` instances.
*
* @beta
*/
export interface IReadonlyPathTrieNode<TItem extends {}> {
/**
* The value that exactly matches the current relative path
*/
readonly value: TItem | undefined;
/**
* Child nodes by subfolder
*/
readonly children: ReadonlyMap<string, IReadonlyPathTrieNode<TItem>> | undefined;
}
/**
* Object containing both the matched item and the start index of the remainder of the query.
*
* @beta
*/
export interface IPrefixMatch<TItem extends {}> {
/**
* The item that matched the prefix
*/
value: TItem;
/**
* The index of the first character after the matched prefix
*/
index: number;
/**
* The last match found (with a shorter prefix), if any
*/
lastMatch?: IPrefixMatch<TItem>;
}
/**
* The readonly component of `LookupByPath`, to simplify unit testing.
*
* @beta
*/
export interface IReadonlyLookupByPath<TItem extends {}> extends Iterable<[string, TItem]> {
/**
* Searches for the item associated with `childPath`, or the nearest ancestor of that path that
* has an associated item.
*
* @returns the found item, or `undefined` if no item was found
*
* @example
* ```ts
* const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]);
* trie.findChildPath('foo/baz'); // returns 1
* trie.findChildPath('foo/bar/baz'); // returns 2
* ```
*/
findChildPath(childPath: string, delimiter?: string): TItem | undefined;
/**
* Searches for the item for which the recorded prefix is the longest matching prefix of `query`.
* Obtains both the item and the length of the matched prefix, so that the remainder of the path can be
* extracted.
*
* @returns the found item and the length of the matched prefix, or `undefined` if no item was found
*
* @example
* ```ts
* const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]);
* trie.findLongestPrefixMatch('foo/baz'); // returns { item: 1, index: 3 }
* trie.findLongestPrefixMatch('foo/bar/baz'); // returns { item: 2, index: 7 }
* ```
*/
findLongestPrefixMatch(query: string, delimiter?: string): IPrefixMatch<TItem> | undefined;
/**
* Searches for the item associated with `childPathSegments`, or the nearest ancestor of that path that
* has an associated item.
*
* @returns the found item, or `undefined` if no item was found
*
* @example
* ```ts
* const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]);
* trie.findChildPathFromSegments(['foo', 'baz']); // returns 1
* trie.findChildPathFromSegments(['foo','bar', 'baz']); // returns 2
* ```
*/
findChildPathFromSegments(childPathSegments: Iterable<string>): TItem | undefined;
/**
* Determines if an entry exists exactly at the specified path.
*
* @returns `true` if an entry exists at the specified path, `false` otherwise
*/
has(query: string, delimiter?: string): boolean;
/**
* Retrieves the entry that exists exactly at the specified path, if any.
*
* @returns The entry that exists exactly at the specified path, or `undefined` if no entry exists.
*/
get(query: string, delimiter?: string): TItem | undefined;
/**
* Gets the number of entries in this trie.
*
* @returns The number of entries in this trie.
*/
get size(): number;
/**
* @returns The root node of the trie, corresponding to the path ''
*/
get tree(): IReadonlyPathTrieNode<TItem>;
/**
* Iterates over the entries in this trie.
*
* @param query - An optional query. If specified only entries that start with the query will be returned.
*
* @returns An iterator over the entries under the specified query (or the root if no query is specified).
* @remarks
* Keys in the returned iterator use the provided delimiter to join segments.
* Iteration order is not specified.
* @example
* ```ts
* const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]);
* [...trie.entries(undefined, ',')); // returns [['foo', 1], ['foo,bar', 2]]
* ```
*/
entries(query?: string, delimiter?: string): IterableIterator<[string, TItem]>;
/**
* Iterates over the entries in this trie.
*
* @param query - An optional query. If specified only entries that start with the query will be returned.
*
* @returns An iterator over the entries under the specified query (or the root if no query is specified).
* @remarks
* Keys in the returned iterator use the provided delimiter to join segments.
* Iteration order is not specified.
*/
[Symbol.iterator](query?: string, delimiter?: string): IterableIterator<[string, TItem]>;
/**
* Groups the provided map of info by the nearest entry in the trie that contains the path. If the path
* is not found in the trie, the info is ignored.
*
* @returns The grouped info, grouped by the nearest entry in the trie that contains the path
*
* @param infoByPath - The info to be grouped, keyed by path
*/
groupByChild<TInfo>(infoByPath: Map<string, TInfo>, delimiter?: string): Map<TItem, Map<string, TInfo>>;
/**
* Retrieves the trie node at the specified prefix, if it exists.
*
* @param query - The prefix to check for
* @param delimiter - The path delimiter
* @returns The trie node at the specified prefix, or `undefined` if no node was found
*/
getNodeAtPrefix(query: string, delimiter?: string): IReadonlyPathTrieNode<TItem> | undefined;
}
/**
* This class is used to associate path-like-strings, such as those returned by `git` commands,
* with entities that correspond with ancestor folders, such as Rush Projects or npm packages.
*
* It is optimized for efficiently locating the nearest ancestor path with an associated value.
*
* It is implemented as a Trie (https://en.wikipedia.org/wiki/Trie) data structure, with each edge
* being a path segment.
*
* @example
* ```ts
* const trie = new LookupByPath([['foo', 1], ['bar', 2], ['foo/bar', 3]]);
* trie.findChildPath('foo'); // returns 1
* trie.findChildPath('foo/baz'); // returns 1
* trie.findChildPath('baz'); // returns undefined
* trie.findChildPath('foo/bar/baz'); returns 3
* trie.findChildPath('bar/foo/bar'); returns 2
* ```
* @beta
*/
export declare class LookupByPath<TItem extends {}> implements IReadonlyLookupByPath<TItem> {
/**
* The delimiter used to split paths
*/
readonly delimiter: string;
/**
* The root node of the trie, corresponding to the path ''
*/
private readonly _root;
/**
* The number of entries in this trie.
*/
private _size;
/**
* Constructs a new `LookupByPath`
*
* @param entries - Initial path-value pairs to populate the trie.
*/
constructor(entries?: Iterable<[string, TItem]>, delimiter?: string);
/**
* Iterates over the segments of a serialized path.
*
* @example
*
* `LookupByPath.iteratePathSegments('foo/bar/baz')` yields 'foo', 'bar', 'baz'
*
* `LookupByPath.iteratePathSegments('foo\\bar\\baz', '\\')` yields 'foo', 'bar', 'baz'
*/
static iteratePathSegments(serializedPath: string, delimiter?: string): Iterable<string>;
private static _iteratePrefixes;
/**
* {@inheritdoc IReadonlyLookupByPath.size}
*/
get size(): number;
/**
* {@inheritdoc IReadonlyLookupByPath.tree}
*/
get tree(): IReadonlyPathTrieNode<TItem>;
/**
* Deletes all entries from this `LookupByPath` instance.
*
* @returns this, for chained calls
*/
clear(): this;
/**
* Associates the value with the specified serialized path.
* If a value is already associated, will overwrite.
*
* @returns this, for chained calls
*/
setItem(serializedPath: string, value: TItem, delimiter?: string): this;
/**
* Deletes an item if it exists.
* @param query - The path to the item to delete
* @param delimeter - Optional override delimeter for parsing the query
* @returns `true` if the item was found and deleted, `false` otherwise
* @remarks
* If the node has children with values, they will be retained.
*/
deleteItem(query: string, delimeter?: string): boolean;
/**
* Deletes an item and all its children.
* @param query - The path to the item to delete
* @param delimeter - Optional override delimeter for parsing the query
* @returns `true` if any nodes were deleted, `false` otherwise
*/
deleteSubtree(query: string, delimeter?: string): boolean;
/**
* Associates the value with the specified path.
* If a value is already associated, will overwrite.
*
* @returns this, for chained calls
*/
setItemFromSegments(pathSegments: Iterable<string>, value: TItem): this;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
findChildPath(childPath: string, delimiter?: string): TItem | undefined;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
findLongestPrefixMatch(query: string, delimiter?: string): IPrefixMatch<TItem> | undefined;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
findChildPathFromSegments(childPathSegments: Iterable<string>): TItem | undefined;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
has(key: string, delimiter?: string): boolean;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
get(key: string, delimiter?: string): TItem | undefined;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
groupByChild<TInfo>(infoByPath: Map<string, TInfo>, delimiter?: string): Map<TItem, Map<string, TInfo>>;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
entries(query?: string, delimiter?: string): IterableIterator<[string, TItem]>;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
[Symbol.iterator](query?: string, delimiter?: string): IterableIterator<[string, TItem]>;
/**
* {@inheritdoc IReadonlyLookupByPath}
*/
getNodeAtPrefix(query: string, delimiter?: string): IReadonlyPathTrieNode<TItem> | undefined;
/**
* Iterates through progressively longer prefixes of a given string and returns as soon
* as the number of candidate items that match the prefix are 1 or 0.
*
* If a match is present, returns the matched itme and the length of the matched prefix.
*
* @returns the found item, or `undefined` if no item was found
*/
private _findLongestPrefixMatch;
/**
* Finds the node at the specified path, or `undefined` if no node was found.
*
* @param query - The path to the node to search for
* @returns The trie node at the specified path, or `undefined` if no node was found
*/
private _findNodeAtPrefix;
}
//# sourceMappingURL=LookupByPath.d.ts.map