@player-ui/player 0.3.0-next.2 → 0.3.0-next.4
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/index.cjs.js +4128 -891
- package/dist/index.d.ts +1227 -50
- package/dist/index.esm.js +4065 -836
- package/package.json +9 -15
- package/src/binding/binding.ts +108 -0
- package/src/binding/index.ts +188 -0
- package/src/binding/resolver.ts +157 -0
- package/src/binding/utils.ts +51 -0
- package/src/binding-grammar/ast.ts +113 -0
- package/src/binding-grammar/custom/index.ts +304 -0
- package/src/binding-grammar/ebnf/binding.ebnf +22 -0
- package/src/binding-grammar/ebnf/index.ts +186 -0
- package/src/binding-grammar/ebnf/types.ts +104 -0
- package/src/binding-grammar/index.ts +4 -0
- package/src/binding-grammar/parsimmon/index.ts +78 -0
- package/src/controllers/constants/index.ts +85 -0
- package/src/controllers/constants/utils.ts +37 -0
- package/src/{data.ts → controllers/data.ts} +6 -6
- package/src/controllers/flow/controller.ts +95 -0
- package/src/controllers/flow/flow.ts +205 -0
- package/src/controllers/flow/index.ts +2 -0
- package/src/controllers/index.ts +5 -0
- package/src/{validation → controllers/validation}/binding-tracker.ts +5 -5
- package/src/{validation → controllers/validation}/controller.ts +15 -14
- package/src/{validation → controllers/validation}/index.ts +0 -0
- package/src/{view → controllers/view}/asset-transform.ts +2 -3
- package/src/{view → controllers/view}/controller.ts +9 -8
- package/src/controllers/view/index.ts +4 -0
- package/src/{view → controllers/view}/store.ts +0 -0
- package/src/{view → controllers/view}/types.ts +2 -1
- package/src/data/dependency-tracker.ts +187 -0
- package/src/data/index.ts +4 -0
- package/src/data/local-model.ts +41 -0
- package/src/data/model.ts +216 -0
- package/src/data/noop-model.ts +18 -0
- package/src/expressions/evaluator-functions.ts +29 -0
- package/src/expressions/evaluator.ts +405 -0
- package/src/expressions/index.ts +3 -0
- package/src/expressions/parser.ts +889 -0
- package/src/expressions/types.ts +200 -0
- package/src/expressions/utils.ts +8 -0
- package/src/index.ts +9 -12
- package/src/logger/consoleLogger.ts +49 -0
- package/src/logger/index.ts +5 -0
- package/src/logger/noopLogger.ts +13 -0
- package/src/logger/proxyLogger.ts +25 -0
- package/src/logger/tapableLogger.ts +38 -0
- package/src/logger/types.ts +6 -0
- package/src/player.ts +21 -18
- package/src/plugins/flow-exp-plugin.ts +2 -3
- package/src/schema/index.ts +2 -0
- package/src/schema/schema.ts +220 -0
- package/src/schema/types.ts +60 -0
- package/src/string-resolver/index.ts +188 -0
- package/src/types.ts +11 -13
- package/src/utils/index.ts +1 -0
- package/src/utils/replaceParams.ts +17 -0
- package/src/validator/index.ts +3 -0
- package/src/validator/registry.ts +20 -0
- package/src/validator/types.ts +75 -0
- package/src/validator/validation-middleware.ts +114 -0
- package/src/view/builder/index.ts +81 -0
- package/src/view/index.ts +5 -4
- package/src/view/parser/index.ts +318 -0
- package/src/view/parser/types.ts +141 -0
- package/src/view/plugins/applicability.ts +78 -0
- package/src/view/plugins/index.ts +5 -0
- package/src/view/plugins/options.ts +4 -0
- package/src/view/plugins/plugin.ts +21 -0
- package/src/view/plugins/string-resolver.ts +149 -0
- package/src/view/plugins/switch.ts +120 -0
- package/src/view/plugins/template-plugin.ts +172 -0
- package/src/view/resolver/index.ts +397 -0
- package/src/view/resolver/types.ts +161 -0
- package/src/view/resolver/utils.ts +57 -0
- package/src/view/view.ts +149 -0
- package/src/utils/desc.d.ts +0 -2
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { setIn } from 'timm';
|
|
2
|
+
import { SyncBailHook, SyncWaterfallHook } from 'tapable-ts';
|
|
3
|
+
import type { Template } from '@player-ui/types';
|
|
4
|
+
import type { Node, AnyAssetType } from './types';
|
|
5
|
+
import { NodeType } from './types';
|
|
6
|
+
|
|
7
|
+
export * from './types';
|
|
8
|
+
|
|
9
|
+
export const EMPTY_NODE: Node.Empty = {
|
|
10
|
+
type: NodeType.Empty,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface ParseObjectOptions {
|
|
14
|
+
/** how nested the templated is */
|
|
15
|
+
templateDepth?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface NestedObj {
|
|
19
|
+
/** The values of a nested local object */
|
|
20
|
+
children: Node.Child[];
|
|
21
|
+
|
|
22
|
+
value: any;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The Parser is the way to take an incoming view from the user and parse it into an AST.
|
|
26
|
+
* It provides a few ways to interact with the parsing, including mutating an object before and after creation of an AST node
|
|
27
|
+
*/
|
|
28
|
+
export class Parser {
|
|
29
|
+
public readonly hooks = {
|
|
30
|
+
/**
|
|
31
|
+
* A hook to interact with an object _before_ parsing it into an AST
|
|
32
|
+
*
|
|
33
|
+
* @param value - The object we're are about to parse
|
|
34
|
+
* @returns - A new value to parse.
|
|
35
|
+
* If undefined, the original value is used.
|
|
36
|
+
* If null, we stop parsing this node.
|
|
37
|
+
*/
|
|
38
|
+
onParseObject: new SyncWaterfallHook<[object, NodeType]>(),
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A callback to interact with an AST _after_ we parse it into the AST
|
|
42
|
+
*
|
|
43
|
+
* @param value - The object we parsed
|
|
44
|
+
* @param node - The AST node we generated
|
|
45
|
+
* @returns - A new AST node to use
|
|
46
|
+
* If undefined, the original value is used.
|
|
47
|
+
* If null, we ignore this node all together
|
|
48
|
+
*/
|
|
49
|
+
onCreateASTNode: new SyncWaterfallHook<
|
|
50
|
+
[Node.Node | undefined | null, object]
|
|
51
|
+
>(),
|
|
52
|
+
|
|
53
|
+
determineNodeType: new SyncBailHook<[object | string], NodeType>(),
|
|
54
|
+
|
|
55
|
+
parseNode: new SyncBailHook<
|
|
56
|
+
[
|
|
57
|
+
obj: object,
|
|
58
|
+
nodeType: Node.ChildrenTypes,
|
|
59
|
+
parseOptions: ParseObjectOptions,
|
|
60
|
+
determinedNodeType: NodeType | null
|
|
61
|
+
],
|
|
62
|
+
Node.Node
|
|
63
|
+
>(),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
public parseView(value: AnyAssetType): Node.View {
|
|
67
|
+
const viewNode = this.parseObject(value, NodeType.View);
|
|
68
|
+
|
|
69
|
+
if (!viewNode) {
|
|
70
|
+
throw new Error('Unable to parse object into a view');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return viewNode as Node.View;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public createASTNode(node: Node.Node | null, value: any): Node.Node | null {
|
|
77
|
+
const tapped = this.hooks.onCreateASTNode.call(node, value);
|
|
78
|
+
|
|
79
|
+
if (tapped === undefined) {
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return tapped;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public parseObject(
|
|
87
|
+
obj: object,
|
|
88
|
+
type: Node.ChildrenTypes = NodeType.Value,
|
|
89
|
+
options: ParseObjectOptions = { templateDepth: 0 }
|
|
90
|
+
): Node.Node | null {
|
|
91
|
+
const nodeType = this.hooks.determineNodeType.call(obj);
|
|
92
|
+
|
|
93
|
+
if (nodeType !== undefined) {
|
|
94
|
+
const parsedNode = this.hooks.parseNode.call(
|
|
95
|
+
obj,
|
|
96
|
+
type,
|
|
97
|
+
options,
|
|
98
|
+
nodeType
|
|
99
|
+
);
|
|
100
|
+
if (parsedNode) {
|
|
101
|
+
return parsedNode;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const parseLocalObject = (
|
|
106
|
+
currentValue: any,
|
|
107
|
+
objToParse: unknown,
|
|
108
|
+
path: string[] = []
|
|
109
|
+
): NestedObj => {
|
|
110
|
+
if (typeof objToParse !== 'object' || objToParse === null) {
|
|
111
|
+
// value = objToParse;
|
|
112
|
+
return { value: objToParse, children: [] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const localObj = this.hooks.onParseObject.call(objToParse, type);
|
|
116
|
+
|
|
117
|
+
if (!localObj) {
|
|
118
|
+
return currentValue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const objEntries = Array.isArray(localObj)
|
|
122
|
+
? localObj.map((v, i) => [i, v])
|
|
123
|
+
: Object.entries(localObj);
|
|
124
|
+
|
|
125
|
+
const defaultValue: NestedObj = {
|
|
126
|
+
children: [],
|
|
127
|
+
value: currentValue,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const newValue = objEntries.reduce((accumulation, current): NestedObj => {
|
|
131
|
+
const { children, ...rest } = accumulation;
|
|
132
|
+
const [localKey, localValue] = current;
|
|
133
|
+
if (localKey === 'asset' && typeof localValue === 'object') {
|
|
134
|
+
const assetAST = this.parseObject(
|
|
135
|
+
localValue,
|
|
136
|
+
NodeType.Asset,
|
|
137
|
+
options
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (assetAST) {
|
|
141
|
+
return {
|
|
142
|
+
...rest,
|
|
143
|
+
children: [
|
|
144
|
+
...children,
|
|
145
|
+
{
|
|
146
|
+
path: [...path, 'asset'],
|
|
147
|
+
value: assetAST,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
} else if (
|
|
153
|
+
this.hooks.determineNodeType.call(localKey) === NodeType.Template &&
|
|
154
|
+
Array.isArray(localValue)
|
|
155
|
+
) {
|
|
156
|
+
const templateChildren = localValue
|
|
157
|
+
.map((template: Template) => {
|
|
158
|
+
const templateAST = this.hooks.onCreateASTNode.call(
|
|
159
|
+
{
|
|
160
|
+
type: NodeType.Template,
|
|
161
|
+
depth: options.templateDepth ?? 0,
|
|
162
|
+
data: template.data,
|
|
163
|
+
template: template.value,
|
|
164
|
+
dynamic: template.dynamic ?? false,
|
|
165
|
+
},
|
|
166
|
+
template
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (templateAST) {
|
|
170
|
+
return {
|
|
171
|
+
path: [...path, template.output],
|
|
172
|
+
value: templateAST,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// eslint-disable-next-line no-useless-return
|
|
177
|
+
return;
|
|
178
|
+
})
|
|
179
|
+
.filter((element) => !!element);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
...rest,
|
|
183
|
+
children: [...children, ...templateChildren],
|
|
184
|
+
} as NestedObj;
|
|
185
|
+
} else if (
|
|
186
|
+
localValue &&
|
|
187
|
+
this.hooks.determineNodeType.call(localValue) === NodeType.Switch
|
|
188
|
+
) {
|
|
189
|
+
const localSwitch = this.hooks.parseNode.call(
|
|
190
|
+
localValue,
|
|
191
|
+
NodeType.Value,
|
|
192
|
+
options,
|
|
193
|
+
NodeType.Switch
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (localSwitch) {
|
|
197
|
+
return {
|
|
198
|
+
...rest,
|
|
199
|
+
children: [
|
|
200
|
+
...children,
|
|
201
|
+
{
|
|
202
|
+
path: [...path, localKey],
|
|
203
|
+
value: localSwitch,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
} else if (localValue && Array.isArray(localValue)) {
|
|
209
|
+
const childValues = localValue
|
|
210
|
+
.map((childVal) =>
|
|
211
|
+
this.parseObject(childVal, NodeType.Value, options)
|
|
212
|
+
)
|
|
213
|
+
.filter((child): child is Node.Node => !!child);
|
|
214
|
+
|
|
215
|
+
if (childValues.length > 0) {
|
|
216
|
+
const multiNode = this.hooks.onCreateASTNode.call(
|
|
217
|
+
{
|
|
218
|
+
type: NodeType.MultiNode,
|
|
219
|
+
override: true,
|
|
220
|
+
values: childValues,
|
|
221
|
+
},
|
|
222
|
+
localValue
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (multiNode?.type === NodeType.MultiNode) {
|
|
226
|
+
multiNode.values.forEach((v) => {
|
|
227
|
+
// eslint-disable-next-line no-param-reassign
|
|
228
|
+
v.parent = multiNode;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (multiNode) {
|
|
233
|
+
return {
|
|
234
|
+
...rest,
|
|
235
|
+
children: [
|
|
236
|
+
...children,
|
|
237
|
+
{
|
|
238
|
+
path: [...path, localKey],
|
|
239
|
+
value: multiNode,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else if (localValue && typeof localValue === 'object') {
|
|
246
|
+
const determineNodeType =
|
|
247
|
+
this.hooks.determineNodeType.call(localValue);
|
|
248
|
+
|
|
249
|
+
if (determineNodeType === NodeType.Applicability) {
|
|
250
|
+
const parsedNode = this.hooks.parseNode.call(
|
|
251
|
+
localValue,
|
|
252
|
+
type,
|
|
253
|
+
options,
|
|
254
|
+
determineNodeType
|
|
255
|
+
);
|
|
256
|
+
if (parsedNode) {
|
|
257
|
+
return {
|
|
258
|
+
...rest,
|
|
259
|
+
children: [
|
|
260
|
+
...children,
|
|
261
|
+
{
|
|
262
|
+
path: [...path, localKey],
|
|
263
|
+
value: parsedNode,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const result = parseLocalObject(accumulation.value, localValue, [
|
|
270
|
+
...path,
|
|
271
|
+
localKey,
|
|
272
|
+
]);
|
|
273
|
+
return {
|
|
274
|
+
value: result.value,
|
|
275
|
+
children: [...children, ...result.children],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
const value = setIn(
|
|
280
|
+
accumulation.value,
|
|
281
|
+
[...path, localKey],
|
|
282
|
+
localValue
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
children,
|
|
287
|
+
value,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return accumulation;
|
|
292
|
+
}, defaultValue);
|
|
293
|
+
|
|
294
|
+
return newValue;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const { value, children } = parseLocalObject(undefined, obj);
|
|
298
|
+
|
|
299
|
+
const baseAst =
|
|
300
|
+
value === undefined && children.length === 0
|
|
301
|
+
? undefined
|
|
302
|
+
: {
|
|
303
|
+
type,
|
|
304
|
+
value,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
if (baseAst !== undefined && children.length > 0) {
|
|
308
|
+
const parent = baseAst as Node.BaseWithChildren<any>;
|
|
309
|
+
parent.children = children;
|
|
310
|
+
children.forEach((child) => {
|
|
311
|
+
// eslint-disable-next-line no-param-reassign
|
|
312
|
+
child.value.parent = parent;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return this.hooks.onCreateASTNode.call(baseAst, obj) ?? null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Asset as AssetType, Expression, Binding } from '@player-ui/types';
|
|
2
|
+
|
|
3
|
+
export type AnyAssetType = AssetType<string>;
|
|
4
|
+
export enum NodeType {
|
|
5
|
+
Asset = 'asset',
|
|
6
|
+
View = 'view',
|
|
7
|
+
Applicability = 'applicability',
|
|
8
|
+
Template = 'template',
|
|
9
|
+
Value = 'value',
|
|
10
|
+
MultiNode = 'multi-node',
|
|
11
|
+
Switch = 'switch',
|
|
12
|
+
Unknown = 'unknown',
|
|
13
|
+
Empty = 'empty',
|
|
14
|
+
}
|
|
15
|
+
export declare namespace Node {
|
|
16
|
+
export type ChildrenTypes = NodeType.Asset | NodeType.Value | NodeType.View;
|
|
17
|
+
|
|
18
|
+
export interface Base<T extends NodeType> {
|
|
19
|
+
/** Every node contains a type to distinguish it from other nodes */
|
|
20
|
+
type: T;
|
|
21
|
+
|
|
22
|
+
/** Every node (outside of the root) contains a reference to it's parent */
|
|
23
|
+
parent?: Node;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type PathSegment = string | number;
|
|
27
|
+
|
|
28
|
+
export interface Child {
|
|
29
|
+
/** The path of the child relative to the parent */
|
|
30
|
+
path: PathSegment[];
|
|
31
|
+
|
|
32
|
+
/** If true, the path points to an array, and the value will be appended to it result */
|
|
33
|
+
array?: boolean;
|
|
34
|
+
|
|
35
|
+
/** The child node */
|
|
36
|
+
value: Node;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BaseWithChildren<T extends NodeType> extends Base<T> {
|
|
40
|
+
/** Any node that contains a list of children underneath it */
|
|
41
|
+
children?: Child[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Asset<T extends AnyAssetType = AnyAssetType>
|
|
45
|
+
extends BaseWithChildren<NodeType.Asset>,
|
|
46
|
+
PluginOptions {
|
|
47
|
+
/** Any asset nested within a view */
|
|
48
|
+
value: T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface View<T extends AnyAssetType = AnyAssetType>
|
|
52
|
+
extends BaseWithChildren<NodeType.View>,
|
|
53
|
+
PluginOptions {
|
|
54
|
+
/** The root of the parsed view */
|
|
55
|
+
value: T;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface Applicability extends Base<NodeType.Applicability> {
|
|
59
|
+
/** The expression to execute that determines applicability of the target node */
|
|
60
|
+
expression: Expression;
|
|
61
|
+
|
|
62
|
+
/** The node to use if the expression is truthy */
|
|
63
|
+
value: Node;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface Template extends Base<NodeType.Template> {
|
|
67
|
+
/** The location of an array in the model */
|
|
68
|
+
data: Binding;
|
|
69
|
+
|
|
70
|
+
/** The template to use when mapping over the data */
|
|
71
|
+
template: unknown;
|
|
72
|
+
|
|
73
|
+
/** The number of nested templates so far */
|
|
74
|
+
depth: number;
|
|
75
|
+
|
|
76
|
+
/** should the template recomputed when data changes */
|
|
77
|
+
dynamic?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface Value
|
|
81
|
+
extends BaseWithChildren<NodeType.Value>,
|
|
82
|
+
PluginOptions {
|
|
83
|
+
/** A simple node representing a value */
|
|
84
|
+
value: any;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MultiNode extends Base<NodeType.MultiNode> {
|
|
88
|
+
/**
|
|
89
|
+
* Should this list override the target node if they overlap?
|
|
90
|
+
* If not amend the existing list
|
|
91
|
+
*/
|
|
92
|
+
override?: boolean;
|
|
93
|
+
|
|
94
|
+
/** A list of values that comprise this node */
|
|
95
|
+
values: Array<Node>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface Switch extends Base<NodeType.Switch> {
|
|
99
|
+
/** Should this list be re-computed when data changes */
|
|
100
|
+
dynamic?: boolean;
|
|
101
|
+
|
|
102
|
+
/** A list of cases to evaluate in order */
|
|
103
|
+
cases: SwitchCase[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SwitchCase {
|
|
107
|
+
/** The expression to evaluate for a single case statement */
|
|
108
|
+
case: Expression | true;
|
|
109
|
+
/** The value to use if this case is true */
|
|
110
|
+
value: Value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface PluginOptions {
|
|
114
|
+
/** A list of plugins */
|
|
115
|
+
plugins?: {
|
|
116
|
+
/** StringResolverPlugin options */
|
|
117
|
+
stringResolver?: {
|
|
118
|
+
/**
|
|
119
|
+
* An optional array of node properties to skip during string resolution
|
|
120
|
+
* Specified in the AssetTransformPlugin
|
|
121
|
+
*/
|
|
122
|
+
propertiesToSkip?: string[];
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type Unknown = Base<NodeType.Unknown>;
|
|
128
|
+
export type Empty = Base<NodeType.Empty>;
|
|
129
|
+
export type ViewOrAsset = View | Asset;
|
|
130
|
+
|
|
131
|
+
export type Node =
|
|
132
|
+
| Asset
|
|
133
|
+
| Applicability
|
|
134
|
+
| Template
|
|
135
|
+
| Value
|
|
136
|
+
| View
|
|
137
|
+
| MultiNode
|
|
138
|
+
| Switch
|
|
139
|
+
| Unknown
|
|
140
|
+
| Empty;
|
|
141
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { omit } from 'timm';
|
|
2
|
+
import type { ViewPlugin, View } from './plugin';
|
|
3
|
+
import type { Options } from './options';
|
|
4
|
+
import type { Resolver } from '../resolver';
|
|
5
|
+
import type { Node, ParseObjectOptions, Parser } from '../parser';
|
|
6
|
+
import { NodeType } from '../parser';
|
|
7
|
+
|
|
8
|
+
/** A view plugin to remove inapplicable assets from the tree */
|
|
9
|
+
export default class ApplicabilityPlugin implements ViewPlugin {
|
|
10
|
+
applyResolver(resolver: Resolver) {
|
|
11
|
+
resolver.hooks.beforeResolve.tap(
|
|
12
|
+
'applicability',
|
|
13
|
+
(node: Node.Node | null, options: Options) => {
|
|
14
|
+
let newNode = node;
|
|
15
|
+
|
|
16
|
+
if (node?.type === NodeType.Applicability) {
|
|
17
|
+
const isApplicable = options.evaluate(node.expression);
|
|
18
|
+
|
|
19
|
+
if (!isApplicable) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
newNode = node.value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return newNode;
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
applyParser(parser: Parser) {
|
|
32
|
+
/** Switches resolved during the parsing phase are static */
|
|
33
|
+
parser.hooks.determineNodeType.tap('applicability', (obj: any) => {
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(obj, 'applicability')) {
|
|
35
|
+
return NodeType.Applicability;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
parser.hooks.parseNode.tap(
|
|
40
|
+
'applicability',
|
|
41
|
+
(
|
|
42
|
+
obj: any,
|
|
43
|
+
nodeType: Node.ChildrenTypes,
|
|
44
|
+
options: ParseObjectOptions,
|
|
45
|
+
determinedNodeType: null | NodeType
|
|
46
|
+
) => {
|
|
47
|
+
if (determinedNodeType === NodeType.Applicability) {
|
|
48
|
+
const parsedApplicability = parser.parseObject(
|
|
49
|
+
omit(obj, 'applicability'),
|
|
50
|
+
nodeType,
|
|
51
|
+
options
|
|
52
|
+
);
|
|
53
|
+
if (parsedApplicability !== null) {
|
|
54
|
+
const applicabilityNode = parser.createASTNode(
|
|
55
|
+
{
|
|
56
|
+
type: NodeType.Applicability,
|
|
57
|
+
expression: (obj as any).applicability,
|
|
58
|
+
value: parsedApplicability,
|
|
59
|
+
},
|
|
60
|
+
obj
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (applicabilityNode?.type === NodeType.Applicability) {
|
|
64
|
+
applicabilityNode.value.parent = applicabilityNode;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return applicabilityNode;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
apply(view: View) {
|
|
75
|
+
view.hooks.resolver.tap('applicability', this.applyResolver.bind(this));
|
|
76
|
+
view.hooks.parser.tap('applicability', this.applyParser.bind(this));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as TemplatePlugin } from './template-plugin';
|
|
2
|
+
export { default as StringResolverPlugin } from './string-resolver';
|
|
3
|
+
export { default as ApplicabilityPlugin } from './applicability';
|
|
4
|
+
export { default as SwitchPlugin } from './switch';
|
|
5
|
+
export type { ViewPlugin } from './plugin';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SyncHook } from 'tapable-ts';
|
|
2
|
+
import type { Resolver } from '../resolver';
|
|
3
|
+
import type { Parser } from '../parser';
|
|
4
|
+
|
|
5
|
+
/** The basic view api */
|
|
6
|
+
export interface View {
|
|
7
|
+
/** The hooks for a view */
|
|
8
|
+
hooks: {
|
|
9
|
+
/** A hook when a parser is created for this view */
|
|
10
|
+
parser: SyncHook<[Parser]>;
|
|
11
|
+
|
|
12
|
+
/** A hook when a resolver is created for this view */
|
|
13
|
+
resolver: SyncHook<[Resolver]>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A plugin for a view */
|
|
18
|
+
export interface ViewPlugin {
|
|
19
|
+
/** Called with a view instance */
|
|
20
|
+
apply(view: View): void;
|
|
21
|
+
}
|