expressir 2.1.29 → 2.1.31

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +98 -0
  3. data/.github/workflows/links.yml +100 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.github/workflows/validate_schemas.yml +1 -1
  7. data/.gitignore +3 -0
  8. data/.rubocop.yml +1 -1
  9. data/.rubocop_todo.yml +209 -55
  10. data/Gemfile +2 -1
  11. data/README.adoc +650 -83
  12. data/docs/Gemfile +12 -0
  13. data/docs/_config.yml +141 -0
  14. data/docs/_guides/changes/changes-format.adoc +778 -0
  15. data/docs/_guides/changes/importing-eengine.adoc +898 -0
  16. data/docs/_guides/changes/index.adoc +396 -0
  17. data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
  18. data/docs/_guides/changes/validating-changes.adoc +681 -0
  19. data/docs/_guides/cli/benchmark-performance.adoc +834 -0
  20. data/docs/_guides/cli/coverage-analysis.adoc +921 -0
  21. data/docs/_guides/cli/format-schemas.adoc +547 -0
  22. data/docs/_guides/cli/index.adoc +8 -0
  23. data/docs/_guides/cli/managing-changes.adoc +927 -0
  24. data/docs/_guides/cli/validate-ascii.adoc +645 -0
  25. data/docs/_guides/cli/validate-schemas.adoc +534 -0
  26. data/docs/_guides/index.adoc +165 -0
  27. data/docs/_guides/ler/creating-packages.adoc +664 -0
  28. data/docs/_guides/ler/index.adoc +305 -0
  29. data/docs/_guides/ler/loading-packages.adoc +707 -0
  30. data/docs/_guides/ler/package-formats.adoc +748 -0
  31. data/docs/_guides/ler/querying-packages.adoc +826 -0
  32. data/docs/_guides/ler/validating-packages.adoc +750 -0
  33. data/docs/_guides/liquid/basic-templates.adoc +813 -0
  34. data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
  35. data/docs/_guides/liquid/drops-reference.adoc +829 -0
  36. data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
  37. data/docs/_guides/liquid/index.adoc +468 -0
  38. data/docs/_guides/manifests/creating-manifests.adoc +483 -0
  39. data/docs/_guides/manifests/index.adoc +307 -0
  40. data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
  41. data/docs/_guides/manifests/validating-manifests.adoc +713 -0
  42. data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
  43. data/docs/_guides/ruby-api/index.adoc +257 -0
  44. data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
  45. data/docs/_guides/ruby-api/search-engine.adoc +609 -0
  46. data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
  47. data/docs/_pages/data-model.adoc +665 -0
  48. data/docs/_pages/express-language.adoc +506 -0
  49. data/docs/_pages/getting-started.adoc +414 -0
  50. data/docs/_pages/index.adoc +116 -0
  51. data/docs/_pages/introduction.adoc +256 -0
  52. data/docs/_pages/ler-packages.adoc +837 -0
  53. data/docs/_pages/parsers.adoc +683 -0
  54. data/docs/_pages/schema-manifests.adoc +431 -0
  55. data/docs/_references/index.adoc +228 -0
  56. data/docs/_tutorials/creating-ler-package.adoc +735 -0
  57. data/docs/_tutorials/documentation-coverage.adoc +795 -0
  58. data/docs/_tutorials/index.adoc +221 -0
  59. data/docs/_tutorials/liquid-templates.adoc +806 -0
  60. data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
  61. data/docs/_tutorials/querying-schemas.adoc +751 -0
  62. data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
  63. data/docs/index.adoc +242 -0
  64. data/docs/lychee.toml +84 -0
  65. data/examples/demo_ler_usage.sh +86 -0
  66. data/examples/ler/README.md +111 -0
  67. data/examples/ler/simple_example.ler +0 -0
  68. data/examples/ler/simple_schema.exp +33 -0
  69. data/examples/ler_build.rb +75 -0
  70. data/examples/ler_cli.rb +79 -0
  71. data/examples/ler_demo_complete.rb +276 -0
  72. data/examples/ler_query.rb +91 -0
  73. data/examples/ler_query_examples.rb +305 -0
  74. data/examples/ler_stats.rb +81 -0
  75. data/examples/phase3_demo.rb +159 -0
  76. data/examples/query_demo_simple.rb +131 -0
  77. data/expressir.gemspec +2 -0
  78. data/lib/expressir/changes/schema_change.rb +32 -22
  79. data/lib/expressir/changes/{edition_change.rb → version_change.rb} +3 -3
  80. data/lib/expressir/cli.rb +12 -4
  81. data/lib/expressir/commands/changes_import_eengine.rb +2 -2
  82. data/lib/expressir/commands/changes_validate.rb +1 -1
  83. data/lib/expressir/commands/manifest.rb +427 -0
  84. data/lib/expressir/commands/package.rb +1274 -0
  85. data/lib/expressir/commands/validate.rb +70 -37
  86. data/lib/expressir/commands/validate_ascii.rb +607 -0
  87. data/lib/expressir/commands/validate_load.rb +88 -0
  88. data/lib/expressir/express/formatter.rb +5 -1
  89. data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
  90. data/lib/expressir/express/parser.rb +33 -0
  91. data/lib/expressir/manifest/resolver.rb +213 -0
  92. data/lib/expressir/manifest/validator.rb +195 -0
  93. data/lib/expressir/model/declarations/entity.rb +6 -0
  94. data/lib/expressir/model/dependency_resolver.rb +270 -0
  95. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  96. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  97. data/lib/expressir/model/indexes/type_index.rb +149 -0
  98. data/lib/expressir/model/interface_validator.rb +384 -0
  99. data/lib/expressir/model/repository.rb +400 -5
  100. data/lib/expressir/model/repository_validator.rb +295 -0
  101. data/lib/expressir/model/search_engine.rb +525 -0
  102. data/lib/expressir/model.rb +4 -94
  103. data/lib/expressir/package/builder.rb +200 -0
  104. data/lib/expressir/package/metadata.rb +81 -0
  105. data/lib/expressir/package/reader.rb +165 -0
  106. data/lib/expressir/schema_manifest.rb +11 -1
  107. data/lib/expressir/version.rb +1 -1
  108. data/lib/expressir.rb +16 -3
  109. metadata +115 -5
  110. data/docs/benchmarking.adoc +0 -107
  111. data/docs/liquid_drops.adoc +0 -1547
@@ -0,0 +1,522 @@
1
+ ---
2
+ title: Parsing Your First Schema
3
+ nav_order: 2
4
+ ---
5
+
6
+ == Parsing Your First Schema
7
+
8
+ === Prerequisites
9
+
10
+ Before starting this tutorial, ensure you have:
11
+
12
+ * Ruby 2.7 or later installed
13
+ * Expressir gem installed (see link:../pages/getting-started.html[Getting Started])
14
+ * Basic understanding of EXPRESS (see link:../pages/express-language.html[EXPRESS Language])
15
+ * A text editor for creating files
16
+
17
+ === Learning Objectives
18
+
19
+ By the end of this tutorial, you will be able to:
20
+
21
+ * Create a simple EXPRESS schema file
22
+ * Parse the schema using the Expressir CLI
23
+ * Parse the schema using the Ruby API
24
+ * Navigate the parsed data model
25
+ * Handle parsing errors gracefully
26
+ * Understand the structure of a parsed repository
27
+
28
+ === What You'll Build
29
+
30
+ You'll create a simple EXPRESS schema representing a person and organization, then parse it with Expressir to explore the resulting Ruby data model.
31
+
32
+ === Step 1: Create a Sample Schema
33
+
34
+ Create a file named `person_schema.exp` with the following content:
35
+
36
+ [source,express]
37
+ ----
38
+ SCHEMA person_schema;
39
+
40
+ ENTITY person;
41
+ name : STRING;
42
+ age : INTEGER;
43
+ email : OPTIONAL STRING;
44
+ END_ENTITY;
45
+
46
+ ENTITY organization;
47
+ org_name : STRING;
48
+ employees : SET [0:?] OF person;
49
+ founded : INTEGER;
50
+ END_ENTITY;
51
+
52
+ TYPE person_list = LIST [1:?] OF person;
53
+ END_TYPE;
54
+
55
+ END_SCHEMA;
56
+ ----
57
+
58
+ This schema defines:
59
+
60
+ * **person entity**: With name, age, and optional email
61
+ * **organization entity**: With name, employees (set of persons), and founding year
62
+ * **person_list type**: A list of at least one person
63
+
64
+ === Step 2: Parse with CLI
65
+
66
+ The simplest way to parse is using the command line.
67
+
68
+ ==== Format the Schema
69
+
70
+ First, verify the schema is valid by formatting it:
71
+
72
+ [source,bash]
73
+ ----
74
+ expressir format person_schema.exp
75
+ ----
76
+
77
+ **Expected output**: The schema printed to stdout with consistent formatting.
78
+
79
+ If there are syntax errors, Expressir will report them with line numbers.
80
+
81
+ ==== Validate the Schema
82
+
83
+ Check if the schema meets validation criteria:
84
+
85
+ [source,bash]
86
+ ----
87
+ expressir validate person_schema.exp
88
+ ----
89
+
90
+ **Expected o
91
+
92
+ utput**:
93
+ [source]
94
+ ----
95
+ Validation passed for all EXPRESS schemas.
96
+ ----
97
+
98
+ ==== What Happened?
99
+
100
+ The CLI:
101
+
102
+ 1. Read the file `person_schema.exp`
103
+ 2. Parsed the EXPRESS text into an Abstract Syntax Tree (AST)
104
+ 3. Transformed the AST into Expressir's Ruby data model
105
+ 4. Resolved all references within the schema
106
+ 5. Formatted or validated the result
107
+
108
+ === Step 3: Parse with Ruby API
109
+
110
+ Now let's parse programmatically using Ruby.
111
+
112
+ ==== Basic Parsing
113
+
114
+ Create a file named `parse_person.rb`:
115
+
116
+ [source,ruby]
117
+ ----
118
+ require 'expressir'
119
+
120
+ # Parse the schema file
121
+ repository = Expressir::Express::Parser.from_file('person_schema.exp')
122
+
123
+ # Access the repository
124
+ puts "Parsed successfully!"
125
+ puts "Number of schemas: #{repository.schemas.size}"
126
+
127
+ # Get the first (and only) schema
128
+ schema = repository.schemas.first
129
+ puts "Schema name: #{schema.id}"
130
+ puts "Schema file: #{schema.file}"
131
+ ----
132
+
133
+ Run it:
134
+
135
+ [source,bash]
136
+ ----
137
+ ruby parse_person.rb
138
+ ----
139
+
140
+ **Expected output**:
141
+ [source]
142
+ ----
143
+ Parsed successfully!
144
+ Number of schemas: 1
145
+ Schema name: person_schema
146
+ Schema file: person_schema.exp
147
+ ----
148
+
149
+ ==== Understanding the Result
150
+
151
+ The [`from_file`](../../lib/expressir/express/parser.rb:605) method returns a [`Repository`](../../lib/expressir/model/repository.rb:10) object containing:
152
+
153
+ * **schemas**: Array of [`Schema`](../../lib/expressir/model/declarations/schema.rb:6) objects
154
+ * **Indexes**: Built automatically for fast lookups
155
+
156
+ Each [`Schema`](../../lib/expressir/model/declarations/schema.rb:6) contains:
157
+
158
+ * **id**: Schema name
159
+ * **file**: Source file path
160
+ * **entities**: Array of entity definitions
161
+ * **types**: Array of type definitions
162
+ * **functions**, **procedures**, **rules**: Other declarations
163
+
164
+ === Step 4: Explore the Parsed Model
165
+
166
+ Now let's explore what was parsed.
167
+
168
+ ==== List Entities
169
+
170
+ Add to `parse_person.rb`:
171
+
172
+ [source,ruby]
173
+ ----
174
+ # List all entities
175
+ puts "\nEntities:"
176
+ schema.entities.each do |entity|
177
+ puts " - #{entity.id}"
178
+
179
+ # List entity attributes
180
+ entity.attributes.each do |attr|
181
+ optional = attr.optional ? " (optional)" : ""
182
+ puts " * #{attr.id}: #{attr.type}#{optional}"
183
+ end
184
+ end
185
+ ----
186
+
187
+ **Output**:
188
+ [source]
189
+ ----
190
+ Entities:
191
+ - person
192
+ * name: STRING
193
+ * age: INTEGER
194
+ * email: STRING (optional)
195
+ - organization
196
+ * org_name: STRING
197
+ * employees: SET [0:?] OF person
198
+ * founded: INTEGER
199
+ ----
200
+
201
+ ==== List Types
202
+
203
+ Add to `parse_person.rb`:
204
+
205
+ [source,ruby]
206
+ ----
207
+ # List all types
208
+ puts "\nTypes:"
209
+ schema.types.each do |type|
210
+ puts " - #{type.id}: #{type.underlying_type}"
211
+ end
212
+ ----
213
+
214
+ **Output**:
215
+ [source]
216
+ ----
217
+ Types:
218
+ - person_list: LIST [1:?] OF person
219
+ ----
220
+
221
+ ==== Access Specific Elements
222
+
223
+ Add to `parse_person.rb`:
224
+
225
+ [source,ruby]
226
+ ----
227
+ # Find a specific entity
228
+ person_entity = schema.entities.find { |e| e.id == "person" }
229
+ if person_entity
230
+ puts "\nFound person entity with #{person_entity.attributes.size} attributes"
231
+
232
+ # Access individual attributes
233
+ name_attr = person_entity.attributes.find { |a| a.id == "name" }
234
+ puts "Name attribute type: #{name_attr.type}"
235
+ end
236
+ ----
237
+
238
+ **Output**:
239
+ [source]
240
+ ----
241
+ Found person entity with 3 attributes
242
+ Name attribute type: STRING
243
+ ----
244
+
245
+ === Step 5: Handle Parsing Errors
246
+
247
+ Errors happen. Let's learn to handle them gracefully.
248
+
249
+ ==== Create an Invalid Schema
250
+
251
+ Create `invalid_schema.exp`:
252
+
253
+ [source,express]
254
+ ----
255
+ SCHEMA invalid_schema;
256
+
257
+ ENTITY person
258
+ name : STRING; -- Missing semicolon after person
259
+ END_ENTITY;
260
+
261
+ END_SCHEMA;
262
+ ----
263
+
264
+ ==== Catch and Handle Errors
265
+
266
+ Create `handle_errors.rb`:
267
+
268
+ [source,ruby]
269
+ ----
270
+ require 'expressir'
271
+
272
+ begin
273
+ repository = Expressir::Express::Parser.from_file('invalid_schema.exp')
274
+ puts "Parsing succeeded!"
275
+ rescue Expressir::Express::Error::SchemaParseFailure => e
276
+ puts "❌ Parsing failed!"
277
+ puts "\nFile: #{e.filename}"
278
+ puts "\nError message:"
279
+ puts e.message
280
+ puts "\nDetailed parse tree:"
281
+ puts e.parse_failure_cause.ascii_tree
282
+ end
283
+ ----
284
+
285
+ Run it:
286
+
287
+ [source,bash]
288
+ ----
289
+ ruby handle_errors.rb
290
+ ----
291
+
292
+ **Expected output**:
293
+ [source]
294
+ ----
295
+ ❌ Parsing failed!
296
+
297
+ File: invalid_schema.exp
298
+
299
+ Error message:
300
+ Failed to parse invalid_schema.exp
301
+
302
+ Detailed parse tree:
303
+ [Shows detailed error location with line/column]
304
+ ----
305
+
306
+ ==== Common Parsing Errors
307
+
308
+ [options="header"]
309
+ |===
310
+ | Error | Cause | Solution
311
+ | "Expected ';'" | Missing semicolon | Add semicolon after declaration
312
+ | "Expected identifier" | Invalid name | Use valid identifier (letters, numbers, underscore)
313
+ | "Unexpected keyword" | Misused keyword | Check EXPRESS syntax reference
314
+ | "Expected 'END_ENTITY'" | Mismatched END | Ensure END matches opening keyword
315
+ |===
316
+
317
+ === Step 6: Working with Parsed Data
318
+
319
+ Let's do something useful with the parsed schema.
320
+
321
+ ==== Generate a Summary Report
322
+
323
+ Create `schema_summary.rb`:
324
+
325
+ [source,ruby]
326
+ ----
327
+ require 'expressir'
328
+
329
+ # Parse the schema
330
+ repo = Expressir::Express::Parser.from_file('person_schema.exp')
331
+ schema = repo.schemas.first
332
+
333
+ # Generate summary
334
+ puts "=" * 60
335
+ puts "Schema Summary: #{schema.id}"
336
+ puts "=" * 60
337
+
338
+ # Count elements
339
+ puts "\nStatistics:"
340
+ puts " Entities: #{schema.entities.size}"
341
+ puts " Types: #{schema.types.size}"
342
+ puts " Total attributes: #{schema.entities.sum { |e| e.attributes.size }}"
343
+
344
+ # Detailed entity report
345
+ puts "\nEntities:"
346
+ schema.entities.each do |entity|
347
+ attr_count = entity.attributes.size
348
+ optional_count = entity.attributes.count(&:optional)
349
+
350
+ puts "\n #{entity.id}:"
351
+ puts " Total attributes: #{attr_count}"
352
+ puts " Optional attributes: #{optional_count}"
353
+ puts " Required attributes: #{attr_count - optional_count}"
354
+ end
355
+
356
+ # Type report
357
+ puts "\nType Definitions:"
358
+ schema.types.each do |type|
359
+ puts " #{type.id} -> #{type.underlying_type}"
360
+ end
361
+
362
+ puts "\n" + "=" * 60
363
+ ----
364
+
365
+ Run it:
366
+
367
+ [source,bash]
368
+ ----
369
+ ruby schema_summary.rb
370
+ ----
371
+
372
+ **Expected output**:
373
+ [source]
374
+ ----
375
+ ============================================================
376
+ Schema Summary: person_schema
377
+ ============================================================
378
+
379
+ Statistics:
380
+ Entities: 2
381
+ Types: 1
382
+ Total attributes: 6
383
+
384
+ Entities:
385
+
386
+ person:
387
+ Total attributes: 3
388
+ Optional attributes: 1
389
+ Required attributes: 2
390
+
391
+ organization:
392
+ Total attributes: 3
393
+ Optional attributes: 0
394
+ Required attributes: 3
395
+
396
+ Type Definitions:
397
+ person_list -> LIST [1:?] OF person
398
+
399
+ ============================================================
400
+ ----
401
+
402
+ === Step 7: Practice Exercises
403
+
404
+ Now it's your turn! Try these exercises to reinforce your learning.
405
+
406
+ ==== Exercise 1: Add More Entities
407
+
408
+ Modify `person_schema.exp` to add:
409
+
410
+ * A `project` entity with name and deadline attributes
411
+ * A `team` entity that references persons and projects
412
+
413
+ Parse it and verify:
414
+ * The new entities appear in the entity list
415
+ * The attributes are correctly parsed
416
+ * References between entities work
417
+
418
+ ==== Exercise 2: Parse Multiple Files
419
+
420
+ Create two schema files:
421
+
422
+ * `base_schema.exp` - With basic entities
423
+ * `extended_schema.exp` - That uses entities from base_schema (via USE FROM)
424
+
425
+ Parse both files together:
426
+
427
+ [source,ruby]
428
+ ----
429
+ files = ['base_schema.exp', 'extended_schema.exp']
430
+ repo = Expressir::Express::Parser.from_files(files)
431
+ puts "Parsed #{repo.schemas.size} schemas"
432
+ ----
433
+
434
+ ==== Exercise 3: Type Exploration
435
+
436
+ Create a schema with various type definitions:
437
+
438
+ * ENUMERATION type (e.g., status: active, inactive, pending)
439
+ * SELECT type (union of multiple types)
440
+ * Aggregate type (ARRAY, LIST, SET, BAG)
441
+
442
+ Parse and identify the type of each TYPE definition.
443
+
444
+ ==== Exercise 4: Error Recovery
445
+
446
+ Create intentionally broken schemas with different errors:
447
+
448
+ * Missing END_ENTITY
449
+ * Invalid attribute type
450
+ * Circular reference
451
+
452
+ Practice catching and logging each error type appropriately.
453
+
454
+ === Common Pitfalls
455
+
456
+ ==== Forgetting Reference Resolution
457
+
458
+ [source,ruby]
459
+ ----
460
+ # ❌ References not resolved
461
+ repo = Expressir::Express::Parser.from_file('schema.exp', skip_references: true)
462
+ entity.attributes.first.type.ref # => nil (not resolved!)
463
+
464
+ # ✅ References resolved (default)
465
+ repo = Expressir::Express::Parser.from_file('schema.exp')
466
+ entity.attributes.first.type.ref # => Points to actual type
467
+ ----
468
+
469
+ ==== Not Handling Parse Errors
470
+
471
+ [source,ruby]
472
+ ----
473
+ # ❌ Unhandled errors crash program
474
+ repo = Expressir::Express::Parser.from_file('might-be-invalid.exp')
475
+
476
+ # ✅ Graceful error handling
477
+ begin
478
+ repo = Expressir::Express::Parser.from_file('might-be-invalid.exp')
479
+ rescue Expressir::Express::Error::SchemaParseFailure => e
480
+ warn "Skipping invalid schema: #{e.filename}"
481
+ end
482
+ ----
483
+
484
+ ==== Assuming Single Schema
485
+
486
+ [source,ruby]
487
+ ----
488
+ # ❌ Assuming first schema
489
+ schema = repo.schemas.first # Might be wrong file!
490
+
491
+ # ✅ Finding correct schema
492
+ schema = repo.schemas.find { |s| s.id == "person_schema" }
493
+ ----
494
+
495
+ === Next Steps
496
+
497
+ Congratulations! You've learned to parse EXPRESS schemas with Expressir.
498
+
499
+ **Continue learning**:
500
+
501
+ * link:working-with-multiple-schemas.html[Working with Multiple Schemas] - Handle dependencies and interfaces
502
+ * link:querying-schemas.html[Querying Schemas] - Find and filter entities/types
503
+ * link:creating-ler-package.html[Creating LER Packages] - Package for performance
504
+
505
+ **Read more**:
506
+
507
+ * link:../pages/parsers.html[Parsers] - Deep dive into parsing architecture
508
+ * link:../pages/data-model.html[Data Model] - Understanding the Ruby model
509
+ * link:../guides/ruby-api/[Ruby API Guides] - Advanced programmatic usage
510
+
511
+ === Summary
512
+
513
+ In this tutorial, you learned to:
514
+
515
+ * ✅ Create valid EXPRESS schema files
516
+ * ✅ Parse schemas using CLI and Ruby API
517
+ * ✅ Navigate the parsed data model
518
+ * ✅ Access entities, types, and attributes
519
+ * ✅ Handle parsing errors gracefully
520
+ * ✅ Generate reports from parsed schemas
521
+
522
+ You're now ready to work with more complex schemas and explore advanced Expressir features!