@hamak/navigation-utils 0.4.0
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/README.md +82 -0
- package/dist/es2015/filesystem/fs-node-abstract.types.js +1 -0
- package/dist/es2015/index.js +9 -0
- package/dist/es2015/itinerary/hyper-layer-node.js +21 -0
- package/dist/es2015/itinerary/itinerary.js +276 -0
- package/dist/es2015/itinerary/itinerary.spec.js +296 -0
- package/dist/es2015/itinerary/stack.js +47 -0
- package/dist/es2015/itinerary/stack.spec.js +35 -0
- package/dist/es2015/path/pathway-resolver.js +31 -0
- package/dist/es2015/path/pathway.js +206 -0
- package/dist/filesystem/fs-node-abstract.types.d.ts +34 -0
- package/dist/filesystem/fs-node-abstract.types.d.ts.map +1 -0
- package/dist/filesystem/fs-node-abstract.types.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/itinerary/hyper-layer-node.d.ts +12 -0
- package/dist/itinerary/hyper-layer-node.d.ts.map +1 -0
- package/dist/itinerary/hyper-layer-node.js +21 -0
- package/dist/itinerary/itinerary.d.ts +86 -0
- package/dist/itinerary/itinerary.d.ts.map +1 -0
- package/dist/itinerary/itinerary.js +275 -0
- package/dist/itinerary/itinerary.spec.d.ts +2 -0
- package/dist/itinerary/itinerary.spec.d.ts.map +1 -0
- package/dist/itinerary/itinerary.spec.js +294 -0
- package/dist/itinerary/stack.d.ts +20 -0
- package/dist/itinerary/stack.d.ts.map +1 -0
- package/dist/itinerary/stack.js +47 -0
- package/dist/itinerary/stack.spec.d.ts +2 -0
- package/dist/itinerary/stack.spec.d.ts.map +1 -0
- package/dist/itinerary/stack.spec.js +34 -0
- package/dist/path/pathway-resolver.d.ts +20 -0
- package/dist/path/pathway-resolver.d.ts.map +1 -0
- package/dist/path/pathway-resolver.js +31 -0
- package/dist/path/pathway.d.ts +84 -0
- package/dist/path/pathway.d.ts.map +1 -0
- package/dist/path/pathway.js +206 -0
- package/package.json +41 -0
- package/src/filesystem/fs-node-abstract.types.ts +34 -0
- package/src/index.ts +11 -0
- package/src/itinerary/hyper-layer-node.ts +25 -0
- package/src/itinerary/itinerary.spec.ts +388 -0
- package/src/itinerary/itinerary.ts +363 -0
- package/src/itinerary/stack.spec.ts +46 -0
- package/src/itinerary/stack.ts +62 -0
- package/src/path/pathway-resolver.ts +36 -0
- package/src/path/pathway.ts +232 -0
- package/tsconfig.es2015.json +23 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import stack, {StackElement} from "./stack";
|
|
2
|
+
import {HyperLayerNode} from "./hyper-layer-node";
|
|
3
|
+
|
|
4
|
+
export type ObjectId = string | number
|
|
5
|
+
|
|
6
|
+
interface PropertyValuePair<P = string,V = any> {
|
|
7
|
+
propertyName : P
|
|
8
|
+
propertyValue : V
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ItineraryStepBase {
|
|
12
|
+
type : "property" | "lookup" | "position"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PropertyStep extends ItineraryStepBase{
|
|
16
|
+
type : "property"
|
|
17
|
+
propertyName : string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LookupStep extends ItineraryStepBase{
|
|
21
|
+
type : "lookup",
|
|
22
|
+
keys : PropertyValuePair[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PositionStep extends ItineraryStepBase{
|
|
26
|
+
type : "position",
|
|
27
|
+
position : number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ItineraryStep = PropertyStep | LookupStep | PositionStep
|
|
31
|
+
|
|
32
|
+
export type Itinerary = StackElement<ItineraryStep>
|
|
33
|
+
|
|
34
|
+
export interface ConstructiveItinerary {
|
|
35
|
+
itinerary : Itinerary | undefined
|
|
36
|
+
prototypes : StackElement<any> | undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type StepValueNodeType = Array<any> | Record<string | number, any>
|
|
40
|
+
|
|
41
|
+
export function constructiveItinerary(steps : Array<string | number | StepValueNodeType>) : ConstructiveItinerary {
|
|
42
|
+
const segments : Array<string | number> = []
|
|
43
|
+
const prototypes : Array<any> = []
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < steps.length; i+=2) {
|
|
46
|
+
const segment = steps[i]
|
|
47
|
+
const prototype = steps[i+1]
|
|
48
|
+
|
|
49
|
+
switch (typeof segment) {
|
|
50
|
+
case "string":
|
|
51
|
+
case "number":{
|
|
52
|
+
const step = typeof segment === "number" ? positionStep(segment) : propertyStep(segment)
|
|
53
|
+
switch (typeof prototype) {
|
|
54
|
+
case "object": { // including Array
|
|
55
|
+
segments.push(segment)
|
|
56
|
+
prototypes.push(prototype)
|
|
57
|
+
} break
|
|
58
|
+
default: {
|
|
59
|
+
throw new Error(`Expecting array or object but got : '${typeof prototype}'`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} break
|
|
63
|
+
default: {
|
|
64
|
+
throw new Error(`Expecting string or number but gor : '${typeof segment}'`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
itinerary: itineraryOf(...segments),
|
|
71
|
+
prototypes: stack.fromArray(prototypes)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function areSameItineraryStep(step1 : ItineraryStep | null | undefined, step2: ItineraryStep | null | undefined) : boolean {
|
|
76
|
+
if(step1 == undefined || step2 == undefined){
|
|
77
|
+
return false
|
|
78
|
+
} else if(step1.type !== step2.type){
|
|
79
|
+
return false
|
|
80
|
+
} else {
|
|
81
|
+
switch (step1.type) {
|
|
82
|
+
case "property": return step2.type === "property" && step1.propertyName === step2.propertyName
|
|
83
|
+
case "position": return step2.type === "position" && step1.position === step2.position
|
|
84
|
+
case "lookup": {
|
|
85
|
+
return step2.type === "lookup"
|
|
86
|
+
&& step1.keys?.length === step2.keys?.length
|
|
87
|
+
&& step1.keys?.every(
|
|
88
|
+
pv1 => step2.keys.some(
|
|
89
|
+
pv2 => areSamePropertyValuePair(pv1, pv2)
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function areSamePropertyValuePair(p1: PropertyValuePair, p2: PropertyValuePair) {
|
|
98
|
+
return p1.propertyName === p2.propertyName
|
|
99
|
+
&& p1.propertyValue === p2.propertyValue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function itineraryOf(... args : (string | number)[]) : Itinerary | undefined{
|
|
103
|
+
return args.reduce((parent, e) => {
|
|
104
|
+
const value = typeof e === 'string' ? propertyStep(e) : positionStep(e)
|
|
105
|
+
return {parent, value}
|
|
106
|
+
}, undefined as StackElement<ItineraryStep> | undefined)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const dataStep = propertyStep("data")
|
|
110
|
+
const childrenStep = propertyStep("children")
|
|
111
|
+
|
|
112
|
+
export function itineraryOverlay(it : Itinerary | undefined) : Itinerary {
|
|
113
|
+
return {value:dataStep, parent:itineraryOverlayInner(it)}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function itineraryOverlayInner(it : Itinerary | undefined) : Itinerary | undefined{
|
|
117
|
+
if(it === undefined){
|
|
118
|
+
return undefined
|
|
119
|
+
} else {
|
|
120
|
+
const {value, parent} = it
|
|
121
|
+
return {value, parent:{value:childrenStep, parent: itineraryOverlayInner(parent)}}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function itineraryToStepArray(itinerary :Itinerary | undefined) : ItineraryStep[] {
|
|
126
|
+
let itn : Itinerary | undefined = itinerary
|
|
127
|
+
const result : ItineraryStep[] = []
|
|
128
|
+
while (itn !== undefined){
|
|
129
|
+
result.push(itn.value)
|
|
130
|
+
itn = itn.parent
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result.reverse()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function propertyStep(propertyName: string) : PropertyStep {
|
|
137
|
+
return {type:"property", propertyName}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function positionStep(position: number) : PositionStep {
|
|
141
|
+
return {type:"position", position}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function lookupStep(criteria: Record<string, any>) : LookupStep {
|
|
145
|
+
return {type:"lookup", keys:Object.entries(criteria).map(([k, v]) => ({propertyName:k, propertyValue:v}))}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function xpathFromStack(path?: StackElement<ItineraryStep>) : string{
|
|
149
|
+
if(path === undefined){
|
|
150
|
+
return '' // TODO may need to return '.' here, to be checked
|
|
151
|
+
}else{
|
|
152
|
+
const {value, parent} = path
|
|
153
|
+
const parentXPath = xpathFromStack(parent)
|
|
154
|
+
|
|
155
|
+
switch(value.type){
|
|
156
|
+
case 'property' : {
|
|
157
|
+
return `${parentXPath}/${value.propertyName}`
|
|
158
|
+
}
|
|
159
|
+
case 'position' : {
|
|
160
|
+
return `${parentXPath}/*[${value.position + 1}]`
|
|
161
|
+
}
|
|
162
|
+
case 'lookup' : {
|
|
163
|
+
const predicate = value.keys.map(({propertyName,propertyValue}) => `${propertyName}=$${propertyName}`).join(' and ')
|
|
164
|
+
return `${parentXPath}/*[${predicate}]`
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function navigate(from: any, itinerary : Itinerary | undefined, prototype? : StackElement<any>) : any {
|
|
172
|
+
if(from === undefined){
|
|
173
|
+
return undefined
|
|
174
|
+
}
|
|
175
|
+
if(itinerary === undefined){
|
|
176
|
+
return from
|
|
177
|
+
}
|
|
178
|
+
const {parent, value : step} = itinerary
|
|
179
|
+
|
|
180
|
+
const current = navigate(from, parent, prototype?.parent)
|
|
181
|
+
|
|
182
|
+
return navigateStep(current, step, prototype?.value)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function navigateStep(from: any, step : ItineraryStep, prototype? : any) : any {
|
|
186
|
+
if(from === undefined){
|
|
187
|
+
return undefined
|
|
188
|
+
}
|
|
189
|
+
if(step === undefined){
|
|
190
|
+
return from
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if(Array.isArray(from)){
|
|
194
|
+
if(step.type === 'lookup'){
|
|
195
|
+
const stp = step
|
|
196
|
+
const result = from.find(e => e !== undefined && stp.keys.every(({propertyName, propertyValue}) => e[propertyName] === propertyValue))
|
|
197
|
+
if (result === undefined && prototype !== undefined){
|
|
198
|
+
from.push(prototype)
|
|
199
|
+
return prototype
|
|
200
|
+
} else {
|
|
201
|
+
return result
|
|
202
|
+
}
|
|
203
|
+
}else if(step.type === 'position'){
|
|
204
|
+
const result = from[step.position];
|
|
205
|
+
if (result === undefined && prototype !== undefined){
|
|
206
|
+
from[step.position] = prototype
|
|
207
|
+
return prototype
|
|
208
|
+
} else {
|
|
209
|
+
return result
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}else if(typeof from === 'object'){
|
|
213
|
+
if(step.type === 'property'){
|
|
214
|
+
const result = from === null ? undefined : from[step.propertyName];
|
|
215
|
+
if(result === undefined && prototype !== undefined && from !== null && from !== undefined){
|
|
216
|
+
from[step.propertyName] = prototype
|
|
217
|
+
return prototype
|
|
218
|
+
} else {
|
|
219
|
+
return result
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return undefined
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function navigationDepth(
|
|
228
|
+
from: any,
|
|
229
|
+
itinerary: Itinerary | undefined,
|
|
230
|
+
originalPath: string = xpathFromStack(itinerary)
|
|
231
|
+
): { value: any; path: string; originalPath: string } {
|
|
232
|
+
if (from === undefined || from === null) {
|
|
233
|
+
return { value: undefined, path: '', originalPath };
|
|
234
|
+
}
|
|
235
|
+
if (itinerary === undefined) {
|
|
236
|
+
return { value: from, path: '', originalPath };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { parent, value: step } = itinerary;
|
|
240
|
+
const current = navigationDepth(from, parent, originalPath);
|
|
241
|
+
|
|
242
|
+
if (current.value === undefined || current.value === null) {
|
|
243
|
+
return current;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const next = navigateStep(current.value, step);
|
|
247
|
+
|
|
248
|
+
if (next === undefined || next === null) {
|
|
249
|
+
return { value: current.value, path: xpathFromStack(parent), originalPath };
|
|
250
|
+
} else {
|
|
251
|
+
return { value: next, path: xpathFromStack(itinerary), originalPath };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
class OverlayNavigator {
|
|
256
|
+
|
|
257
|
+
navigate<T>(from: HyperLayerNode<T>, itinerary : Itinerary | undefined) : HyperLayerNode<T> | undefined{
|
|
258
|
+
if(from === undefined){
|
|
259
|
+
return undefined
|
|
260
|
+
}
|
|
261
|
+
if(itinerary === undefined){
|
|
262
|
+
return from
|
|
263
|
+
}
|
|
264
|
+
const {parent, value : step} = itinerary
|
|
265
|
+
|
|
266
|
+
const current = this.navigate(from, parent)
|
|
267
|
+
|
|
268
|
+
return current === undefined ? undefined : this.navigateStep(current, step)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
protected navigateStep<T>(from: HyperLayerNode<T>, step : ItineraryStep) : HyperLayerNode<T> | undefined {
|
|
272
|
+
if(from === undefined){
|
|
273
|
+
return undefined
|
|
274
|
+
}
|
|
275
|
+
if(step === undefined){
|
|
276
|
+
return from
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if(Array.isArray(from.children)){
|
|
280
|
+
if(step.type === 'lookup'){
|
|
281
|
+
const stp = step
|
|
282
|
+
const result = from.children.find(e => e !== undefined && stp.keys.every(({propertyName, propertyValue}) => e.keys?.[propertyName] === propertyValue))
|
|
283
|
+
return result
|
|
284
|
+
}else if(step.type === 'position'){
|
|
285
|
+
const result = from.children[step.position];
|
|
286
|
+
return result
|
|
287
|
+
}
|
|
288
|
+
}else if(from.children !== null && typeof from.children === 'object'){
|
|
289
|
+
if(step.type === 'property'){
|
|
290
|
+
const result = from === null ? undefined : from.children[step.propertyName];
|
|
291
|
+
return result
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return undefined
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export const overlayNavigator = new OverlayNavigator()
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Same as {navigate} but try to path missing data node using prototype parameter
|
|
304
|
+
* @param from
|
|
305
|
+
* @param itinerary
|
|
306
|
+
* @param prototype
|
|
307
|
+
*/
|
|
308
|
+
export function ensurePath(from: any, itinerary : Itinerary | undefined, prototype? : any) : any {
|
|
309
|
+
return navigate(from, itinerary, prototype)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
interface PointerBase {
|
|
313
|
+
type : "absolute" | "relative"
|
|
314
|
+
itinerary : ItineraryStep[]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export interface AbsolutePointer extends PointerBase {
|
|
318
|
+
type : "absolute"
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface RelativePointer extends PointerBase {
|
|
322
|
+
type : "relative"
|
|
323
|
+
originUuid : ObjectId
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
abstract class AbstractPointerBuilder {
|
|
327
|
+
readonly itinerary : ItineraryStep[] = []
|
|
328
|
+
public child(propertyName : string){
|
|
329
|
+
this.itinerary.push({type:"property", propertyName})
|
|
330
|
+
return this
|
|
331
|
+
}
|
|
332
|
+
public lookup(...criterion : [string, any]){
|
|
333
|
+
criterion.forEach(([propertyName, propertyValue]) => {
|
|
334
|
+
this.itinerary.push({type:"lookup", keys:[{propertyName, propertyValue}]})
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
class RelativePointerBuilder extends AbstractPointerBuilder implements RelativePointer {
|
|
340
|
+
readonly type = "relative"
|
|
341
|
+
public constructor(public originUuid : ObjectId){
|
|
342
|
+
super()
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class AbsolutePointerBuilder extends AbstractPointerBuilder implements AbsolutePointer {
|
|
347
|
+
readonly type = "absolute"
|
|
348
|
+
public constructor(){
|
|
349
|
+
super()
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
type Pointer = AbsolutePointer | RelativePointer
|
|
354
|
+
|
|
355
|
+
export function pointer(fromId?: ObjectId) : AbsolutePointerBuilder | RelativePointerBuilder {
|
|
356
|
+
if(fromId === undefined){
|
|
357
|
+
return new AbsolutePointerBuilder()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return new RelativePointerBuilder(fromId)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export const ROOT : AbsolutePointer = Object.freeze({type:"absolute", itinerary:[]})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import stack from "./stack";
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe("Core util stack", () => {
|
|
5
|
+
it("fromArray should work", () => {
|
|
6
|
+
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
7
|
+
|
|
8
|
+
expect(itinerary?.value).toBe(4)
|
|
9
|
+
expect(itinerary?.parent?.parent?.parent?.value).toBe(1)
|
|
10
|
+
|
|
11
|
+
expect(stack.fromArray([])).toBe(undefined)
|
|
12
|
+
expect(stack.fromArray(undefined)).toBe(undefined)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("reduce should work", () => {
|
|
16
|
+
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
17
|
+
|
|
18
|
+
const result = stack.reduce(itinerary, (acc, t) => acc + t, 0);
|
|
19
|
+
expect(result).toBe(10)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("reduce order should work", () => {
|
|
23
|
+
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
24
|
+
|
|
25
|
+
const result = stack.reduce(itinerary, (acc, t) => [...acc, t], [] as number[]);
|
|
26
|
+
expect(result).toStrictEqual([1, 2, 3, 4])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("equal should work", () => {
|
|
30
|
+
expect(stack.equal(undefined, undefined)).toBe(false)
|
|
31
|
+
expect(stack.equal(null, null)).toBe(false)
|
|
32
|
+
expect(stack.equal(undefined, null)).toBe(false)
|
|
33
|
+
expect(stack.equal(null, undefined)).toBe(false)
|
|
34
|
+
|
|
35
|
+
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), undefined)).toBe(false)
|
|
36
|
+
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), null)).toBe(false)
|
|
37
|
+
|
|
38
|
+
expect(stack.equal(undefined, stack.fromArray([1, 2, 3, 4]))).toBe(false)
|
|
39
|
+
expect(stack.equal(null, stack.fromArray([1, 2, 3, 4]))).toBe(false)
|
|
40
|
+
|
|
41
|
+
expect(stack.equal(stack.fromArray([1]), stack.fromArray([2]))).toBe(false)
|
|
42
|
+
expect(stack.equal(stack.fromArray([1]), stack.fromArray([1, 2]))).toBe(false)
|
|
43
|
+
|
|
44
|
+
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), stack.fromArray([1, 2, 3, 4]))).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface StackElement<T> {
|
|
2
|
+
value : T
|
|
3
|
+
parent? : StackElement<T>
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function head<T>(value : T) : StackElement<T> {
|
|
7
|
+
return {value}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function push<T>(head : StackElement<T> | undefined, value : T) : StackElement<T> {
|
|
11
|
+
return {value, parent: head}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function concat<T>(tail : StackElement<T> | undefined, stack : StackElement<T> | undefined) : StackElement<T> | undefined{
|
|
15
|
+
if(tail === undefined) return stack
|
|
16
|
+
if(stack === undefined) return tail
|
|
17
|
+
|
|
18
|
+
const {parent, value} = stack
|
|
19
|
+
|
|
20
|
+
return {value, parent:concat(tail, parent)}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function fromArray<T = any>(t : T[] | undefined) : StackElement<T> | undefined {
|
|
24
|
+
return t?.reduce((acc, t) => push(acc, t), undefined as undefined | StackElement<T>)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function reduce<T, R>(head : StackElement<T> | undefined, reducer : (r: R, t : T) => R, initial : R) : R {
|
|
28
|
+
if(head === undefined){
|
|
29
|
+
return initial
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {parent, value} = head
|
|
33
|
+
const acc = reduce(parent, reducer, initial)
|
|
34
|
+
return reducer(acc, value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function equal<T>(a:StackElement<T> | null | undefined, b : StackElement<T> | null | undefined) : boolean {
|
|
38
|
+
// Not equal if both are nullish
|
|
39
|
+
if( a === undefined || a === null || b === undefined || b === null ){
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
while (a !== undefined && b !== undefined){
|
|
44
|
+
if(a.value !== b.value){
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
a = a.parent
|
|
49
|
+
b = b.parent
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Equal if traversed all stack at both side
|
|
53
|
+
if((a === undefined || a === null) && (b === undefined || b === null) ){
|
|
54
|
+
return true
|
|
55
|
+
} else {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stack = {push, head, concat, reduce, fromArray, equal}
|
|
61
|
+
|
|
62
|
+
export default stack;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {Path, Pathway} from "./pathway";
|
|
2
|
+
|
|
3
|
+
export interface PathwayResolver<T>{
|
|
4
|
+
resolve(path : string | string[] | Pathway) : T | undefined
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class RecordPathwayResolver<T> implements PathwayResolver<T> {
|
|
8
|
+
constructor(readonly record : Record<string, T>) {
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
resolve(path: string | string[] | Pathway): T | undefined {
|
|
12
|
+
const key = Pathway.asString(path)
|
|
13
|
+
return this.record[key]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class EmptyPathwayResolver<T> implements PathwayResolver<T> {
|
|
18
|
+
resolve(path: string | string[] | Pathway): T | undefined {
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class RelativePathwayResolver<T> implements PathwayResolver<T> {
|
|
24
|
+
private relativePathway : Pathway
|
|
25
|
+
constructor(readonly parentResolver : PathwayResolver<T>, readonly path : Path) {
|
|
26
|
+
this.relativePathway = Pathway.of(path)
|
|
27
|
+
}
|
|
28
|
+
resolve(path: string | string[] | Pathway): T | undefined {
|
|
29
|
+
const pathway = Pathway.of(path)
|
|
30
|
+
if(pathway.isAbsolute()){
|
|
31
|
+
return this.parentResolver.resolve(path)
|
|
32
|
+
} else {
|
|
33
|
+
return this.parentResolver.resolve(this.relativePathway.resolve(pathway))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|