@valbuild/core 0.12.0 → 0.13.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.
Files changed (72) hide show
  1. package/README.md +25 -37
  2. package/jest.config.js +4 -0
  3. package/package.json +1 -1
  4. package/src/Json.ts +4 -0
  5. package/src/expr/README.md +193 -0
  6. package/src/expr/eval.test.ts +202 -0
  7. package/src/expr/eval.ts +248 -0
  8. package/src/expr/expr.ts +91 -0
  9. package/src/expr/index.ts +3 -0
  10. package/src/expr/parser.test.ts +158 -0
  11. package/src/expr/parser.ts +229 -0
  12. package/src/expr/repl.ts +93 -0
  13. package/src/expr/tokenizer.test.ts +539 -0
  14. package/src/expr/tokenizer.ts +117 -0
  15. package/src/fetchVal.test.ts +164 -0
  16. package/src/fetchVal.ts +211 -0
  17. package/src/fp/array.ts +30 -0
  18. package/src/fp/index.ts +3 -0
  19. package/src/fp/result.ts +214 -0
  20. package/src/fp/util.ts +52 -0
  21. package/src/index.ts +55 -0
  22. package/src/initSchema.ts +45 -0
  23. package/src/initVal.ts +96 -0
  24. package/src/module.test.ts +170 -0
  25. package/src/module.ts +333 -0
  26. package/src/patch/deref.test.ts +300 -0
  27. package/src/patch/deref.ts +128 -0
  28. package/src/patch/index.ts +11 -0
  29. package/src/patch/json.test.ts +583 -0
  30. package/src/patch/json.ts +304 -0
  31. package/src/patch/operation.ts +74 -0
  32. package/src/patch/ops.ts +83 -0
  33. package/src/patch/parse.test.ts +202 -0
  34. package/src/patch/parse.ts +187 -0
  35. package/src/patch/patch.ts +46 -0
  36. package/src/patch/util.ts +67 -0
  37. package/src/schema/array.ts +52 -0
  38. package/src/schema/boolean.ts +38 -0
  39. package/src/schema/i18n.ts +65 -0
  40. package/src/schema/image.ts +70 -0
  41. package/src/schema/index.ts +46 -0
  42. package/src/schema/literal.ts +42 -0
  43. package/src/schema/number.ts +45 -0
  44. package/src/schema/object.ts +67 -0
  45. package/src/schema/oneOf.ts +60 -0
  46. package/src/schema/richtext.ts +417 -0
  47. package/src/schema/string.ts +49 -0
  48. package/src/schema/union.ts +62 -0
  49. package/src/selector/ExprProxy.test.ts +203 -0
  50. package/src/selector/ExprProxy.ts +209 -0
  51. package/src/selector/SelectorProxy.test.ts +172 -0
  52. package/src/selector/SelectorProxy.ts +237 -0
  53. package/src/selector/array.ts +37 -0
  54. package/src/selector/boolean.ts +4 -0
  55. package/src/selector/file.ts +14 -0
  56. package/src/selector/i18n.ts +13 -0
  57. package/src/selector/index.ts +159 -0
  58. package/src/selector/number.ts +4 -0
  59. package/src/selector/object.ts +22 -0
  60. package/src/selector/primitive.ts +17 -0
  61. package/src/selector/remote.ts +9 -0
  62. package/src/selector/selector.test.ts +453 -0
  63. package/src/selector/selectorOf.ts +7 -0
  64. package/src/selector/string.ts +4 -0
  65. package/src/source/file.ts +45 -0
  66. package/src/source/i18n.ts +60 -0
  67. package/src/source/index.ts +50 -0
  68. package/src/source/remote.ts +54 -0
  69. package/src/val/array.ts +10 -0
  70. package/src/val/index.ts +90 -0
  71. package/src/val/object.ts +13 -0
  72. package/src/val/primitive.ts +8 -0
@@ -0,0 +1,229 @@
1
+ import { result } from "../fp";
2
+ import { Call, Expr, StringLiteral, StringTemplate, Sym } from "./expr";
3
+ import { Token, tokenize } from "./tokenizer";
4
+
5
+ export class ParserError {
6
+ constructor(
7
+ public readonly message: string,
8
+ public readonly span?: [number, number?]
9
+ ) {}
10
+ }
11
+
12
+ function parseTokens(inputTokens: Token[]): result.Result<Expr, ParserError> {
13
+ const tokens = inputTokens.slice();
14
+
15
+ function slurpCall(
16
+ first: Token,
17
+ isAnon: boolean
18
+ ): result.Result<Call | Sym, ParserError> {
19
+ // peek
20
+ if (
21
+ (tokens[0]?.type === "ws" && tokens[1]?.type === ")") ||
22
+ tokens[0]?.type === ")"
23
+ ) {
24
+ slurpWs();
25
+ tokens.shift();
26
+ return result.ok(new Sym("()", [first.span[0], first.span[1] + 1]));
27
+ }
28
+ const args: Expr[] = [];
29
+ let completed = false;
30
+ while (!completed) {
31
+ const res = slurp();
32
+ if (result.isOk(res)) {
33
+ args.push(res.value);
34
+ completed =
35
+ tokens[0]?.type !== "ws" ||
36
+ (tokens[0]?.type === "ws" && tokens[1]?.type === ")");
37
+ } else {
38
+ return res;
39
+ }
40
+ }
41
+ if (tokens[0]?.type === "ws" && tokens[1]?.type === ")") {
42
+ tokens.shift();
43
+ }
44
+ const last = tokens.shift();
45
+ if (last?.type !== ")") {
46
+ return result.err(
47
+ new ParserError("unbalanced parens: missing a ')'", [
48
+ first.span[0],
49
+ first.span[1] + 1,
50
+ ])
51
+ );
52
+ }
53
+ return result.ok(
54
+ new Call(args, isAnon, [first.span[0], args.slice(-1)[0].span?.[1] || -1])
55
+ );
56
+ }
57
+
58
+ function slurpWs() {
59
+ while (tokens[0]?.type === "ws") {
60
+ tokens.shift();
61
+ }
62
+ }
63
+
64
+ function slurpTemplate(
65
+ first: Token
66
+ ): result.Result<StringLiteral | StringTemplate, ParserError> {
67
+ const children: Expr[] = [];
68
+ while (tokens.length > 0) {
69
+ if ((tokens[0]?.type as string) === "'") {
70
+ break;
71
+ }
72
+ const nextToken = tokens.shift();
73
+ if (nextToken?.type === "${") {
74
+ const res = slurp();
75
+ if (result.isOk(res)) {
76
+ children.push(res.value);
77
+ const last = tokens.shift();
78
+ if (!last) {
79
+ return result.err(
80
+ new ParserError("unbalanced string template: missing a '}'", [
81
+ first.span[0],
82
+ children.slice(-1)[0].span?.[1],
83
+ ])
84
+ );
85
+ } else if (last.type !== "}") {
86
+ return result.err(
87
+ new ParserError(
88
+ "unbalanced string template: expected '}'",
89
+ last.span
90
+ )
91
+ );
92
+ }
93
+ } else {
94
+ return res;
95
+ }
96
+ } else if (nextToken?.type === "string") {
97
+ children.push(
98
+ new StringLiteral(
99
+ nextToken.unescapedValue || nextToken.value || "",
100
+ nextToken.span
101
+ )
102
+ );
103
+ }
104
+ }
105
+
106
+ const last = tokens.shift();
107
+ if (!last) {
108
+ return result.err(
109
+ new ParserError("unbalanced string template: missing a '''", [
110
+ first.span[0],
111
+ children.slice(-1)[0].span?.[1],
112
+ ])
113
+ );
114
+ } else if (last.type !== "'") {
115
+ return result.err(
116
+ new ParserError("unbalanced string template: expected '''", last.span)
117
+ );
118
+ }
119
+
120
+ return result.ok(
121
+ new StringTemplate(children, [first.span[0], last.span[1]])
122
+ );
123
+ }
124
+
125
+ function slurpString(
126
+ first: Token
127
+ ): result.Result<StringLiteral | StringTemplate, ParserError> {
128
+ if (tokens[0]?.type === "string" && tokens[1]?.type === "'") {
129
+ const stringToken = tokens.shift();
130
+ const last = tokens.shift();
131
+ if (!last || !stringToken) {
132
+ throw Error("Unexpected error: stringToken or last is undefined");
133
+ }
134
+ return result.ok(
135
+ new StringLiteral(
136
+ stringToken.unescapedValue || stringToken.value || "",
137
+ [first.span[0], last.span[1]]
138
+ )
139
+ );
140
+ } else if (tokens[0]?.type === "'") {
141
+ const last = tokens.shift();
142
+ if (!last) {
143
+ throw Error("Unexpected error: last is undefined");
144
+ }
145
+ return result.ok(new StringLiteral("", [first.span[0], last.span[1]]));
146
+ } else {
147
+ return slurpTemplate(first);
148
+ }
149
+ }
150
+
151
+ function slurp(): result.Result<Expr, ParserError> {
152
+ slurpWs();
153
+ const first = tokens.shift();
154
+ if (!first) {
155
+ return result.err(
156
+ new ParserError("expected '(', '!(', string or literal", [0, 0])
157
+ );
158
+ }
159
+ if (first.type === "(" || first.type === "!(") {
160
+ return slurpCall(first, first.type === "!(");
161
+ } else if (first.type === "'") {
162
+ return slurpString(first);
163
+ } else if (first.type === "token") {
164
+ if (first.value?.includes("(") || first.value?.includes(")")) {
165
+ return result.err(
166
+ new ParserError(
167
+ "unexpected token: '(' and ')' are not allowed in tokens",
168
+ first.span
169
+ )
170
+ );
171
+ }
172
+ if (first.value?.includes("'")) {
173
+ return result.err(
174
+ new ParserError(
175
+ 'unexpected token: "\'" is not allowed in tokens',
176
+ first.span
177
+ )
178
+ );
179
+ }
180
+ if (first.value?.includes(".")) {
181
+ return result.err(
182
+ new ParserError(
183
+ 'unexpected token: "." is not allowed in tokens',
184
+ first.span
185
+ )
186
+ );
187
+ }
188
+ if (first.value?.includes("{") || first.value?.includes("}")) {
189
+ return result.err(
190
+ new ParserError(
191
+ "unexpected token: '{' and '}' are not allowed in tokens",
192
+ first.span
193
+ )
194
+ );
195
+ }
196
+ return result.ok(new Sym(first.value || "", first.span));
197
+ } else {
198
+ return result.err(
199
+ new ParserError(
200
+ `expected '(', '!(' or literal or token${
201
+ first.value || first.type
202
+ ? `, got: '${first.value || first.type}'`
203
+ : ""
204
+ }`,
205
+ first.span
206
+ )
207
+ );
208
+ }
209
+ }
210
+ const res = slurp();
211
+ slurpWs();
212
+ if (result.isErr(res)) {
213
+ return res;
214
+ }
215
+ if (tokens.length > 0) {
216
+ return result.err(
217
+ new ParserError("expected end of input, superfluous tokens", [
218
+ tokens[0].span[0],
219
+ tokens.slice(-1)[0].span[1],
220
+ ])
221
+ );
222
+ }
223
+ return res;
224
+ }
225
+ export function parse(input: string): result.Result<Expr, ParserError> {
226
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
227
+ const [tokens, cursor] = tokenize(input); // TODO: we can use cursor to improve error messages / spans
228
+ return parseTokens(tokens);
229
+ }
@@ -0,0 +1,93 @@
1
+ import * as repl from "repl";
2
+ import { result, pipe } from "../../fp";
3
+ import { newSelectorProxy, selectorToVal } from "../selector/SelectorProxy";
4
+ import { SourcePath } from "../val";
5
+ import { evaluate } from "./eval";
6
+ import { parse } from "./parser";
7
+
8
+ const sources = {
9
+ "/app/text": "text1",
10
+ "/numbers": [1, 2, 3],
11
+ "/blogs": [
12
+ { title: "title1", text: "text1" },
13
+ { title: "title2", text: "text2" },
14
+ ],
15
+ };
16
+
17
+ repl
18
+ .start({
19
+ prompt: "β > ",
20
+ eval: (
21
+ cmd,
22
+ context,
23
+ filename,
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ callback: (arg0: any, arg1: any) => void
26
+ ) => {
27
+ const res = parse(cmd);
28
+ if (result.isErr(res)) {
29
+ let lines = "\n\x1b[31m";
30
+ lines +=
31
+ res.error.message[0]?.toUpperCase() +
32
+ res.error.message.slice(1) +
33
+ ` (${res.error.span?.[0]}:${res.error.span?.[1]})` +
34
+ ":\x1b[0m\n\n";
35
+ lines += cmd + "\n";
36
+ let underline = "\x1b[31m";
37
+ for (let i = 0; i < cmd.length; i++) {
38
+ if (
39
+ res.error.span &&
40
+ i >= res.error.span[0] &&
41
+ i <= (res.error.span?.[1] === undefined ? -1 : res.error.span?.[1])
42
+ ) {
43
+ underline += "^";
44
+ } else {
45
+ if (cmd[i] === "\n") {
46
+ if (!underline.includes("^")) {
47
+ underline = "";
48
+ }
49
+ } else {
50
+ underline += " ";
51
+ }
52
+ }
53
+ }
54
+ lines += underline + "\x1b[0m\n";
55
+ callback(null, lines);
56
+ } else {
57
+ pipe(
58
+ evaluate(
59
+ res.value,
60
+ (ref) =>
61
+ newSelectorProxy(
62
+ sources[ref as keyof typeof sources],
63
+ ref as SourcePath
64
+ ),
65
+
66
+ []
67
+ ),
68
+ result.map((v) => {
69
+ try {
70
+ console.log(selectorToVal(v).val);
71
+ callback(null, undefined);
72
+ } catch (e) {
73
+ callback(
74
+ null,
75
+ `\x1b[31mInvalid function! Expected selector, but got:\x1b[0m:\n${JSON.stringify(
76
+ v
77
+ )}\n\nDetails: ${
78
+ e instanceof Error ? e.message : JSON.stringify(e)
79
+ }`
80
+ );
81
+ }
82
+ })
83
+ );
84
+ }
85
+ },
86
+ ignoreUndefined: true,
87
+ writer: (output) => {
88
+ return output;
89
+ },
90
+ })
91
+ .setupHistory(".repl_history", () => {
92
+ //
93
+ });