Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/conat/core/patterns.ts
1453 views
1
import { isEqual } from "lodash";
2
import { getLogger } from "@cocalc/conat/client";
3
import { EventEmitter } from "events";
4
5
type Index = { [pattern: string]: Index | string };
6
7
const logger = getLogger("pattern");
8
9
export class Patterns<T> extends EventEmitter {
10
private patterns: { [pattern: string]: T } = {};
11
private index: Index = {};
12
13
constructor() {
14
super();
15
this.setMaxListeners(1000);
16
}
17
18
close = () => {
19
this.emit("closed");
20
this.patterns = {};
21
this.index = {};
22
};
23
24
serialize = (fromT?: (x: T) => any) => {
25
let patterns: { [pattern: string]: any };
26
if (fromT != null) {
27
patterns = {};
28
for (const pattern in this.patterns) {
29
patterns[pattern] = fromT(this.patterns[pattern]);
30
}
31
} else {
32
patterns = this.patterns;
33
}
34
35
return { patterns, index: this.index };
36
};
37
38
deserialize = (
39
{ patterns, index }: { patterns: { [pattern: string]: any }; index: Index },
40
toT?: (x: any) => T,
41
) => {
42
if (toT != null) {
43
for (const pattern in patterns) {
44
patterns[pattern] = toT(patterns[pattern]); // make it of type T
45
}
46
}
47
this.patterns = patterns;
48
this.index = index;
49
this.emit("change");
50
};
51
52
// mutate this by merging in data from p.
53
merge = (p: Patterns<T>) => {
54
for (const pattern in p.patterns) {
55
const t = p.patterns[pattern];
56
this.set(pattern, t);
57
}
58
this.emit("change");
59
};
60
61
matches = (subject: string): string[] => {
62
return matchUsingIndex(this.index, subject.split("."));
63
};
64
65
matchesTest = (subject: string): string[] => {
66
const a = this.matches(subject);
67
const b = this.matchNaive(subject);
68
a.sort();
69
b.sort();
70
if (!isEqual(a, b)) {
71
logger.debug("BUG in PATTERN MATCHING!!!", {
72
subject,
73
a,
74
b,
75
index: this.index,
76
patterns: Object.keys(this.patterns),
77
});
78
}
79
return b;
80
};
81
82
matchNaive = (subject: string): string[] => {
83
const v: string[] = [];
84
for (const pattern in this.patterns) {
85
if (matchesPattern(pattern, subject)) {
86
v.push(pattern);
87
}
88
}
89
return v;
90
};
91
92
get = (pattern: string): T | undefined => {
93
return this.patterns[pattern];
94
};
95
96
set = (pattern: string, t: T) => {
97
this.patterns[pattern] = t;
98
setIndex(this.index, pattern.split("."), pattern);
99
this.emit("change");
100
};
101
102
delete = (pattern: string) => {
103
delete this.patterns[pattern];
104
deleteIndex(this.index, pattern.split("."));
105
};
106
}
107
108
function setIndex(index: Index, segments: string[], pattern) {
109
if (segments.length == 0) {
110
index[""] = pattern;
111
return;
112
}
113
if (segments[0] == ">") {
114
// there can't be anything after it
115
index[">"] = pattern;
116
return;
117
}
118
const v = index[segments[0]];
119
if (v === undefined) {
120
const idx: Index = {};
121
setIndex(idx, segments.slice(1), pattern);
122
index[segments[0]] = idx;
123
return;
124
}
125
if (typeof v == "string") {
126
// already set
127
return;
128
}
129
setIndex(v, segments.slice(1), pattern);
130
}
131
132
function deleteIndex(index: Index, segments: string[]) {
133
const ind = index[segments[0]];
134
if (ind === undefined) {
135
return;
136
}
137
if (typeof ind != "string") {
138
deleteIndex(ind, segments.slice(1));
139
// if there is anything still stored in ind
140
// besides ind[''], we do NOT delete it.
141
for (const key in ind) {
142
if (key != "") {
143
return;
144
}
145
}
146
}
147
delete index[segments[0]];
148
}
149
150
// todo deal with >
151
function matchUsingIndex(index: Index, segments: string[]): string[] {
152
if (segments.length == 0) {
153
const p = index[""];
154
if (p === undefined) {
155
return [];
156
} else if (typeof p === "string") {
157
return [p];
158
} else {
159
throw Error("bug");
160
}
161
}
162
const matches: string[] = [];
163
const subject = segments[0];
164
for (const pattern of ["*", ">", subject]) {
165
if (index[pattern] !== undefined) {
166
const p = index[pattern];
167
if (typeof p == "string") {
168
// end of this pattern -- matches if segments also
169
// stops *or* this pattern is >
170
if (segments.length == 1) {
171
matches.push(p);
172
} else if (pattern == ">") {
173
matches.push(p);
174
}
175
} else {
176
for (const s of matchUsingIndex(p, segments.slice(1))) {
177
matches.push(s);
178
}
179
}
180
}
181
}
182
return matches;
183
}
184
185
export function matchesSegment(pattern, subject): boolean {
186
if (pattern == "*" || pattern == ">") {
187
return true;
188
}
189
return pattern == subject;
190
}
191
192
export function matchesPattern(pattern, subject): boolean {
193
const subParts = subject.split(".");
194
const patParts = pattern.split(".");
195
let i = 0,
196
j = 0;
197
while (i < subParts.length && j < patParts.length) {
198
if (patParts[j] === ">") return true;
199
if (patParts[j] !== "*" && patParts[j] !== subParts[i]) return false;
200
i++;
201
j++;
202
}
203
204
return i === subParts.length && j === patParts.length;
205
}
206
207