skir-java-gen 0.0.2
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 +424 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +730 -0
- package/dist/index.js.map +1 -0
- package/dist/naming.d.ts +20 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +138 -0
- package/dist/naming.js.map +1 -0
- package/dist/type_speller.d.ts +15 -0
- package/dist/type_speller.d.ts.map +1 -0
- package/dist/type_speller.js +167 -0
- package/dist/type_speller.js.map +1 -0
- package/package.json +50 -0
- package/src/index.ts +1124 -0
- package/src/naming.ts +161 -0
- package/src/type_speller.ts +185 -0
package/README.md
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/skir-java-gen)
|
|
2
|
+
[](https://github.com/gepheum/skir-java-gen/actions)
|
|
3
|
+
|
|
4
|
+
# skir's Java code generator
|
|
5
|
+
|
|
6
|
+
Official plugin for generating Java code from [.skir](https://github.com/gepheum/skir) files.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
From your project's root directory, run `npm i --save-dev skir-java-gen`.
|
|
11
|
+
|
|
12
|
+
In your `skir.yml` file, add the following snippet under `generators`:
|
|
13
|
+
```yaml
|
|
14
|
+
- mod: skir-java-gen
|
|
15
|
+
config: {}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The `npm run skirc` command will now generate .java files within the `skirout` directory.
|
|
19
|
+
|
|
20
|
+
The generated Java code has a runtime dependency on `build.skir:skir-kotlin-client`. Add this line to your `build.gradle` file in the `dependencies` section:
|
|
21
|
+
|
|
22
|
+
```gradle
|
|
23
|
+
implementation 'build.skir:skir-kotlin-client:1.1.4' // Pick the latest version
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For more information, see this Java project [example](https://github.com/gepheum/skir-java-example).
|
|
27
|
+
|
|
28
|
+
## Java generated code guide
|
|
29
|
+
|
|
30
|
+
The examples below are for the code generated from [this](https://github.com/gepheum/skir-java-example/blob/main/skir-src/user.skir) .skir file.
|
|
31
|
+
|
|
32
|
+
### Referring to generated symbols
|
|
33
|
+
|
|
34
|
+
```java
|
|
35
|
+
// Import the given symbols from the Java module generated from "user.skir"
|
|
36
|
+
import skirout.user.User;
|
|
37
|
+
import skirout.user.UserRegistry;
|
|
38
|
+
import skirout.user.SubscriptionStatus;
|
|
39
|
+
import skirout.user.Constants;
|
|
40
|
+
|
|
41
|
+
// Now you can use: Constants.TARZAN, User, UserRegistry, SubscriptionStatus, etc.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Struct classes
|
|
45
|
+
|
|
46
|
+
skir generates a deeply immutable Java class for every struct in the .skir file.
|
|
47
|
+
|
|
48
|
+
```java
|
|
49
|
+
// To construct a User, use the builder pattern.
|
|
50
|
+
|
|
51
|
+
final User john =
|
|
52
|
+
User.builder()
|
|
53
|
+
// All fields are required. The compiler will error if you miss one or if
|
|
54
|
+
// you don't specify them in alphabetical order.
|
|
55
|
+
.setName("John Doe")
|
|
56
|
+
.setPets(
|
|
57
|
+
List.of(
|
|
58
|
+
User.Pet.builder()
|
|
59
|
+
.setHeightInMeters(1.0f)
|
|
60
|
+
.setName("Dumbo")
|
|
61
|
+
.setPicture("🐘")
|
|
62
|
+
.build()))
|
|
63
|
+
.setQuote("Coffee is just a socially acceptable form of rage.")
|
|
64
|
+
.setSubscriptionStatus(SubscriptionStatus.FREE)
|
|
65
|
+
.setUserId(42)
|
|
66
|
+
.build();
|
|
67
|
+
|
|
68
|
+
assert john.name().equals("John Doe");
|
|
69
|
+
|
|
70
|
+
// john.pets().clear();
|
|
71
|
+
// ^ Runtime error: the list is deeply immutable.
|
|
72
|
+
|
|
73
|
+
// With partialBuilder(), you are not required to specify all the fields,
|
|
74
|
+
// and there is no constraint on the order.
|
|
75
|
+
final User jane = User.partialBuilder().setUserId(43).setName("Jane Doe").build();
|
|
76
|
+
|
|
77
|
+
// Fields not explicitly set are initialized to their default values.
|
|
78
|
+
assert jane.quote().equals("");
|
|
79
|
+
assert jane.pets().equals(List.of());
|
|
80
|
+
|
|
81
|
+
// User.DEFAULT is an instance of User with all fields set to their default
|
|
82
|
+
// values.
|
|
83
|
+
assert User.DEFAULT.name().equals("");
|
|
84
|
+
assert User.DEFAULT.userId() == 0;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Creating modified copies
|
|
88
|
+
|
|
89
|
+
```java
|
|
90
|
+
// toBuilder() creates a builder initialized with the values of this instance.
|
|
91
|
+
// This is useful for creating a modified copy of an existing object.
|
|
92
|
+
final User evilJohn =
|
|
93
|
+
john.toBuilder()
|
|
94
|
+
// Like with partialBuilder(), there is no constraint on the order.
|
|
95
|
+
.setName("Evil John")
|
|
96
|
+
.setQuote("I solemnly swear I am up to no good.")
|
|
97
|
+
.build();
|
|
98
|
+
|
|
99
|
+
assert evilJohn.name().equals("Evil John");
|
|
100
|
+
assert evilJohn.userId() == 42;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Enum classes
|
|
104
|
+
|
|
105
|
+
skir generates a deeply immutable Java class for every enum in the .skir file. This class is *not* a Java enum, although the syntax for referring to constants is similar.
|
|
106
|
+
|
|
107
|
+
The definition of the `SubscriptionStatus` enum in the .skir file is:
|
|
108
|
+
```rust
|
|
109
|
+
enum SubscriptionStatus {
|
|
110
|
+
FREE;
|
|
111
|
+
trial: Trial;
|
|
112
|
+
PREMIUM;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Making enum values
|
|
117
|
+
|
|
118
|
+
```java
|
|
119
|
+
final List<SubscriptionStatus> someStatuses =
|
|
120
|
+
List.of(
|
|
121
|
+
// The UNKNOWN constant is present in all skir enums even if it is not
|
|
122
|
+
// declared in the .skir file.
|
|
123
|
+
SubscriptionStatus.UNKNOWN,
|
|
124
|
+
SubscriptionStatus.FREE,
|
|
125
|
+
SubscriptionStatus.PREMIUM,
|
|
126
|
+
// To construct wrapper variants, call the wrap{VariantName} static
|
|
127
|
+
// methods.
|
|
128
|
+
SubscriptionStatus.wrapTrial(
|
|
129
|
+
SubscriptionStatus.Trial.builder()
|
|
130
|
+
.setStartTime(Instant.now())
|
|
131
|
+
.build()));
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Conditions on enums
|
|
135
|
+
|
|
136
|
+
```java
|
|
137
|
+
assert john.subscriptionStatus().equals(SubscriptionStatus.FREE);
|
|
138
|
+
|
|
139
|
+
// UNKNOWN is the default value for enums.
|
|
140
|
+
assert jane.subscriptionStatus().equals(SubscriptionStatus.UNKNOWN);
|
|
141
|
+
|
|
142
|
+
final Instant now = Instant.now();
|
|
143
|
+
final SubscriptionStatus trialStatus =
|
|
144
|
+
SubscriptionStatus.wrapTrial(
|
|
145
|
+
SubscriptionStatus.Trial.builder()
|
|
146
|
+
.setStartTime(now)
|
|
147
|
+
.build());
|
|
148
|
+
|
|
149
|
+
assert trialStatus.kind() == SubscriptionStatus.Kind.TRIAL_WRAPPER;
|
|
150
|
+
assert trialStatus.asTrial().startTime() == now;
|
|
151
|
+
|
|
152
|
+
// SubscriptionStatus.FREE.asTrial();
|
|
153
|
+
// ^ Runtime error: asTrial() can only be called on a trial wrapper.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Branching on enum variants
|
|
157
|
+
|
|
158
|
+
```java
|
|
159
|
+
// First way to branch on enum variants: switch on kind()
|
|
160
|
+
final Function<SubscriptionStatus, String> getInfoText =
|
|
161
|
+
status ->
|
|
162
|
+
switch (status.kind()) {
|
|
163
|
+
case FREE_CONST -> "Free user";
|
|
164
|
+
case PREMIUM_CONST -> "Premium user";
|
|
165
|
+
case TRIAL_WRAPPER -> "On trial since " + status.asTrial().startTime();
|
|
166
|
+
case UNKNOWN -> "Unknown subscription status";
|
|
167
|
+
default -> throw new AssertionError("Unreachable");
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
System.out.println(getInfoText.apply(john.subscriptionStatus()));
|
|
171
|
+
// "Free user"
|
|
172
|
+
|
|
173
|
+
// Second way to branch on enum variants: visitor pattern.
|
|
174
|
+
// It is a bit more verbose, but it adds compile-time safety and it gives
|
|
175
|
+
// you a guarantee that all variants are handled.
|
|
176
|
+
final SubscriptionStatus.Visitor<String> infoTextVisitor =
|
|
177
|
+
new SubscriptionStatus.Visitor<>() {
|
|
178
|
+
@Override
|
|
179
|
+
public String onFree() {
|
|
180
|
+
return "Free user";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@Override
|
|
184
|
+
public String onPremium() {
|
|
185
|
+
return "Premium user";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@Override
|
|
189
|
+
public String onTrial(SubscriptionStatus.Trial trial) {
|
|
190
|
+
return "On trial since " + trial.startTime();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Override
|
|
194
|
+
public String onUnknown() {
|
|
195
|
+
return "Unknown subscription status";
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
System.out.println(john.subscriptionStatus().accept(infoTextVisitor));
|
|
200
|
+
// "Free user"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Serialization
|
|
204
|
+
|
|
205
|
+
Every frozen struct class and enum class has a static readonly `SERIALIZER` property which can be used for serializing and deserializing instances of the class.
|
|
206
|
+
|
|
207
|
+
```java
|
|
208
|
+
// Serialize 'john' to dense JSON.
|
|
209
|
+
|
|
210
|
+
final Serializer<User> serializer = User.SERIALIZER;
|
|
211
|
+
|
|
212
|
+
System.out.println(serializer.toJsonCode(john));
|
|
213
|
+
// [42,"John Doe","Coffee is just a socially acceptable form of rage.",[["Dumbo",1.0,"🐘"]],[1]]
|
|
214
|
+
|
|
215
|
+
// Serialize 'john' to readable JSON.
|
|
216
|
+
System.out.println(serializer.toJsonCode(john, JsonFlavor.READABLE));
|
|
217
|
+
// {
|
|
218
|
+
// "user_id": 42,
|
|
219
|
+
// "name": "John Doe",
|
|
220
|
+
// "quote": "Coffee is just a socially acceptable form of rage.",
|
|
221
|
+
// "pets": [
|
|
222
|
+
// {
|
|
223
|
+
// "name": "Dumbo",
|
|
224
|
+
// "height_in_meters": 1.0,
|
|
225
|
+
// "picture": "🐘"
|
|
226
|
+
// }
|
|
227
|
+
// ],
|
|
228
|
+
// "subscription_status": "FREE"
|
|
229
|
+
// }
|
|
230
|
+
|
|
231
|
+
// The dense JSON flavor is the flavor you should pick if you intend to
|
|
232
|
+
// deserialize the value in the future. skir allows fields to be renamed,
|
|
233
|
+
// and because field names are not part of the dense JSON, renaming a field
|
|
234
|
+
// does not prevent you from deserializing the value.
|
|
235
|
+
// You should pick the readable flavor mostly for debugging purposes.
|
|
236
|
+
|
|
237
|
+
// Serialize 'john' to binary format.
|
|
238
|
+
System.out.println(serializer.toBytes(john));
|
|
239
|
+
|
|
240
|
+
// The binary format is not human readable, but it is slightly more compact
|
|
241
|
+
// than JSON, and serialization/deserialization can be a bit faster in
|
|
242
|
+
// languages like C++. Only use it when this small performance gain is
|
|
243
|
+
// likely to matter, which should be rare.
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Deserialization
|
|
247
|
+
|
|
248
|
+
```java
|
|
249
|
+
// Use fromJson(), fromJsonCode() and fromBytes() to deserialize.
|
|
250
|
+
|
|
251
|
+
final User reserializedJohn =
|
|
252
|
+
serializer.fromJsonCode(serializer.toJsonCode(john));
|
|
253
|
+
assert reserializedJohn.equals(john);
|
|
254
|
+
|
|
255
|
+
final User reserializedEvilJohn =
|
|
256
|
+
serializer.fromJsonCode(
|
|
257
|
+
// fromJson/fromJsonCode can deserialize both dense and readable JSON
|
|
258
|
+
serializer.toJsonCode(john, JsonFlavor.READABLE));
|
|
259
|
+
assert reserializedEvilJohn.equals(evilJohn);
|
|
260
|
+
|
|
261
|
+
final User reserializedJane =
|
|
262
|
+
serializer.fromBytes(serializer.toBytes(jane));
|
|
263
|
+
assert reserializedJane.equals(jane);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Frozen lists and copies
|
|
267
|
+
|
|
268
|
+
```java
|
|
269
|
+
// Since all skir objects are deeply immutable, all lists contained in a
|
|
270
|
+
// skir object are also deeply immutable.
|
|
271
|
+
// This section helps understand when lists are copied and when they are
|
|
272
|
+
// not.
|
|
273
|
+
|
|
274
|
+
final List<User.Pet> pets = new ArrayList<>();
|
|
275
|
+
pets.add(
|
|
276
|
+
User.Pet.builder()
|
|
277
|
+
.setHeightInMeters(0.25f)
|
|
278
|
+
.setName("Fluffy")
|
|
279
|
+
.setPicture("🐶")
|
|
280
|
+
.build());
|
|
281
|
+
pets.add(
|
|
282
|
+
User.Pet.builder()
|
|
283
|
+
.setHeightInMeters(0.5f)
|
|
284
|
+
.setName("Fido")
|
|
285
|
+
.setPicture("🐻")
|
|
286
|
+
.build());
|
|
287
|
+
|
|
288
|
+
final User jade =
|
|
289
|
+
User.partialBuilder()
|
|
290
|
+
.setName("Jade")
|
|
291
|
+
.setPets(pets)
|
|
292
|
+
// 'pets' is mutable, so skir makes an immutable shallow copy of it
|
|
293
|
+
.build();
|
|
294
|
+
|
|
295
|
+
// jade.pets().clear();
|
|
296
|
+
// ^ Runtime error: pets is a frozen list
|
|
297
|
+
|
|
298
|
+
assert pets.equals(jade.pets());
|
|
299
|
+
assert pets != jade.pets();
|
|
300
|
+
|
|
301
|
+
final User jack =
|
|
302
|
+
User.partialBuilder()
|
|
303
|
+
.setName("Jack")
|
|
304
|
+
.setPets(jade.pets())
|
|
305
|
+
// The list is already immutable, so skir does not make a copy
|
|
306
|
+
.build();
|
|
307
|
+
|
|
308
|
+
assert jack.pets() == jade.pets();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Keyed lists
|
|
312
|
+
|
|
313
|
+
```java
|
|
314
|
+
final UserRegistry userRegistry =
|
|
315
|
+
UserRegistry.builder().setUsers(List.of(john, jane, evilJohn)).build();
|
|
316
|
+
|
|
317
|
+
// findByKey() returns the user with the given key (specified in the .skir file).
|
|
318
|
+
// In this example, the key is the user id.
|
|
319
|
+
// The first lookup runs in O(N) time, and the following lookups run in O(1)
|
|
320
|
+
// time.
|
|
321
|
+
assert userRegistry.users().findByKey(43) == jane;
|
|
322
|
+
// If multiple elements have the same key, the last one is returned.
|
|
323
|
+
assert userRegistry.users().findByKey(42) == evilJohn;
|
|
324
|
+
assert userRegistry.users().findByKey(100) == null;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Constants
|
|
328
|
+
|
|
329
|
+
```java
|
|
330
|
+
System.out.println(Constants.TARZAN);
|
|
331
|
+
// {
|
|
332
|
+
// "user_id": 123,
|
|
333
|
+
// "name": "Tarzan",
|
|
334
|
+
// "quote": "AAAAaAaAaAyAAAAaAaAaAyAAAAaAaAaA",
|
|
335
|
+
// "pets": [
|
|
336
|
+
// {
|
|
337
|
+
// "name": "Cheeta",
|
|
338
|
+
// "height_in_meters": 1.67,
|
|
339
|
+
// "picture": "🐒"
|
|
340
|
+
// }
|
|
341
|
+
// ],
|
|
342
|
+
// "subscription_status": {
|
|
343
|
+
// "kind": "trial",
|
|
344
|
+
// "value": {
|
|
345
|
+
// "start_time": {
|
|
346
|
+
// "unix_millis": 1743592409000,
|
|
347
|
+
// "formatted": "2025-04-02T11:13:29Z"
|
|
348
|
+
// }
|
|
349
|
+
// }
|
|
350
|
+
// }
|
|
351
|
+
// }
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### skir services
|
|
355
|
+
|
|
356
|
+
#### Starting a skir service on an HTTP server
|
|
357
|
+
|
|
358
|
+
Full example [here](https://github.com/gepheum/skir-java-example/blob/main/src/main/java/examples/StartService.java).
|
|
359
|
+
|
|
360
|
+
#### Sending RPCs to a skir service
|
|
361
|
+
|
|
362
|
+
Full example [here](https://github.com/gepheum/skir-java-example/blob/main/src/main/java/examples/CallService.java).
|
|
363
|
+
|
|
364
|
+
### Reflection
|
|
365
|
+
|
|
366
|
+
Reflection allows you to inspect a skir type at runtime.
|
|
367
|
+
|
|
368
|
+
```java
|
|
369
|
+
import build.skir.reflection.StructDescriptor;
|
|
370
|
+
import build.skir.reflection.TypeDescriptor;
|
|
371
|
+
|
|
372
|
+
System.out.println(
|
|
373
|
+
User.TYPE_DESCRIPTOR
|
|
374
|
+
.getFields()
|
|
375
|
+
.stream()
|
|
376
|
+
.map((field) -> field.getName())
|
|
377
|
+
.toList());
|
|
378
|
+
// [user_id, name, quote, pets, subscription_status]
|
|
379
|
+
|
|
380
|
+
// A type descriptor can be serialized to JSON and deserialized later.
|
|
381
|
+
final TypeDescriptor typeDescriptor =
|
|
382
|
+
TypeDescriptor.Companion.parseFromJsonCode(
|
|
383
|
+
User.SERIALIZER.typeDescriptor().asJsonCode());
|
|
384
|
+
|
|
385
|
+
assert typeDescriptor instanceof StructDescriptor;
|
|
386
|
+
assert ((StructDescriptor) typeDescriptor).getFields().size() == 5;
|
|
387
|
+
|
|
388
|
+
// The 'allStringsToUpperCase' function uses reflection to convert all the
|
|
389
|
+
// strings contained in a given skir value to upper case.
|
|
390
|
+
// See the implementation at
|
|
391
|
+
// https://github.com/gepheum/skir-java-example/blob/main/src/main/java/examples/AllStringsToUpperCase.java
|
|
392
|
+
System.out.println(
|
|
393
|
+
AllStringsToUpperCase.allStringsToUpperCase(
|
|
394
|
+
Constants.TARZAN, User.TYPE_DESCRIPTOR));
|
|
395
|
+
// {
|
|
396
|
+
// "user_id": 123,
|
|
397
|
+
// "name": "TARZAN",
|
|
398
|
+
// "quote": "AAAAAAAAAAYAAAAAAAAAAYAAAAAAAAAA",
|
|
399
|
+
// "pets": [
|
|
400
|
+
// {
|
|
401
|
+
// "name": "CHEETA",
|
|
402
|
+
// "height_in_meters": 1.67,
|
|
403
|
+
// "picture": "🐒"
|
|
404
|
+
// }
|
|
405
|
+
// ],
|
|
406
|
+
// "subscription_status": {
|
|
407
|
+
// "kind": "trial",
|
|
408
|
+
// "value": {
|
|
409
|
+
// "start_time": {
|
|
410
|
+
// "unix_millis": 1743592409000,
|
|
411
|
+
// "formatted": "2025-04-02T11:13:29Z"
|
|
412
|
+
// }
|
|
413
|
+
// }
|
|
414
|
+
// }
|
|
415
|
+
// }
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Java codegen versus Kotlin codegen
|
|
419
|
+
|
|
420
|
+
While Java and Kotlin code can interoperate seamlessly, skir provides separate code generators for each language to leverage their unique strengths and idioms. For instance, the Kotlin generator utilizes named parameters for struct construction, whereas the Java generator employs the builder pattern.
|
|
421
|
+
|
|
422
|
+
Although it's technically feasible to use Kotlin-generated code in a Java project (or vice versa), doing so results in an API that feels unnatural and cumbersome in the calling language. For the best developer experience, use the code generator that matches your project's primary language.
|
|
423
|
+
|
|
424
|
+
Note that both the Java and Kotlin generated code share the same runtime dependency: `build.skir:skir-kotlin-client`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CodeGenerator } from "skir-internal";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
declare const Config: z.ZodObject<{
|
|
4
|
+
packagePrefix: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
type Config = z.infer<typeof Config>;
|
|
7
|
+
declare class JavaCodeGenerator implements CodeGenerator<Config> {
|
|
8
|
+
readonly id = "java";
|
|
9
|
+
readonly configType: z.ZodObject<{
|
|
10
|
+
packagePrefix: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
readonly version = "1.0.0";
|
|
13
|
+
generateCode(input: CodeGenerator.Input<Config>): CodeGenerator.Output;
|
|
14
|
+
}
|
|
15
|
+
export declare const GENERATOR: JavaCodeGenerator;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,aAAa,EASnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,MAAM;;iBAKV,CAAC;AAEH,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAErC,cAAM,iBAAkB,YAAW,aAAa,CAAC,MAAM,CAAC;IACtD,QAAQ,CAAC,EAAE,UAAU;IACrB,QAAQ,CAAC,UAAU;;sBAAU;IAC7B,QAAQ,CAAC,OAAO,WAAW;IAE3B,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM;CAmDvE;AA+gCD,eAAO,MAAM,SAAS,mBAA0B,CAAC"}
|