@hamak/shared-utils 0.4.1
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/dist/core-utils-chrono.d.ts +8 -0
- package/dist/core-utils-chrono.d.ts.map +1 -0
- package/dist/core-utils-chrono.js +16 -0
- package/dist/core-utils-comparator.d.ts +11 -0
- package/dist/core-utils-comparator.d.ts.map +1 -0
- package/dist/core-utils-comparator.js +22 -0
- package/dist/core-utils-file.d.ts +10 -0
- package/dist/core-utils-file.d.ts.map +1 -0
- package/dist/core-utils-file.js +1 -0
- package/dist/core-utils-filesystem.d.ts +69 -0
- package/dist/core-utils-filesystem.d.ts.map +1 -0
- package/dist/core-utils-filesystem.js +156 -0
- package/dist/core-utils-fqn.d.ts +11 -0
- package/dist/core-utils-fqn.d.ts.map +1 -0
- package/dist/core-utils-fqn.js +19 -0
- package/dist/core-utils-pathway-resolver.d.ts +20 -0
- package/dist/core-utils-pathway-resolver.d.ts.map +1 -0
- package/dist/core-utils-pathway-resolver.js +31 -0
- package/dist/core-utils-pathway.d.ts +84 -0
- package/dist/core-utils-pathway.d.ts.map +1 -0
- package/dist/core-utils-pathway.js +206 -0
- package/dist/core-utils-registry.d.ts +7 -0
- package/dist/core-utils-registry.d.ts.map +1 -0
- package/dist/core-utils-registry.js +22 -0
- package/dist/core-utils-resolver.d.ts +33 -0
- package/dist/core-utils-resolver.d.ts.map +1 -0
- package/dist/core-utils-resolver.js +65 -0
- package/dist/core-utils-stack.d.ts +20 -0
- package/dist/core-utils-stack.d.ts.map +1 -0
- package/dist/core-utils-stack.js +47 -0
- package/dist/core-utils-types-hkt.d.ts +12 -0
- package/dist/core-utils-types-hkt.d.ts.map +1 -0
- package/dist/core-utils-types-hkt.js +1 -0
- package/dist/core-utils-types.d.ts +83 -0
- package/dist/core-utils-types.d.ts.map +1 -0
- package/dist/core-utils-types.js +39 -0
- package/dist/core-utils-validations.d.ts +21 -0
- package/dist/core-utils-validations.d.ts.map +1 -0
- package/dist/core-utils-validations.js +18 -0
- package/dist/core-utils.d.ts +39 -0
- package/dist/core-utils.d.ts.map +1 -0
- package/dist/core-utils.js +113 -0
- package/dist/es2015/core-utils-chrono.js +16 -0
- package/dist/es2015/core-utils-comparator.js +22 -0
- package/dist/es2015/core-utils-file.js +1 -0
- package/dist/es2015/core-utils-filesystem.js +157 -0
- package/dist/es2015/core-utils-fqn.js +19 -0
- package/dist/es2015/core-utils-pathway-resolver.js +31 -0
- package/dist/es2015/core-utils-pathway.js +206 -0
- package/dist/es2015/core-utils-registry.js +22 -0
- package/dist/es2015/core-utils-resolver.js +68 -0
- package/dist/es2015/core-utils-stack.js +47 -0
- package/dist/es2015/core-utils-types-hkt.js +1 -0
- package/dist/es2015/core-utils-types.js +39 -0
- package/dist/es2015/core-utils-validations.js +18 -0
- package/dist/es2015/core-utils.js +113 -0
- package/dist/es2015/index.js +24 -0
- package/dist/es2015/itinerary/hyper-layer-node.js +21 -0
- package/dist/es2015/itinerary/itinerary.js +276 -0
- package/dist/es2015/itinerary/stack.js +47 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -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/stack.d.ts +20 -0
- package/dist/itinerary/stack.d.ts.map +1 -0
- package/dist/itinerary/stack.js +47 -0
- package/package.json +41 -0
- package/src/core-utils-chrono.ts +21 -0
- package/src/core-utils-comparator.ts +27 -0
- package/src/core-utils-file.ts +10 -0
- package/src/core-utils-filesystem.spec.ts +63 -0
- package/src/core-utils-filesystem.ts +232 -0
- package/src/core-utils-fqn.ts +17 -0
- package/src/core-utils-pathway-resolver.ts +36 -0
- package/src/core-utils-pathway.ts +232 -0
- package/src/core-utils-registry.ts +23 -0
- package/src/core-utils-resolver.ts +75 -0
- package/src/core-utils-stack.spec.ts +42 -0
- package/src/core-utils-stack.ts +62 -0
- package/src/core-utils-types-hkt.ts +9 -0
- package/src/core-utils-types.ts +127 -0
- package/src/core-utils-validations.ts +36 -0
- package/src/core-utils.spec.ts +56 -0
- package/src/core-utils.ts +139 -0
- package/src/index.ts +34 -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/tsconfig.es2015.json +24 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { Path, Pathway } from './core-utils-pathway';
|
|
2
|
+
import { Holder } from './core-utils-types';
|
|
3
|
+
import { PathwayResolver } from './core-utils-pathway-resolver';
|
|
4
|
+
|
|
5
|
+
//export type FileContentSchema = string | {schemaObject : SchemaNode, namespace? : string}
|
|
6
|
+
export type FileContentSchema = string | { schemaObject: string /*TypeString*/, namespace?: string }
|
|
7
|
+
|
|
8
|
+
export interface FileSystemState {
|
|
9
|
+
root: DirectoryNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FileMemoState {
|
|
13
|
+
originalContent: Holder
|
|
14
|
+
modified: boolean // tell whether the current file content is modified compared to original
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FileSystemNodeState {
|
|
18
|
+
contentLoaded: boolean
|
|
19
|
+
contentLoading: boolean
|
|
20
|
+
contentLoadError?: { code?: string, message?: string }
|
|
21
|
+
memo?: FileMemoState
|
|
22
|
+
contentHistory: any[]
|
|
23
|
+
|
|
24
|
+
extensionStates: Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export function fileSystemNodeInitialState(contentPresent = true): FileSystemNodeState {
|
|
29
|
+
return {
|
|
30
|
+
contentLoaded: contentPresent,
|
|
31
|
+
contentLoading: false,
|
|
32
|
+
contentLoadError: undefined,
|
|
33
|
+
contentHistory: [],
|
|
34
|
+
extensionStates: {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export interface FileSystemNodeBase {
|
|
40
|
+
type: 'directory' | 'file'
|
|
41
|
+
name: string
|
|
42
|
+
state: FileSystemNodeState
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export interface DirectoryNode extends FileSystemNodeBase {
|
|
47
|
+
type: 'directory'
|
|
48
|
+
children: Record<string, FileSystemNode>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface FileNode<T = any> extends FileSystemNodeBase {
|
|
52
|
+
type: 'file'
|
|
53
|
+
content: T
|
|
54
|
+
schema: string | FileContentSchema
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type FileSystemNode = DirectoryNode | FileNode
|
|
58
|
+
|
|
59
|
+
function isRoot(node : FileSystemNode) : boolean {
|
|
60
|
+
switch (node.type) {
|
|
61
|
+
case "directory":{
|
|
62
|
+
return node.name === null || node.name === undefined || node.name.trim() === "" || node.name.trim() === "/"
|
|
63
|
+
}
|
|
64
|
+
case "file":{
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class DirectoryNodePathwayResolver implements PathwayResolver<FileSystemNode> {
|
|
71
|
+
constructor(readonly root: FileSystemNode) {
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
resolve(path: string | Pathway | string[]): FileSystemNode | undefined {
|
|
75
|
+
return fsutils.resolveFileNode(this.root, path)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
changeDIr(path : Path) : DirectoryNodePathwayResolver {
|
|
79
|
+
const fileNode = this.resolve(path)
|
|
80
|
+
if(fileNode === undefined){
|
|
81
|
+
throw new Error(`No such directory: ${Pathway.asString(path)}`)
|
|
82
|
+
} else {
|
|
83
|
+
switch (fileNode.type) {
|
|
84
|
+
case "directory": return new DirectoryNodePathwayResolver(fileNode)
|
|
85
|
+
case "file": throw new Error(`Path is not directory: ${Pathway.asString(path)}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
class FilesystemUtil{
|
|
92
|
+
createRootDir(children : FileSystemNode[], state = fileSystemNodeInitialState()) : DirectoryNode {
|
|
93
|
+
return this.createDir("", children, state)
|
|
94
|
+
}
|
|
95
|
+
createDir(name : string, children : FileSystemNode[], state = fileSystemNodeInitialState()) : DirectoryNode {
|
|
96
|
+
return {
|
|
97
|
+
type: "directory", name, state,
|
|
98
|
+
children: children.reduce((acc, child) => ({...acc, [child.name]: child}), {} as Record<string, FileSystemNode>)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
createDirs(path : Path, children : FileSystemNode[], state = fileSystemNodeInitialState()) : DirectoryNode {
|
|
102
|
+
const pathway = Pathway.of(path)
|
|
103
|
+
const dir = pathway.getSegments().reverse().reduce((acc, segment) => {
|
|
104
|
+
return this.createDir(segment, acc === undefined ? children : [acc])
|
|
105
|
+
}, undefined as DirectoryNode | undefined)
|
|
106
|
+
|
|
107
|
+
if(dir === undefined){
|
|
108
|
+
throw new Error(`Unable de create dir '${path}'`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return pathway.isAbsolute() ? this.createRootDir([dir]) : dir
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
createFile(name : string, content : any, schema = "xs:any", state = fileSystemNodeInitialState()) : FileNode {
|
|
115
|
+
return {type: "file", name, content, schema, state}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
resolveFileNode(fromDir: FileSystemNode | undefined, path : Path) : FileSystemNode | undefined {
|
|
119
|
+
if(fromDir === undefined){
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
const pathway = Pathway.of(path)
|
|
123
|
+
if (pathway.isAbsolute() && !isRoot(fromDir)) {
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result = pathway.getSegments().reduce((acc, segment) => {
|
|
128
|
+
if (acc === undefined) {
|
|
129
|
+
return undefined
|
|
130
|
+
} else {
|
|
131
|
+
switch (acc.type) {
|
|
132
|
+
case "directory": {
|
|
133
|
+
return acc.children?.[segment]
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "file": {
|
|
137
|
+
return undefined
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
default: {
|
|
141
|
+
return undefined
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}, fromDir as FileSystemNode | undefined)
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
asFile<T = any>(fsn : FileSystemNode | undefined) : FileNode<T> | undefined {
|
|
152
|
+
return fsn?.type === "file" ? fsn : undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
asDirectory(fsn : FileSystemNode | undefined) : DirectoryNode | undefined {
|
|
156
|
+
return fsn?.type === "directory" ? fsn : undefined
|
|
157
|
+
}
|
|
158
|
+
resolveDescendent(fromDir: FileSystemNode | undefined, path : Path) : FileSystemNode | undefined {
|
|
159
|
+
const pathway = Pathway.of(path)
|
|
160
|
+
return pathway.isCurrentDir() ? undefined : this.resolveFileNode(fromDir, pathway)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Set a file node content a specified location. It create intermediary directories and target if missed location.
|
|
165
|
+
* @param fromDir
|
|
166
|
+
* @param path
|
|
167
|
+
* @param content
|
|
168
|
+
* @param schema
|
|
169
|
+
* @param state
|
|
170
|
+
*/
|
|
171
|
+
createDescendent(
|
|
172
|
+
fromDir: FileSystemNode | undefined,
|
|
173
|
+
path: Path,
|
|
174
|
+
content: any,
|
|
175
|
+
schema = "xs:any",
|
|
176
|
+
state = fileSystemNodeInitialState(),
|
|
177
|
+
override = false
|
|
178
|
+
): FileNode | undefined {
|
|
179
|
+
if (!fromDir) return undefined;
|
|
180
|
+
|
|
181
|
+
const pathway = Pathway.of(path);
|
|
182
|
+
if (pathway.isCurrentDir()) return undefined;
|
|
183
|
+
|
|
184
|
+
const segments = pathway.getSegments();
|
|
185
|
+
if (segments.length === 0) return undefined;
|
|
186
|
+
|
|
187
|
+
if (fromDir.type !== "directory") {
|
|
188
|
+
throw new Error("Cannot create descendent on a non-directory node.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let currentDir = fromDir;
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
194
|
+
const segment = segments[i];
|
|
195
|
+
let nextNode = currentDir.children[segment];
|
|
196
|
+
|
|
197
|
+
if (!nextNode) {
|
|
198
|
+
// Create intermediate directory
|
|
199
|
+
const newDir: DirectoryNode = this.createDir(segment, []);
|
|
200
|
+
currentDir.children[segment] = newDir;
|
|
201
|
+
nextNode = newDir;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (nextNode.type !== "directory") {
|
|
205
|
+
throw new Error(`Path conflict: '${segment}' is a file, expected a directory.`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
currentDir = nextNode;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const fileName = segments[segments.length - 1];
|
|
212
|
+
const existingNode = currentDir.children[fileName];
|
|
213
|
+
|
|
214
|
+
if (existingNode) {
|
|
215
|
+
if (existingNode.type !== "file") {
|
|
216
|
+
throw new Error(`Path conflict: '${fileName}' exists and is not a file.`);
|
|
217
|
+
}
|
|
218
|
+
if (!override) {
|
|
219
|
+
throw new Error(`File '${fileName}' already exists and override is disabled.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const fileNode: FileNode = this.createFile(fileName, content, schema, state);
|
|
224
|
+
|
|
225
|
+
currentDir.children[fileName] = fileNode;
|
|
226
|
+
|
|
227
|
+
return fileNode;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const fsutils = new FilesystemUtil()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unresolved qualified name by either namespace of prefix
|
|
3
|
+
*/
|
|
4
|
+
export class LexicalQualifiedName {
|
|
5
|
+
constructor(public readonly namespace: string | undefined, public readonly prefix : string | undefined, public readonly name : string) {
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
isEqualTo(other : LexicalQualifiedName) : boolean {
|
|
9
|
+
if((this.name === undefined && other.name === undefined)
|
|
10
|
+
|| (this.name === null && other.name === null)){
|
|
11
|
+
return false
|
|
12
|
+
} else {
|
|
13
|
+
return this.namespace === other.namespace && this.prefix === other.prefix && this.name === other.name
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {Path, Pathway} from "./core-utils-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
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export type Path = string | string[] | Pathway
|
|
2
|
+
/**
|
|
3
|
+
* Class representing a normalized pathway.
|
|
4
|
+
*/
|
|
5
|
+
export class Pathway {
|
|
6
|
+
private segments: string[];
|
|
7
|
+
private isAbsolutePath: boolean;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a Pathway instance.
|
|
11
|
+
* @param path - The pathway to normalize, either as a string or an array of strings.
|
|
12
|
+
* An empty array or "." represents the current directory.
|
|
13
|
+
*/
|
|
14
|
+
constructor(path: string | string[] | null | undefined) {
|
|
15
|
+
if (path == null || path === "." || (Array.isArray(path) && path.length === 0)) {
|
|
16
|
+
this.segments = [];
|
|
17
|
+
this.isAbsolutePath = false;
|
|
18
|
+
} else {
|
|
19
|
+
this.isAbsolutePath = Array.isArray(path) ? path[0].startsWith('/') : path.startsWith('/');
|
|
20
|
+
this.segments = this.normalizePath(path);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Factory method to create a Pathway instance.
|
|
26
|
+
* @param path - The pathway to normalize, either as a string or an array of strings.
|
|
27
|
+
* @returns A new Pathway instance.
|
|
28
|
+
*/
|
|
29
|
+
public static of(path: string | string[] | null | undefined | Pathway): Pathway {
|
|
30
|
+
if(path !== undefined && path !== null && typeof path === "object" && !Array.isArray(path)) {
|
|
31
|
+
return path
|
|
32
|
+
} else {
|
|
33
|
+
return new Pathway(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalizes a pathway by converting it into an array of segments.
|
|
39
|
+
* @param path - The pathway to normalize, either as a string or an array of strings.
|
|
40
|
+
* @returns An array of normalized path segments, or an empty array if the input is null or undefined.
|
|
41
|
+
*/
|
|
42
|
+
private normalizePath(path: string | string[] | null | undefined): string[] {
|
|
43
|
+
if (path == null) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const segments = Array.isArray(path) ? path : path.split('/');
|
|
48
|
+
return segments
|
|
49
|
+
.reduce((acc, segment) => {
|
|
50
|
+
if (segment === "." || segment === "") {
|
|
51
|
+
return acc;
|
|
52
|
+
}
|
|
53
|
+
return acc.concat(segment.split('/'));
|
|
54
|
+
}, [] as string[])
|
|
55
|
+
.filter(segment => segment.length > 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the normalized path segments.
|
|
60
|
+
* @returns An array of normalized path segments.
|
|
61
|
+
*/
|
|
62
|
+
public getSegments(): string[] {
|
|
63
|
+
return this.segments;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public isCurrentDir(){
|
|
67
|
+
return this.getSegments().length === 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Converts the normalized path segments back to a string.
|
|
72
|
+
* @returns The normalized path as a string.
|
|
73
|
+
*/
|
|
74
|
+
public toString(): string {
|
|
75
|
+
return this.isAbsolute() ? '/' + this.segments.join('/') : this.segments.join('/');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Checks if the pathway is absolute.
|
|
80
|
+
* @returns True if the pathway is absolute, false otherwise.
|
|
81
|
+
*/
|
|
82
|
+
public isAbsolute(): boolean {
|
|
83
|
+
return this.isAbsolutePath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Checks if the pathway is relative.
|
|
88
|
+
* @returns True if the pathway is relative, false otherwise.
|
|
89
|
+
*/
|
|
90
|
+
public isRelative(): boolean {
|
|
91
|
+
return !this.isAbsolutePath;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolves the current pathway with another pathway or string.
|
|
96
|
+
* @param otherPath - The other pathway or string to resolve against.
|
|
97
|
+
* @returns A new Pathway instance representing the resolved path.
|
|
98
|
+
*/
|
|
99
|
+
public resolve(otherPath: Pathway | string | string[]): Pathway {
|
|
100
|
+
const otherSegments = otherPath instanceof Pathway ? otherPath.getSegments() : this.normalizePath(otherPath);
|
|
101
|
+
const resolvedSegments = [...this.segments];
|
|
102
|
+
|
|
103
|
+
if (otherPath instanceof Pathway ? otherPath.isAbsolute() : (Array.isArray(otherPath) ? otherPath[0].startsWith('/') : otherPath.startsWith('/'))) {
|
|
104
|
+
return new Pathway(otherSegments);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const segment of otherSegments) {
|
|
108
|
+
if (segment === '..') {
|
|
109
|
+
if (resolvedSegments.length > 0 && resolvedSegments[resolvedSegments.length - 1] !== '..') {
|
|
110
|
+
resolvedSegments.pop();
|
|
111
|
+
} else {
|
|
112
|
+
resolvedSegments.push(segment);
|
|
113
|
+
}
|
|
114
|
+
} else if (segment !== '.') {
|
|
115
|
+
resolvedSegments.push(segment);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return new Pathway(this.isAbsolute() ? ["/", ...resolvedSegments] : resolvedSegments);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Gets the parent directory of the current pathway.
|
|
124
|
+
* @returns A new Pathway instance representing the parent directory.
|
|
125
|
+
*/
|
|
126
|
+
public getParent(): Pathway {
|
|
127
|
+
if (this.segments.length === 0) {
|
|
128
|
+
return new Pathway(null);
|
|
129
|
+
}
|
|
130
|
+
const parentSegments = this.segments.slice(0, -1);
|
|
131
|
+
return new Pathway(this.isAbsolute() ? ["/", ...parentSegments] : parentSegments);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Gets the current directory of the pathway.
|
|
136
|
+
* @returns The current directory as a string.
|
|
137
|
+
*/
|
|
138
|
+
public getCurrentDirectory(): string {
|
|
139
|
+
if (this.segments.length === 0) {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
return this.segments[this.segments.length - 1];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Static method to get the absolute path of the root.
|
|
147
|
+
* @returns A new Pathway instance representing the root path.
|
|
148
|
+
*/
|
|
149
|
+
public static ofRoot(): Pathway {
|
|
150
|
+
return new Pathway('/');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Determines the relative path from this pathway to another pathway.
|
|
155
|
+
* @param otherPath - The other pathway to relativize against.
|
|
156
|
+
* @returns A new Pathway instance representing the relative path.
|
|
157
|
+
*/
|
|
158
|
+
public relativize(otherPath: Pathway | string | string[]): Pathway {
|
|
159
|
+
const otherSegments = otherPath instanceof Pathway ? otherPath.getSegments() : this.normalizePath(otherPath);
|
|
160
|
+
|
|
161
|
+
if (this.isAbsolute() !== (otherPath instanceof Pathway ? otherPath.isAbsolute() : (Array.isArray(otherPath) ? otherPath[0].startsWith('/') : otherPath.startsWith('/')))) {
|
|
162
|
+
throw new Error("Cannot relativize between absolute and relative paths");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let commonLength = 0;
|
|
166
|
+
const maxLength = Math.min(this.segments.length, otherSegments.length);
|
|
167
|
+
for (let i = 0; i < maxLength; i++) {
|
|
168
|
+
if (this.segments[i] !== otherSegments[i]) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
commonLength++;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const upSegments = this.segments.slice(commonLength).map(() => '..');
|
|
175
|
+
const downSegments = otherSegments.slice(commonLength);
|
|
176
|
+
|
|
177
|
+
return new Pathway(upSegments.concat(downSegments));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Checks if the given path is a descendant of the current pathway.
|
|
182
|
+
* @param otherPath - The other pathway to check.
|
|
183
|
+
* @returns True if the given path is a descendant, false otherwise.
|
|
184
|
+
*/
|
|
185
|
+
public isDescendant(otherPath: Pathway | string | string[]): boolean {
|
|
186
|
+
const otherSegments = otherPath instanceof Pathway ? otherPath.getSegments() : this.normalizePath(otherPath);
|
|
187
|
+
|
|
188
|
+
if (otherSegments.length < this.segments.length) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < this.segments.length; i++) {
|
|
193
|
+
if (this.segments[i] !== otherSegments[i]) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
static isRelativePath(path : Path) : boolean {
|
|
203
|
+
const pathway = typeof path === "object" && !Array.isArray(path) ? path : Pathway.of(path)
|
|
204
|
+
return pathway.isRelative()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static asString(path : Path) : string {
|
|
208
|
+
if(typeof path === "string"){
|
|
209
|
+
return path
|
|
210
|
+
} else {
|
|
211
|
+
const pathway = typeof path === "object" && !Array.isArray(path)? path : Pathway.of(path)
|
|
212
|
+
return pathway.toString()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static getName(path: string | string[] | Pathway) {
|
|
217
|
+
let name: string
|
|
218
|
+
switch (typeof path) {
|
|
219
|
+
case "string": {
|
|
220
|
+
name = path
|
|
221
|
+
}
|
|
222
|
+
break
|
|
223
|
+
case "object": {
|
|
224
|
+
const segments = Array.isArray(path) ? path : path.getSegments()
|
|
225
|
+
name = segments[segments.length - 1]
|
|
226
|
+
}
|
|
227
|
+
break
|
|
228
|
+
}
|
|
229
|
+
return name;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Resolver } from './core-utils-resolver';
|
|
2
|
+
export class SimpleRegistry<T> implements Resolver<T>{
|
|
3
|
+
private readonly records : Map<string | undefined, Record<string, T>> = new Map()
|
|
4
|
+
resolve(namespace: string | undefined, prefix: string | undefined, variableName: string): T | undefined {
|
|
5
|
+
if (prefix !== undefined){
|
|
6
|
+
throw new Error(`Not support namespace prefix for name resolution : '${prefix}'`)
|
|
7
|
+
} else {
|
|
8
|
+
const record = this.records.get(namespace)
|
|
9
|
+
return record?.[variableName]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
register(namespace: string | undefined, variableName: string, t : T) {
|
|
14
|
+
let namespaceRecord = this.records.get(namespace)
|
|
15
|
+
if(namespaceRecord === undefined){
|
|
16
|
+
namespaceRecord = {}
|
|
17
|
+
this.records.set(namespace, namespaceRecord)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
namespaceRecord[variableName] = t
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface Resolver<T> {
|
|
2
|
+
resolve(namespace: string | undefined, prefix: string | undefined, variableName: string): T | undefined
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class RecordResolver<T> implements Resolver<T> {
|
|
6
|
+
constructor(readonly variables: Record<string, T>, readonly parent?: Resolver<T>) {
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
resolve(namespace: string | undefined, prefix: string | undefined, variableName: string): T | undefined {
|
|
10
|
+
return this.variables[variableName] ?? this.parent?.resolve(undefined, undefined, variableName)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class QualifiedNameResolverImpl<T> implements Resolver<T> {
|
|
15
|
+
readonly entries = new Map<string| undefined, Map<string, T>>()
|
|
16
|
+
constructor(readonly ignoreNs = false) {
|
|
17
|
+
}
|
|
18
|
+
resolve(namespace: string | undefined, prefix: string | undefined, name: string): T | undefined {
|
|
19
|
+
if(!this.ignoreNs && namespace !== undefined && namespace !== null){
|
|
20
|
+
throw new Error(`Not support namespace URI but passed as parameter: ${namespace}`)
|
|
21
|
+
}
|
|
22
|
+
return this.entries.get(namespace)?.get(name)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setEntries(prefix: string | undefined, entries : Record<string, T>){
|
|
26
|
+
const prefixEntry = this.getOrCreatePrefixEntry(prefix)
|
|
27
|
+
Object.entries(entries).forEach(([k, v]) => prefixEntry.set(k, v))
|
|
28
|
+
}
|
|
29
|
+
setEntry(prefix: string | undefined, name : string, entry : T){
|
|
30
|
+
const prefixEntry = this.getOrCreatePrefixEntry(prefix)
|
|
31
|
+
prefixEntry.set(name, entry)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private getOrCreatePrefixEntry(prefix : string | undefined) : Map<string, T> {
|
|
35
|
+
let prefixEntry = this.entries.get(prefix)
|
|
36
|
+
if(prefixEntry === undefined){
|
|
37
|
+
prefixEntry = new Map<string, T>()
|
|
38
|
+
this.entries.set(prefix, prefixEntry)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return prefixEntry
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ResolverPredicate = (namespace: string | undefined, prefix: string | undefined, name: string) => boolean
|
|
47
|
+
|
|
48
|
+
export class SimpleResolver<T> implements Resolver<T> {
|
|
49
|
+
constructor(readonly parent: Resolver<T> | undefined, readonly namespace: string | undefined, readonly variableName: string | undefined, readonly variableValue: T) {
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
resolve(namespace: string | undefined, prefix: string | undefined, variableName: string): T | undefined {
|
|
53
|
+
if (this.namespace === namespace && this.variableName === variableName) {
|
|
54
|
+
return this.variableValue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this.parent?.resolve(namespace, prefix, variableName)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class ChainedResolver<T> implements Resolver<T> {
|
|
62
|
+
constructor(readonly resolvers: Resolver<T>[]) {
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resolve(namespace: string | undefined, prefix: string | undefined, variableName: string): T | undefined {
|
|
66
|
+
for (const resolver of this.resolvers) {
|
|
67
|
+
const s = resolver.resolve(namespace, prefix, variableName)
|
|
68
|
+
if (s !== undefined) {
|
|
69
|
+
return s
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return undefined
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import stack from "./core-utils-stack";
|
|
2
|
+
|
|
3
|
+
describe("Core util stack", () => {
|
|
4
|
+
it("fromArray should work", () => {
|
|
5
|
+
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
6
|
+
|
|
7
|
+
expect(itinerary?.value).toBe(4)
|
|
8
|
+
expect(itinerary?.parent?.parent?.parent?.value).toBe(1)
|
|
9
|
+
|
|
10
|
+
expect(stack.fromArray([])).toBe(undefined)
|
|
11
|
+
expect(stack.fromArray(undefined)).toBe(undefined)
|
|
12
|
+
})
|
|
13
|
+
it("reduce should work", () => {
|
|
14
|
+
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
15
|
+
|
|
16
|
+
const result = stack.reduce(itinerary, (acc, t) => acc + t, 0);
|
|
17
|
+
expect(result).toBe(10)
|
|
18
|
+
})
|
|
19
|
+
it("reduce order should work", () => {
|
|
20
|
+
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
21
|
+
|
|
22
|
+
const result = stack.reduce(itinerary, (acc, t) => [...acc, t], [] as number[]);
|
|
23
|
+
expect(result).toStrictEqual([1, 2, 3, 4])
|
|
24
|
+
})
|
|
25
|
+
it("equal should work", () => {
|
|
26
|
+
expect(stack.equal(undefined, undefined)).toBe(false)
|
|
27
|
+
expect(stack.equal(null, null)).toBe(false)
|
|
28
|
+
expect(stack.equal(undefined, null)).toBe(false)
|
|
29
|
+
expect(stack.equal(null, undefined)).toBe(false)
|
|
30
|
+
|
|
31
|
+
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), undefined)).toBe(false)
|
|
32
|
+
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), null)).toBe(false)
|
|
33
|
+
|
|
34
|
+
expect(stack.equal(undefined, stack.fromArray([1, 2, 3, 4]))).toBe(false)
|
|
35
|
+
expect(stack.equal(null, stack.fromArray([1, 2, 3, 4]))).toBe(false)
|
|
36
|
+
|
|
37
|
+
expect(stack.equal(stack.fromArray([1]), stack.fromArray([2]))).toBe(false)
|
|
38
|
+
expect(stack.equal(stack.fromArray([1]), stack.fromArray([1, 2]))).toBe(false)
|
|
39
|
+
|
|
40
|
+
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), stack.fromArray([1, 2, 3, 4]))).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
})
|