@vltpkg/dss-breadcrumb 0.0.0-14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +23 -0
- package/dist/esm/index.d.ts +75 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +203 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/types.d.ts +32 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (c) vlt technology, Inc.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
4
|
+
|
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
6
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
7
|
+
Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by:
|
|
8
|
+
|
|
9
|
+
(a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or
|
|
10
|
+
(b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution.
|
|
11
|
+
Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise.
|
|
12
|
+
|
|
13
|
+
DISCLAIMER
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @vltpkg/dss-breadcrumb
|
|
2
|
+
|
|
3
|
+
The Dependency Selector Syntax breadcrumb utilities used by the vlt
|
|
4
|
+
client.
|
|
5
|
+
|
|
6
|
+
A returned "Breadcrumb" object is a data structure that contains a
|
|
7
|
+
linked list of items that where parsed from a given Dependency
|
|
8
|
+
Selector Syntax query string that can be used to navigate through a
|
|
9
|
+
given graph.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { parseBreadcrumb } from '@vltpkg/dss-breadcrumb'
|
|
15
|
+
|
|
16
|
+
// Parse a selector string into a breadcrumb
|
|
17
|
+
const breadcrumb = parseBreadcrumb(':root > #a')
|
|
18
|
+
|
|
19
|
+
// Use the breadcrumb to navigate through the query
|
|
20
|
+
console.log(breadcrumb.current.value) // :root
|
|
21
|
+
breadcrumb.next()
|
|
22
|
+
console.log(breadcrumb.current.value) // a
|
|
23
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ModifierBreadcrumb, ModifierBreadcrumbItem, ModifierInteractiveBreadcrumb } from './types.ts';
|
|
2
|
+
export * from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The Breadcrumb class is used to represent a valid breadcrumb
|
|
5
|
+
* path that helps you traverse a graph and find a specific node or edge.
|
|
6
|
+
*
|
|
7
|
+
* Alongside the traditional analogy, "Breadcrumb" is also being used here
|
|
8
|
+
* as a term used to describe the subset of the query language that uses
|
|
9
|
+
* only root/workspace selectors, id selectors & combinators.
|
|
10
|
+
*
|
|
11
|
+
* The Breadcrumb implements a doubly-linked list of items
|
|
12
|
+
* that can be used to navigate through the breadcrumb.
|
|
13
|
+
* The InteractiveBreadcrumb can also be used to keep track of state
|
|
14
|
+
* of the current breadcrumb item that should be used for checks.
|
|
15
|
+
*
|
|
16
|
+
* It also validates that each element of the provided query string is
|
|
17
|
+
* valid according to the previous definition of a "Breadcrumb" query
|
|
18
|
+
* language subset.
|
|
19
|
+
*/
|
|
20
|
+
export declare class Breadcrumb implements ModifierBreadcrumb {
|
|
21
|
+
#private;
|
|
22
|
+
comment: string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Initializes the interactive breadcrumb with a query string.
|
|
25
|
+
*/
|
|
26
|
+
constructor(query: string);
|
|
27
|
+
/**
|
|
28
|
+
* Retrieves the first breadcrumb item.
|
|
29
|
+
*/
|
|
30
|
+
get first(): ModifierBreadcrumbItem;
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves the last breadcrumb item.
|
|
33
|
+
*/
|
|
34
|
+
get last(): ModifierBreadcrumbItem;
|
|
35
|
+
/**
|
|
36
|
+
* Returns `true` if the breadcrumb is composed of a single item.
|
|
37
|
+
*/
|
|
38
|
+
get single(): boolean;
|
|
39
|
+
[Symbol.iterator](): ArrayIterator<ModifierBreadcrumbItem>;
|
|
40
|
+
/**
|
|
41
|
+
* Empties the current breadcrumb list.
|
|
42
|
+
*/
|
|
43
|
+
clear(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Gets an {@link InteractiveBreadcrumb} instance that can be
|
|
46
|
+
* used to track state of the current breadcrumb item.
|
|
47
|
+
*/
|
|
48
|
+
interactive(): InteractiveBreadcrumb;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The InteractiveBreadcrumb is used to keep track of state
|
|
52
|
+
* of the current breadcrumb item that should be used for checks.
|
|
53
|
+
*/
|
|
54
|
+
export declare class InteractiveBreadcrumb implements ModifierInteractiveBreadcrumb {
|
|
55
|
+
#private;
|
|
56
|
+
constructor(breadcrumb: Breadcrumb);
|
|
57
|
+
/**
|
|
58
|
+
* The current breadcrumb item.
|
|
59
|
+
*/
|
|
60
|
+
get current(): ModifierBreadcrumbItem | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Returns `true` if the current breadcrumb has no more items left.
|
|
63
|
+
*/
|
|
64
|
+
get done(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The next breadcrumb item.
|
|
67
|
+
*/
|
|
68
|
+
next(): this;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns an {@link Breadcrumb} list of items
|
|
72
|
+
* for a given query string.
|
|
73
|
+
*/
|
|
74
|
+
export declare const parseBreadcrumb: (query: string) => ModifierBreadcrumb;
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,6BAA6B,EAC9B,MAAM,YAAY,CAAA;AAEnB,cAAc,YAAY,CAAA;AAE1B;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,UAAW,YAAW,kBAAkB;;IAEnD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAE3B;;OAEG;gBACS,KAAK,EAAE,MAAM;IAiHzB;;OAEG;IACH,IAAI,KAAK,IAAI,sBAAsB,CAKlC;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,sBAAsB,CAMjC;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,CAAC,MAAM,CAAC,QAAQ,CAAC;IAIjB;;OAEG;IACH,KAAK;IAQL;;;OAGG;IACH,WAAW;CAGZ;AAED;;;GAGG;AACH,qBAAa,qBACX,YAAW,6BAA6B;;gBAG5B,UAAU,EAAE,UAAU;IAIlC;;OAEG;IACH,IAAI,OAAO,IAAI,sBAAsB,GAAG,SAAS,CAEhD;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI;CAIb;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,kBACzB,CAAA"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { isPostcssNodeWithChildren, isPseudoNode, isIdentifierNode, isCombinatorNode, isCommentNode, parse, } from '@vltpkg/dss-parser';
|
|
2
|
+
import { error } from '@vltpkg/error-cause';
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The Breadcrumb class is used to represent a valid breadcrumb
|
|
6
|
+
* path that helps you traverse a graph and find a specific node or edge.
|
|
7
|
+
*
|
|
8
|
+
* Alongside the traditional analogy, "Breadcrumb" is also being used here
|
|
9
|
+
* as a term used to describe the subset of the query language that uses
|
|
10
|
+
* only root/workspace selectors, id selectors & combinators.
|
|
11
|
+
*
|
|
12
|
+
* The Breadcrumb implements a doubly-linked list of items
|
|
13
|
+
* that can be used to navigate through the breadcrumb.
|
|
14
|
+
* The InteractiveBreadcrumb can also be used to keep track of state
|
|
15
|
+
* of the current breadcrumb item that should be used for checks.
|
|
16
|
+
*
|
|
17
|
+
* It also validates that each element of the provided query string is
|
|
18
|
+
* valid according to the previous definition of a "Breadcrumb" query
|
|
19
|
+
* language subset.
|
|
20
|
+
*/
|
|
21
|
+
export class Breadcrumb {
|
|
22
|
+
#items;
|
|
23
|
+
comment;
|
|
24
|
+
/**
|
|
25
|
+
* Initializes the interactive breadcrumb with a query string.
|
|
26
|
+
*/
|
|
27
|
+
constructor(query) {
|
|
28
|
+
this.#items = [];
|
|
29
|
+
const ast = parse(query);
|
|
30
|
+
// Keep track of the previous AST node for consolidation
|
|
31
|
+
let prevNode;
|
|
32
|
+
// iterates only at the first level of the AST since
|
|
33
|
+
// any nested nodes are invalid syntax
|
|
34
|
+
for (const item of ast.first.nodes) {
|
|
35
|
+
const isWorkspaceOrProject = isPseudoNode(item) &&
|
|
36
|
+
(item.value === ':workspace' || item.value === ':project');
|
|
37
|
+
const allowedPseudoNodes = isPseudoNode(item) &&
|
|
38
|
+
(item.value === ':root' ||
|
|
39
|
+
item.value === ':workspace' ||
|
|
40
|
+
item.value === ':project');
|
|
41
|
+
const allowedTypes = isIdentifierNode(item) ||
|
|
42
|
+
allowedPseudoNodes ||
|
|
43
|
+
(isCombinatorNode(item) && item.value === '>') ||
|
|
44
|
+
isCommentNode(item);
|
|
45
|
+
// Check if this is a nested selector that's not an allowed pseudo
|
|
46
|
+
const isNestedSelector = isPostcssNodeWithChildren(item) &&
|
|
47
|
+
!(allowedPseudoNodes && item.nodes.length === 0);
|
|
48
|
+
// validation, only the root/workspace selectors, id selectors
|
|
49
|
+
// and combinators are valid ast node items
|
|
50
|
+
if (isNestedSelector || !allowedTypes) {
|
|
51
|
+
throw error('Invalid query', { found: query });
|
|
52
|
+
}
|
|
53
|
+
// combinators and comments are skipped
|
|
54
|
+
if (isCombinatorNode(item)) {
|
|
55
|
+
prevNode = undefined;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
else if (isCommentNode(item)) {
|
|
59
|
+
const cleanComment = item.value
|
|
60
|
+
.replace(/^\/\*/, '')
|
|
61
|
+
.replace(/\*\/$/, '')
|
|
62
|
+
.trim();
|
|
63
|
+
this.comment = cleanComment;
|
|
64
|
+
prevNode = undefined;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// check if we need to consolidate with previous item
|
|
68
|
+
const isPrevWorkspaceOrProject = prevNode &&
|
|
69
|
+
isPseudoNode(prevNode) &&
|
|
70
|
+
(prevNode.value === ':workspace' ||
|
|
71
|
+
prevNode.value === ':project');
|
|
72
|
+
const isPrevId = prevNode && isIdentifierNode(prevNode);
|
|
73
|
+
const isCurrentId = isIdentifierNode(item);
|
|
74
|
+
// we define the last item as we iterate through the list of
|
|
75
|
+
// breadcrumb items so that this value can also be used to
|
|
76
|
+
// update previous items when needed
|
|
77
|
+
const lastItem = this.#items.length > 0 ?
|
|
78
|
+
this.#items[this.#items.length - 1]
|
|
79
|
+
: undefined;
|
|
80
|
+
// current node is ID, previous was workspace/project
|
|
81
|
+
// we fold the current node value into the same object
|
|
82
|
+
// and move on to the next ast item
|
|
83
|
+
if (isCurrentId && isPrevWorkspaceOrProject && lastItem) {
|
|
84
|
+
// Modify the last item to include the ID
|
|
85
|
+
lastItem.name = item.value;
|
|
86
|
+
lastItem.value = `${lastItem.value}#${item.value}`;
|
|
87
|
+
prevNode = undefined;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// current node is workspace/project, previous was ID
|
|
91
|
+
// we fold the current node value into the same object
|
|
92
|
+
// and move on to the next ast item
|
|
93
|
+
if (isWorkspaceOrProject && isPrevId && lastItem) {
|
|
94
|
+
// Modify the last item to include the pseudo
|
|
95
|
+
lastItem.value = `${lastItem.value}${item.value}`;
|
|
96
|
+
lastItem.importer = true;
|
|
97
|
+
prevNode = undefined;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const newItem = {
|
|
101
|
+
value: item.type === 'id' ? `#${item.value}` : item.value,
|
|
102
|
+
name: item.type === 'id' ? item.value : undefined,
|
|
103
|
+
type: item.type,
|
|
104
|
+
prev: lastItem,
|
|
105
|
+
next: undefined,
|
|
106
|
+
importer: allowedPseudoNodes,
|
|
107
|
+
};
|
|
108
|
+
if (lastItem) {
|
|
109
|
+
lastItem.next = newItem;
|
|
110
|
+
}
|
|
111
|
+
this.#items.push(newItem);
|
|
112
|
+
prevNode = item;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// the parsed query should have at least one item
|
|
116
|
+
// that is then going to be set as the current item
|
|
117
|
+
if (!this.#items[0]) {
|
|
118
|
+
throw error('Failed to parse query', {
|
|
119
|
+
found: query,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Retrieves the first breadcrumb item.
|
|
125
|
+
*/
|
|
126
|
+
get first() {
|
|
127
|
+
if (!this.#items[0]) {
|
|
128
|
+
throw error('Failed to find first breadcrumb item');
|
|
129
|
+
}
|
|
130
|
+
return this.#items[0];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Retrieves the last breadcrumb item.
|
|
134
|
+
*/
|
|
135
|
+
get last() {
|
|
136
|
+
const lastItem = this.#items[this.#items.length - 1];
|
|
137
|
+
if (!lastItem) {
|
|
138
|
+
throw error('Failed to find first breadcrumb item');
|
|
139
|
+
}
|
|
140
|
+
return lastItem;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Returns `true` if the breadcrumb is composed of a single item.
|
|
144
|
+
*/
|
|
145
|
+
get single() {
|
|
146
|
+
return this.#items.length === 1;
|
|
147
|
+
}
|
|
148
|
+
[Symbol.iterator]() {
|
|
149
|
+
return this.#items.values();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Empties the current breadcrumb list.
|
|
153
|
+
*/
|
|
154
|
+
clear() {
|
|
155
|
+
for (const item of this.#items) {
|
|
156
|
+
item.prev = undefined;
|
|
157
|
+
item.next = undefined;
|
|
158
|
+
}
|
|
159
|
+
this.#items.length = 0;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Gets an {@link InteractiveBreadcrumb} instance that can be
|
|
163
|
+
* used to track state of the current breadcrumb item.
|
|
164
|
+
*/
|
|
165
|
+
interactive() {
|
|
166
|
+
return new InteractiveBreadcrumb(this);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The InteractiveBreadcrumb is used to keep track of state
|
|
171
|
+
* of the current breadcrumb item that should be used for checks.
|
|
172
|
+
*/
|
|
173
|
+
export class InteractiveBreadcrumb {
|
|
174
|
+
#current;
|
|
175
|
+
constructor(breadcrumb) {
|
|
176
|
+
this.#current = breadcrumb.first;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The current breadcrumb item.
|
|
180
|
+
*/
|
|
181
|
+
get current() {
|
|
182
|
+
return this.#current;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Returns `true` if the current breadcrumb has no more items left.
|
|
186
|
+
*/
|
|
187
|
+
get done() {
|
|
188
|
+
return !this.#current;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The next breadcrumb item.
|
|
192
|
+
*/
|
|
193
|
+
next() {
|
|
194
|
+
this.#current = this.#current?.next;
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns an {@link Breadcrumb} list of items
|
|
200
|
+
* for a given query string.
|
|
201
|
+
*/
|
|
202
|
+
export const parseBreadcrumb = (query) => new Breadcrumb(query);
|
|
203
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isPostcssNodeWithChildren,\n isPseudoNode,\n isIdentifierNode,\n isCombinatorNode,\n isCommentNode,\n parse,\n} from '@vltpkg/dss-parser'\nimport { error } from '@vltpkg/error-cause'\nimport type { PostcssNode } from '@vltpkg/dss-parser'\nimport type {\n ModifierBreadcrumb,\n ModifierBreadcrumbItem,\n ModifierInteractiveBreadcrumb,\n} from './types.ts'\n\nexport * from './types.ts'\n\n/**\n * The Breadcrumb class is used to represent a valid breadcrumb\n * path that helps you traverse a graph and find a specific node or edge.\n *\n * Alongside the traditional analogy, \"Breadcrumb\" is also being used here\n * as a term used to describe the subset of the query language that uses\n * only root/workspace selectors, id selectors & combinators.\n *\n * The Breadcrumb implements a doubly-linked list of items\n * that can be used to navigate through the breadcrumb.\n * The InteractiveBreadcrumb can also be used to keep track of state\n * of the current breadcrumb item that should be used for checks.\n *\n * It also validates that each element of the provided query string is\n * valid according to the previous definition of a \"Breadcrumb\" query\n * language subset.\n */\nexport class Breadcrumb implements ModifierBreadcrumb {\n #items: ModifierBreadcrumbItem[]\n comment: string | undefined\n\n /**\n * Initializes the interactive breadcrumb with a query string.\n */\n constructor(query: string) {\n this.#items = []\n const ast = parse(query)\n\n // Keep track of the previous AST node for consolidation\n let prevNode: PostcssNode | undefined\n\n // iterates only at the first level of the AST since\n // any nested nodes are invalid syntax\n for (const item of ast.first.nodes) {\n const isWorkspaceOrProject =\n isPseudoNode(item) &&\n (item.value === ':workspace' || item.value === ':project')\n\n const allowedPseudoNodes =\n isPseudoNode(item) &&\n (item.value === ':root' ||\n item.value === ':workspace' ||\n item.value === ':project')\n\n const allowedTypes =\n isIdentifierNode(item) ||\n allowedPseudoNodes ||\n (isCombinatorNode(item) && item.value === '>') ||\n isCommentNode(item)\n\n // Check if this is a nested selector that's not an allowed pseudo\n const isNestedSelector =\n isPostcssNodeWithChildren(item) &&\n !(allowedPseudoNodes && item.nodes.length === 0)\n\n // validation, only the root/workspace selectors, id selectors\n // and combinators are valid ast node items\n if (isNestedSelector || !allowedTypes) {\n throw error('Invalid query', { found: query })\n }\n\n // combinators and comments are skipped\n if (isCombinatorNode(item)) {\n prevNode = undefined\n continue\n } else if (isCommentNode(item)) {\n const cleanComment = item.value\n .replace(/^\\/\\*/, '')\n .replace(/\\*\\/$/, '')\n .trim()\n this.comment = cleanComment\n prevNode = undefined\n } else {\n // check if we need to consolidate with previous item\n const isPrevWorkspaceOrProject =\n prevNode &&\n isPseudoNode(prevNode) &&\n (prevNode.value === ':workspace' ||\n prevNode.value === ':project')\n\n const isPrevId = prevNode && isIdentifierNode(prevNode)\n const isCurrentId = isIdentifierNode(item)\n\n // we define the last item as we iterate through the list of\n // breadcrumb items so that this value can also be used to\n // update previous items when needed\n const lastItem =\n this.#items.length > 0 ?\n this.#items[this.#items.length - 1]\n : undefined\n\n // current node is ID, previous was workspace/project\n // we fold the current node value into the same object\n // and move on to the next ast item\n if (isCurrentId && isPrevWorkspaceOrProject && lastItem) {\n // Modify the last item to include the ID\n lastItem.name = item.value\n lastItem.value = `${lastItem.value}#${item.value}`\n prevNode = undefined\n continue\n }\n\n // current node is workspace/project, previous was ID\n // we fold the current node value into the same object\n // and move on to the next ast item\n if (isWorkspaceOrProject && isPrevId && lastItem) {\n // Modify the last item to include the pseudo\n lastItem.value = `${lastItem.value}${item.value}`\n lastItem.importer = true\n prevNode = undefined\n continue\n }\n\n const newItem = {\n value: item.type === 'id' ? `#${item.value}` : item.value,\n name: item.type === 'id' ? item.value : undefined,\n type: item.type,\n prev: lastItem,\n next: undefined,\n importer: allowedPseudoNodes,\n }\n if (lastItem) {\n lastItem.next = newItem\n }\n this.#items.push(newItem)\n prevNode = item\n }\n }\n // the parsed query should have at least one item\n // that is then going to be set as the current item\n if (!this.#items[0]) {\n throw error('Failed to parse query', {\n found: query,\n })\n }\n }\n\n /**\n * Retrieves the first breadcrumb item.\n */\n get first(): ModifierBreadcrumbItem {\n if (!this.#items[0]) {\n throw error('Failed to find first breadcrumb item')\n }\n return this.#items[0]\n }\n\n /**\n * Retrieves the last breadcrumb item.\n */\n get last(): ModifierBreadcrumbItem {\n const lastItem = this.#items[this.#items.length - 1]\n if (!lastItem) {\n throw error('Failed to find first breadcrumb item')\n }\n return lastItem\n }\n\n /**\n * Returns `true` if the breadcrumb is composed of a single item.\n */\n get single(): boolean {\n return this.#items.length === 1\n }\n\n [Symbol.iterator]() {\n return this.#items.values()\n }\n\n /**\n * Empties the current breadcrumb list.\n */\n clear() {\n for (const item of this.#items) {\n item.prev = undefined\n item.next = undefined\n }\n this.#items.length = 0\n }\n\n /**\n * Gets an {@link InteractiveBreadcrumb} instance that can be\n * used to track state of the current breadcrumb item.\n */\n interactive() {\n return new InteractiveBreadcrumb(this)\n }\n}\n\n/**\n * The InteractiveBreadcrumb is used to keep track of state\n * of the current breadcrumb item that should be used for checks.\n */\nexport class InteractiveBreadcrumb\n implements ModifierInteractiveBreadcrumb\n{\n #current: ModifierBreadcrumbItem | undefined\n constructor(breadcrumb: Breadcrumb) {\n this.#current = breadcrumb.first\n }\n\n /**\n * The current breadcrumb item.\n */\n get current(): ModifierBreadcrumbItem | undefined {\n return this.#current\n }\n\n /**\n * Returns `true` if the current breadcrumb has no more items left.\n */\n get done(): boolean {\n return !this.#current\n }\n\n /**\n * The next breadcrumb item.\n */\n next(): this {\n this.#current = this.#current?.next\n return this\n }\n}\n\n/**\n * Returns an {@link Breadcrumb} list of items\n * for a given query string.\n */\nexport const parseBreadcrumb = (query: string): ModifierBreadcrumb =>\n new Breadcrumb(query)\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A valid item of a given breadcrumb.
|
|
3
|
+
*/
|
|
4
|
+
export type ModifierBreadcrumbItem = {
|
|
5
|
+
name?: string;
|
|
6
|
+
value: string;
|
|
7
|
+
type: string;
|
|
8
|
+
importer: boolean;
|
|
9
|
+
prev: ModifierBreadcrumbItem | undefined;
|
|
10
|
+
next: ModifierBreadcrumbItem | undefined;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A breadcrumb is a linked list of items, where
|
|
14
|
+
* each item has a value and a type.
|
|
15
|
+
*/
|
|
16
|
+
export interface ModifierBreadcrumb extends Iterable<ModifierBreadcrumbItem> {
|
|
17
|
+
clear(): void;
|
|
18
|
+
comment: string | undefined;
|
|
19
|
+
first: ModifierBreadcrumbItem;
|
|
20
|
+
last: ModifierBreadcrumbItem;
|
|
21
|
+
single: boolean;
|
|
22
|
+
interactive: () => ModifierInteractiveBreadcrumb;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An interactive breadcrumb that holds state on what is the current item.
|
|
26
|
+
*/
|
|
27
|
+
export type ModifierInteractiveBreadcrumb = {
|
|
28
|
+
current: ModifierBreadcrumbItem | undefined;
|
|
29
|
+
done: boolean;
|
|
30
|
+
next: () => ModifierInteractiveBreadcrumb;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,sBAAsB,GAAG,SAAS,CAAA;IACxC,IAAI,EAAE,sBAAsB,GAAG,SAAS,CAAA;CACzC,CAAA;AAED;;;GAGG;AACH,MAAM,WAAW,kBACf,SAAQ,QAAQ,CAAC,sBAAsB,CAAC;IACxC,KAAK,IAAI,IAAI,CAAA;IACb,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,KAAK,EAAE,sBAAsB,CAAA;IAC7B,IAAI,EAAE,sBAAsB,CAAA;IAC5B,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,EAAE,MAAM,6BAA6B,CAAA;CACjD;AAED;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,sBAAsB,GAAG,SAAS,CAAA;IAC3C,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,MAAM,6BAA6B,CAAA;CAC1C,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A valid item of a given breadcrumb.\n */\nexport type ModifierBreadcrumbItem = {\n name?: string\n value: string\n type: string\n importer: boolean\n prev: ModifierBreadcrumbItem | undefined\n next: ModifierBreadcrumbItem | undefined\n}\n\n/**\n * A breadcrumb is a linked list of items, where\n * each item has a value and a type.\n */\nexport interface ModifierBreadcrumb\n extends Iterable<ModifierBreadcrumbItem> {\n clear(): void\n comment: string | undefined\n first: ModifierBreadcrumbItem\n last: ModifierBreadcrumbItem\n single: boolean\n interactive: () => ModifierInteractiveBreadcrumb\n}\n\n/**\n * An interactive breadcrumb that holds state on what is the current item.\n */\nexport type ModifierInteractiveBreadcrumb = {\n current: ModifierBreadcrumbItem | undefined\n done: boolean\n next: () => ModifierInteractiveBreadcrumb\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vltpkg/dss-breadcrumb",
|
|
3
|
+
"description": "The Dependency Selector Syntax (DSS) breadcrumb utilities",
|
|
4
|
+
"version": "0.0.0-14",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
8
|
+
"directory": "src/dss-breadcrumb"
|
|
9
|
+
},
|
|
10
|
+
"tshy": {
|
|
11
|
+
"selfLink": false,
|
|
12
|
+
"liveDev": true,
|
|
13
|
+
"dialects": [
|
|
14
|
+
"esm"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
".": "./src/index.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@vltpkg/dss-parser": "0.0.0-14",
|
|
23
|
+
"@vltpkg/error-cause": "0.0.0-14"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@eslint/js": "^9.28.0",
|
|
27
|
+
"@types/node": "^22.15.29",
|
|
28
|
+
"eslint": "^9.28.0",
|
|
29
|
+
"prettier": "^3.5.3",
|
|
30
|
+
"tap": "^21.1.0",
|
|
31
|
+
"tshy": "^3.0.2",
|
|
32
|
+
"typedoc": "~0.27.9",
|
|
33
|
+
"typescript": "5.7.3",
|
|
34
|
+
"typescript-eslint": "^8.33.1"
|
|
35
|
+
},
|
|
36
|
+
"license": "BSD-2-Clause-Patent",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22"
|
|
39
|
+
},
|
|
40
|
+
"tap": {
|
|
41
|
+
"extends": "../../tap-config.yaml"
|
|
42
|
+
},
|
|
43
|
+
"prettier": "../../.prettierrc.js",
|
|
44
|
+
"module": "./dist/esm/index.js",
|
|
45
|
+
"type": "module",
|
|
46
|
+
"exports": {
|
|
47
|
+
"./package.json": "./package.json",
|
|
48
|
+
".": {
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "./dist/esm/index.d.ts",
|
|
51
|
+
"default": "./dist/esm/index.js"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"dist"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"format": "prettier --write . --log-level warn --ignore-path ../../.prettierignore --cache",
|
|
60
|
+
"format:check": "prettier --check . --ignore-path ../../.prettierignore --cache",
|
|
61
|
+
"lint": "eslint . --fix",
|
|
62
|
+
"lint:check": "eslint .",
|
|
63
|
+
"snap": "tap",
|
|
64
|
+
"test": "tap",
|
|
65
|
+
"posttest": "tsc --noEmit",
|
|
66
|
+
"tshy": "tshy",
|
|
67
|
+
"typecheck": "tsc --noEmit"
|
|
68
|
+
}
|
|
69
|
+
}
|