expressir 2.1.30 → 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 (107) 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 +244 -39
  10. data/Gemfile +2 -1
  11. data/README.adoc +621 -54
  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/cli.rb +12 -4
  79. data/lib/expressir/commands/manifest.rb +427 -0
  80. data/lib/expressir/commands/package.rb +1274 -0
  81. data/lib/expressir/commands/validate.rb +70 -37
  82. data/lib/expressir/commands/validate_ascii.rb +607 -0
  83. data/lib/expressir/commands/validate_load.rb +88 -0
  84. data/lib/expressir/express/formatter.rb +5 -1
  85. data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
  86. data/lib/expressir/express/parser.rb +33 -0
  87. data/lib/expressir/manifest/resolver.rb +213 -0
  88. data/lib/expressir/manifest/validator.rb +195 -0
  89. data/lib/expressir/model/declarations/entity.rb +6 -0
  90. data/lib/expressir/model/dependency_resolver.rb +270 -0
  91. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  92. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  93. data/lib/expressir/model/indexes/type_index.rb +149 -0
  94. data/lib/expressir/model/interface_validator.rb +384 -0
  95. data/lib/expressir/model/repository.rb +400 -5
  96. data/lib/expressir/model/repository_validator.rb +295 -0
  97. data/lib/expressir/model/search_engine.rb +525 -0
  98. data/lib/expressir/model.rb +4 -94
  99. data/lib/expressir/package/builder.rb +200 -0
  100. data/lib/expressir/package/metadata.rb +81 -0
  101. data/lib/expressir/package/reader.rb +165 -0
  102. data/lib/expressir/schema_manifest.rb +11 -1
  103. data/lib/expressir/version.rb +1 -1
  104. data/lib/expressir.rb +15 -2
  105. metadata +114 -4
  106. data/docs/benchmarking.adoc +0 -107
  107. data/docs/liquid_drops.adoc +0 -1547
@@ -0,0 +1,806 @@
1
+ ---
2
+ title: Liquid Templates
3
+ nav_order: 6
4
+ ---
5
+
6
+ == Liquid Templates for Documentation
7
+
8
+ === Prerequisites
9
+
10
+ Before starting this tutorial, ensure you have:
11
+
12
+ * Completed link:querying-schemas.html[Querying Schemas]
13
+ * Basic understanding of template languages
14
+ * Familiarity with Expressir data model
15
+ * Ruby and Expressir installed
16
+
17
+ === Learning Objectives
18
+
19
+ By the end of this tutorial, you will be able to:
20
+
21
+ * Convert Expressir models to Liquid drops
22
+ * Create basic Liquid templates
23
+ * Access schema elements in templates
24
+ * Use filters to format output
25
+ * Generate entity and type documentation
26
+ * Create complete schema documentation
27
+ * Build custom documentation generators
28
+
29
+ === What You'll Build
30
+
31
+ You'll create various documentation templates that generate HTML and Markdown documentation from EXPRESS schemas automatically.
32
+
33
+ === Step 1: Understanding Liquid Integration
34
+
35
+ ==== What is Liquid?
36
+
37
+ Liquid is a template language created by Shopify that allows you to:
38
+
39
+ * Generate dynamic content from data
40
+ * Use control structures (if/for/etc.)
41
+ * Apply filters to transform data
42
+ * Create reusable templates
43
+
44
+ ==== Expressir Liquid Drops
45
+
46
+ Expressir converts its Ruby data model to "Liquid drops" - objects that work in templates:
47
+
48
+ [source]
49
+ ----
50
+ Expressir Model → Liquid Drop → Template → Output
51
+ ----
52
+
53
+ Every model class has a corresponding drop class:
54
+ * `Repository` → `RepositoryDrop`
55
+ * `Schema` → `SchemaDrop`
56
+ * `Entity` → `EntityDrop`
57
+ * `Type` → `TypeDrop`
58
+ * etc.
59
+
60
+ === Step 2: Your First Template
61
+
62
+ ==== Setup
63
+
64
+ Create `template_demo.rb`:
65
+
66
+ [source,ruby]
67
+ ----
68
+ require 'expressir'
69
+ require 'liquid'
70
+
71
+ # Parse schema
72
+ repo = Expressir::Express::Parser.from_file('example.exp')
73
+
74
+ # Convert to Liquid drop
75
+ repo_drop = repo.to_liquid
76
+
77
+ # Create Liquid template
78
+ template_text = <<~LIQUID
79
+ Schemas: {{ schemas.size }}
80
+
81
+ {% for schema in schemas %}
82
+ - {{ schema.id }}
83
+ {% endfor %}
84
+ LIQUID
85
+
86
+ template = Liquid::Template.parse(template_text)
87
+
88
+ # Render
89
+ output = template.render('schemas' => repo_drop.schemas)
90
+ puts output
91
+ ----
92
+
93
+ ==== Create Sample Schema
94
+
95
+ Create `example.exp`:
96
+
97
+ [source,express]
98
+ ----
99
+ SCHEMA example_schema;
100
+
101
+ ENTITY person;
102
+ name : STRING;
103
+ age : INTEGER;
104
+ END_ENTITY;
105
+
106
+ ENTITY organization;
107
+ org_name : STRING;
108
+ employees : SET [0:?] OF person;
109
+ END_ENTITY;
110
+
111
+ END_SCHEMA;
112
+ ----
113
+
114
+ ==== Run the Template
115
+
116
+ [source,bash]
117
+ ----
118
+ ruby template_demo.rb
119
+ ----
120
+
121
+ **Output**:
122
+ [source]
123
+ ----
124
+ Schemas: 1
125
+
126
+ - example_schema
127
+ ----
128
+
129
+ === Step 3: Accessing Schema Elements
130
+
131
+ ==== List Entities
132
+
133
+ Create `list_entities.liquid`:
134
+
135
+ [source,liquid]
136
+ ----
137
+ # {{ schema.id }}
138
+
139
+ ## Entities
140
+
141
+ {% for entity in schema.entities %}
142
+ ### {{ entity.id }}
143
+
144
+ Attributes:
145
+ {% for attr in entity.attributes %}
146
+ - **{{ attr.id }}**: {{ attr.type }}{% if attr.optional %} (optional){% endif %}
147
+ {% endfor %}
148
+ {% endfor %}
149
+ ----
150
+
151
+ ==== Render Entity List
152
+
153
+ Create `render_entities.rb`:
154
+
155
+ [source,ruby]
156
+ ----
157
+ require 'expressir'
158
+ require 'liquid'
159
+
160
+ repo = Expressir::Express::Parser.from_file('example.exp')
161
+ schema_drop = repo.schemas.first.to_liquid
162
+
163
+ template = Liquid::Template.parse(File.read('list_entities.liquid'))
164
+ output = template.render('schema' => schema_drop)
165
+
166
+ puts output
167
+ ----
168
+
169
+ **Output**:
170
+ [source,markdown]
171
+ ----
172
+ # example_schema
173
+
174
+ ## Entities
175
+
176
+ ### person
177
+
178
+ Attributes:
179
+ - **name**: STRING
180
+ - **age**: INTEGER
181
+
182
+ ### organization
183
+
184
+ Attributes:
185
+ - **org_name**: STRING
186
+ - **employees**: SET [0:?] OF person
187
+ ----
188
+
189
+ === Step 4: Working with Drop Attributes
190
+
191
+ ==== Understanding Drop Attributes
192
+
193
+ Every drop has specific attributes. For `SchemaDrop`:
194
+
195
+ [source,liquid]
196
+ ----
197
+ {{ schema.id }} <!-- Schema name -->
198
+ {{ schema.file }} <!-- Source file -->
199
+ {{ schema.version.value }} <!-- Version string -->
200
+ {{ schema.entities }} <!-- Array of entities -->
201
+ {{ schema.types }} <!-- Array of types -->
202
+ {{ schema.functions }} <!-- Array of functions -->
203
+ {{ schema.interfaces }} <!-- Array of interfaces -->
204
+ ----
205
+
206
+ ==== Entity Drop Attributes
207
+
208
+ For `EntityDrop`:
209
+
210
+ [source,liquid]
211
+ ----
212
+ {{ entity.id }} <!-- Entity name -->
213
+ {{ entity.abstract }} <!-- Is abstract? -->
214
+ {{ entity.attributes }} <!-- Attributes array -->
215
+ {{ entity.where_rules }} <!-- WHERE rules -->
216
+ {{ entity.unique_rules }} <!-- UNIQUE rules -->
217
+ {{ entity.subtype_of }} <!-- Supertypes -->
218
+ {{ entity.remarks }} <!-- Documentation -->
219
+ ----
220
+
221
+ ==== Attribute Drop
222
+
223
+ For `AttributeDrop`:
224
+
225
+ [source,liquid]
226
+ ----
227
+ {{ attr.id }} <!-- Attribute name -->
228
+ {{ attr.type }} <!-- Attribute type -->
229
+ {{ attr.optional }} <!-- Is optional? -->
230
+ {{ attr.kind }} <!-- Kind: explicit/derived/inverse -->
231
+ {{ attr.remarks }} <!-- Documentation -->
232
+ ----
233
+
234
+ === Step 5: Control Structures
235
+
236
+ ==== Conditionals
237
+
238
+ [source,liquid]
239
+ ----
240
+ {% for entity in schema.entities %}
241
+ ## {{ entity.id }}
242
+
243
+ {% if entity.abstract %}
244
+ **Abstract entity** - Cannot be instantiated directly.
245
+ {% endif %}
246
+
247
+ {% if entity.attributes.size > 0 %}
248
+ ### Attributes
249
+ {% for attr in entity.attributes %}
250
+ - {{ attr.id }}: {{ attr.type }}
251
+ {% endfor %}
252
+ {% else %}
253
+ No attributes defined.
254
+ {% endif %}
255
+
256
+ {% if entity.where_rules.size > 0 %}
257
+ ### Constraints
258
+ {% for rule in entity.where_rules %}
259
+ - **{{ rule.id }}**: {{ rule.expression }}
260
+ {% endfor %}
261
+ {% endif %}
262
+ {% endfor %}
263
+ ----
264
+
265
+ ==== Loops with Filters
266
+
267
+ [source,liquid]
268
+ ----
269
+ {% comment %}Sort entities alphabetically{% endcomment %}
270
+ {% assign sorted_entities = schema.entities | sort: 'id' %}
271
+
272
+ {% for entity in sorted_entities %}
273
+ - {{ entity.id }}
274
+ {% endfor %}
275
+
276
+ {% comment %}Filter optional attributes{% endcomment %}
277
+ {% for entity in schema.entities %}
278
+ Optional attributes in {{ entity.id }}:
279
+ {% for attr in entity.attributes %}
280
+ {% if attr.optional %}
281
+ - {{ attr.id }}
282
+ {% endif %}
283
+ {% endfor %}
284
+ {% endfor %}
285
+ ----
286
+
287
+ === Step 6: Using Liquid Filters
288
+
289
+ ==== Built-in Filters
290
+
291
+ [source,liquid]
292
+ ----
293
+ {% comment %}String filters{% endcomment %}
294
+ {{ entity.id | upcase }}
295
+ {{ entity.id | capitalize }}
296
+ {{ entity.id | replace: '_', ' ' }}
297
+
298
+ {% comment %}Array filters{% endcomment %}
299
+ {{ schema.entities | size }}
300
+ {{ schema.entities | map: 'id' | join: ', ' }}
301
+ {{ entity.attributes | first }}
302
+ {{ entity.attributes | last }}
303
+
304
+ {% comment %}Conditional filters{% endcomment %}
305
+ {{ attr.optional | default: false }}
306
+ ----
307
+
308
+ ==== Custom Filters
309
+
310
+ Create `render_with_filters.rb`:
311
+
312
+ [source,ruby]
313
+ ----
314
+ require 'expressir'
315
+ require 'liquid'
316
+
317
+ # Define custom filter
318
+ module CustomFilters
319
+ def type_category(type_obj)
320
+ case type_obj._class
321
+ when /Entity/
322
+ 'entity'
323
+ when /Aggregate/
324
+ 'aggregate'
325
+ when /Select/
326
+ 'select'
327
+ else
328
+ 'simple'
329
+ end
330
+ end
331
+
332
+ def format_path(obj)
333
+ if obj.respond_to?(:parent) && obj.parent
334
+ "#{obj.parent.id}.#{obj.id}"
335
+ else
336
+ obj.id
337
+ end
338
+ end
339
+ end
340
+
341
+ Liquid::Template.register_filter(CustomFilters)
342
+
343
+ # Use in template
344
+ template_text = <<~LIQUID
345
+ {% for entity in entities %}
346
+ {{ entity | format_path }}: {{ entity.attributes.size }} attributes
347
+ {% endfor %}
348
+ LIQUID
349
+
350
+ repo = Expressir::Express::Parser.from_file('example.exp')
351
+ schema_drop = repo.schemas.first.to_liquid
352
+
353
+ template = Liquid::Template.parse(template_text)
354
+ output = template.render('entities' => schema_drop.entities)
355
+ puts output
356
+ ----
357
+
358
+ === Step 7: Complete Documentation Templates
359
+
360
+ ==== Entity Documentation
361
+
362
+ Create `entity_doc.liquid`:
363
+
364
+ [source,liquid]
365
+ ----
366
+ ---
367
+ title: {{ entity.id }}
368
+ ---
369
+
370
+ # {{ entity.id }}
371
+
372
+ {% if entity.remarks.size > 0 %}
373
+ ## Description
374
+
375
+ {% for remark in entity.remarks %}
376
+ {{ remark }}
377
+ {% endfor %}
378
+ {% endif %}
379
+
380
+ ## Definition
381
+
382
+ ```express
383
+ ENTITY {{ entity.id }}{% if entity.abstract %} ABSTRACT{% endif %};
384
+ {% for attr in entity.attributes %}
385
+ {{ attr.id }} : {% if attr.optional %}OPTIONAL {% endif %}{{ attr.type }};
386
+ {% endfor %}
387
+ {% if entity.where_rules.size > 0 %}
388
+ WHERE
389
+ {% for rule in entity.where_rules %}
390
+ {{ rule.id }}: {{ rule.expression }};
391
+ {% endfor %}
392
+ {% endif %}
393
+ END_ENTITY;
394
+ ```
395
+
396
+ ## Attributes
397
+
398
+ | Name | Type | Optional | Description |
399
+ |------|------|----------|-------------|
400
+ {% for attr in entity.attributes %}
401
+ | {{ attr.id }} | {{ attr.type }} | {{ attr.optional }} | {% if attr.remarks.size > 0 %}{{ attr.remarks | join: ' ' }}{% else %}-{% endif %} |
402
+ {% endfor %}
403
+
404
+ {% if entity.subtype_of.size > 0 %}
405
+ ## Supertypes
406
+
407
+ {% for super in entity.subtype_of %}
408
+ - {{ super }}
409
+ {% endfor %}
410
+ {% endif %}
411
+
412
+ {% if entity.where_rules.size > 0 %}
413
+ ## Constraints
414
+
415
+ {% for rule in entity.where_rules %}
416
+ ### {{ rule.id }}
417
+
418
+ {{ rule.expression }}
419
+
420
+ {% if rule.remarks.size > 0 %}
421
+ {{ rule.remarks | join: "\n" }}
422
+ {% endif %}
423
+ {% endfor %}
424
+ {% endif %}
425
+ ----
426
+
427
+ ==== Schema Overview
428
+
429
+ Create `schema_overview.liquid`:
430
+
431
+ [source,liquid]
432
+ ----
433
+ # {{ schema.id }}
434
+
435
+ {% if schema.version %}
436
+ **Version**: {{ schema.version.value }}
437
+ {% endif %}
438
+
439
+ {% if schema.remarks.size > 0 %}
440
+ ## Description
441
+
442
+ {% for remark in schema.remarks %}
443
+ {{ remark }}
444
+ {% endfor %}
445
+ {% endif %}
446
+
447
+ ## Contents
448
+
449
+ - **Entities**: {{ schema.entities.size }}
450
+ - **Types**: {{ schema.types.size }}
451
+ - **Functions**: {{ schema.functions.size }}
452
+ - **Rules**: {{ schema.rules.size }}
453
+
454
+ ## Entities
455
+
456
+ {% for entity in schema.entities %}
457
+ ### [{{ entity.id }}](#{{ entity.id | downcase }})
458
+
459
+ {% if entity.remarks.size > 0 %}
460
+ {{ entity.remarks | first }}
461
+ {% else %}
462
+ Entity with {{ entity.attributes.size }} attributes.
463
+ {% endif %}
464
+ {% endfor %}
465
+
466
+ ## Types
467
+
468
+ {% for type in schema.types %}
469
+ ### {{ type.id }}
470
+
471
+ Type: {{ type.underlying_type._class }}
472
+
473
+ {% if type.remarks.size > 0 %}
474
+ {{ type.remarks | join: ' ' }}
475
+ {% endif %}
476
+ {% endfor %}
477
+
478
+ {% if schema.interfaces.size > 0 %}
479
+ ## Dependencies
480
+
481
+ {% for interface in schema.interfaces %}
482
+ - **{{ interface.kind | upcase }}** from {{ interface.schema.id }}
483
+ {% if interface.items.size > 0 %}
484
+ {% for item in interface.items %}
485
+ - {{ item.id }}
486
+ {% endfor %}
487
+ {% endif %}
488
+ {% endfor %}
489
+ {% endif %}
490
+ ----
491
+
492
+ === Step 8: Multiple Schema Documentation
493
+
494
+ ==== Repository Documentation Generator
495
+
496
+ Create `doc_generator.rb`:
497
+
498
+ [source,ruby]
499
+ ----
500
+ require 'expressir'
501
+ require 'liquid'
502
+ require 'fileutils'
503
+
504
+ class DocumentationGenerator
505
+ def initialize(repo_or_package)
506
+ if repo_or_package.is_a?(String)
507
+ @repo = Expressir::Model::Repository.from_package(repo_or_package)
508
+ else
509
+ @repo = repo_or_package
510
+ end
511
+ @repo_drop = @repo.to_liquid
512
+ end
513
+
514
+ def generate_all(output_dir)
515
+ FileUtils.mkdir_p(output_dir)
516
+
517
+ # Generate index
518
+ generate_index(output_dir)
519
+
520
+ # Generate per-schema docs
521
+ @repo_drop.schemas.each do |schema|
522
+ generate_schema_doc(schema, output_dir)
523
+
524
+ # Generate per-entity docs
525
+ schema.entities.each do |entity|
526
+ generate_entity_doc(schema, entity, output_dir)
527
+ end
528
+ end
529
+
530
+ puts "Documentation generated in #{output_dir}/"
531
+ end
532
+
533
+ private
534
+
535
+ def generate_index(output_dir)
536
+ template = Liquid::Template.parse(index_template)
537
+ output = template.render('schemas' => @repo_drop.schemas)
538
+
539
+ File.write("#{output_dir}/index.md", output)
540
+ puts " ✓ index.md"
541
+ end
542
+
543
+ def generate_schema_doc(schema, output_dir)
544
+ template = Liquid::Template.parse(schema_template)
545
+ output = template.render('schema' => schema)
546
+
547
+ File.write("#{output_dir}/#{schema.id}.md", output)
548
+ puts " ✓ #{schema.id}.md"
549
+ end
550
+
551
+ def generate_entity_doc(schema, entity, output_dir)
552
+ entity_dir = "#{output_dir}/entities"
553
+ FileUtils.mkdir_p(entity_dir)
554
+
555
+ template = Liquid::Template.parse(entity_template)
556
+ output = template.render('entity' => entity, 'schema' => schema)
557
+
558
+ File.write("#{entity_dir}/#{entity.id}.md", output)
559
+ puts " ✓ entities/#{entity.id}.md"
560
+ end
561
+
562
+ def index_template
563
+ File.read('templates/index.liquid')
564
+ end
565
+
566
+ def schema_template
567
+ File.read('templates/schema.liquid')
568
+ end
569
+
570
+ def entity_template
571
+ File.read('templates/entity.liquid')
572
+ end
573
+ end
574
+
575
+ # Usage
576
+ repo = Expressir::Express::Parser.from_file('example.exp')
577
+ generator = DocumentationGenerator.new(repo)
578
+ generator.generate_all('docs_output')
579
+ ----
580
+
581
+ ==== Create Template Files
582
+
583
+ Create `templates/index.liquid`:
584
+
585
+ [source,liquid]
586
+ ----
587
+ # EXPRESS Schema Documentation
588
+
589
+ ## Schemas
590
+
591
+ {% for schema in schemas %}
592
+ - [{{ schema.id }}]({{ schema.id }}.md) - {{ schema.entities.size }} entities, {{ schema.types.size }} types
593
+ {% endfor %}
594
+
595
+ ## Quick Statistics
596
+
597
+ - Total Schemas: {{ schemas.size }}
598
+ - Total Entities: {% assign total = 0 %}{% for s in schemas %}{% assign total = total | plus: s.entities.size %}{% endfor %}{{ total }}
599
+ - Total Types: {% assign total = 0 %}{% for s in schemas %}{% assign total = total | plus: s.types.size %}{% endfor %}{{ total }}
600
+ ----
601
+
602
+ === Step 9: Advanced Templates
603
+
604
+ ==== Inheritance Hierarchy
605
+
606
+ Create `hierarchy.liquid`:
607
+
608
+ [source,liquid]
609
+ ----
610
+ # Entity Inheritance Hierarchy
611
+
612
+ {% for entity in schema.entities %}
613
+ {% if entity.subtype_of.size == 0 %}
614
+ ## {{ entity.id }} (Root)
615
+
616
+ {% assign children = schema.entities | where: 'subtype_of', entity.id %}
617
+ {% if children.size > 0 %}
618
+ ### Subtypes:
619
+ {% for child in children %}
620
+ - {{ child.id }}
621
+ {% endfor %}
622
+ {% endif %}
623
+ {% endif %}
624
+ {% endfor %}
625
+
626
+ ## All Entities
627
+
628
+ {% for entity in schema.entities %}
629
+ - **{{ entity.id }}**
630
+ {% if entity.subtype_of.size > 0 %}
631
+ - Extends: {{ entity.subtype_of | join: ', ' }}
632
+ {% endif %}
633
+ {% endfor %}
634
+ ----
635
+
636
+ ==== Cross-Reference Table
637
+
638
+ Create `cross_reference.liquid`:
639
+
640
+ [source,liquid]
641
+ ----
642
+ # Cross-Reference Table
643
+
644
+ ## Entities by Schema
645
+
646
+ | Schema | Entity | Attributes |
647
+ |--------|--------|------------|
648
+ {% for schema in schemas %}
649
+ {% for entity in schema.entities %}
650
+ | {{ schema.id }} | {{ entity.id }} | {{ entity.attributes.size }} |
651
+ {% endfor %}
652
+ {% endfor %}
653
+
654
+ ## Type Usage
655
+
656
+ {% for schema in schemas %}
657
+ ### {{ schema.id }}
658
+
659
+ | Type | Used By |
660
+ |------|---------|
661
+ {% for type in schema.types %}
662
+ | {{ type.id }} | {% comment %}Find usage{% endcomment %}- |
663
+ {% endfor %}
664
+ {% endfor %}
665
+ ----
666
+
667
+ === Step 10: Practice Exercises
668
+
669
+ ==== Exercise 1: HTML Documentation
670
+
671
+ Create an HTML template that generates a single-page documentation with:
672
+ * Table of contents
673
+ * Entity cards with styling
674
+ * Syntax highlighting for EXPRESS code
675
+ * Responsive layout
676
+
677
+ ==== Exercise 2: JSON Schema
678
+
679
+ Create a template that generates JSON Schema from EXPRESS entities:
680
+
681
+ [source,liquid]
682
+ ----
683
+ {
684
+ "$schema": "http://json-schema.org/draft-07/schema#",
685
+ "type": "object",
686
+ "properties": {
687
+ {% for attr in entity.attributes %}
688
+ "{{ attr.id }}": {
689
+ "type": "string"
690
+ }{% unless forloop.last %},{% endunless %}
691
+ {% endfor %}
692
+ }
693
+ }
694
+ ----
695
+
696
+ ==== Exercise 3: Dependency Graph
697
+
698
+ Generate a DOT file for GraphViz showing schema dependencies:
699
+
700
+ [source,liquid]
701
+ ----
702
+ digraph schemas {
703
+ {% for schema in schemas %}
704
+ "{{ schema.id }}";
705
+ {% for interface in schema.interfaces %}
706
+ "{{ schema.id }}" -> "{{ interface.schema.id }}";
707
+ {% endfor %}
708
+ {% endfor %}
709
+ }
710
+ ----
711
+
712
+ === Best Practices
713
+
714
+ **Template Organization**::
715
+ * Keep templates in a dedicated directory
716
+ * Use includes for reusable parts
717
+ * Name templates descriptively
718
+
719
+ **Error Handling**::
720
+ * Check for nil/empty before iterating
721
+ * Provide defaults for optional values
722
+ * Test templates with various schemas
723
+
724
+ **Performance**::
725
+ * Cache compiled templates
726
+ * Use filters efficiently
727
+ * Avoid complex logic in templates
728
+
729
+ **Maintainability**::
730
+ * Comment complex template logic
731
+ * Use consistent formatting
732
+ * Version control your templates
733
+
734
+ === Common Pitfalls
735
+
736
+ ==== Nil Checks
737
+
738
+ [source,liquid]
739
+ ----
740
+ {% comment %}❌ Wrong - may crash{% endcomment %}
741
+ {% for item in items %}
742
+ {{ item.value }}
743
+ {% endfor %}
744
+
745
+ {% comment %}✅ Correct - check first{% endcomment %}
746
+ {% if items and items.size > 0 %}
747
+ {% for item in items %}
748
+ {{ item.value }}
749
+ {% endfor %}
750
+ {% endif %}
751
+ ----
752
+
753
+ ==== Accessing Parent Objects
754
+
755
+ [source,liquid]
756
+ ----
757
+ {% comment %}❌ Wrong - parent may not exist{% endcomment %}
758
+ {{ entity.parent.id }}
759
+
760
+ {% comment %}✅ Correct - check parent{% endcomment %}
761
+ {% if entity.parent %}
762
+ Schema: {{ entity.parent.id }}
763
+ {% endif %}
764
+ ----
765
+
766
+ ==== Type Checking
767
+
768
+ [source,liquid]
769
+ ----
770
+ {% comment %}Use _class to check type{% endcomment %}
771
+ {% if object._class contains 'Entity' %}
772
+ This is an entity
773
+ {% elsif object._class contains 'Type' %}
774
+ This is a type
775
+ {% endif %}
776
+ ----
777
+
778
+ === Next Steps
779
+
780
+ Congratulations! You can now generate documentation with Liquid templates.
781
+
782
+ **Continue learning**:
783
+
784
+ * link:documentation-coverage.html[Documentation Coverage] - Analyze documentation quality
785
+ * link:../guides/liquid/[Liquid Guides] - Advanced template techniques
786
+ * link:../references/liquid-drops/[Liquid Drops Reference] - Complete API
787
+
788
+ **Read more**:
789
+
790
+ * link:../pages/data-model.html[Data Model] - Understanding the structure
791
+ * https://shopify.github.io/liquid/[Liquid Documentation] - Learn more about Liquid
792
+
793
+ === Summary
794
+
795
+ In this tutorial, you learned to:
796
+
797
+ * ✅ Convert Expressir models to Liquid drops
798
+ * ✅ Create basic and advanced templates
799
+ * ✅ Access schema elements in templates
800
+ * ✅ Use built-in and custom filters
801
+ * ✅ Generate entity documentation
802
+ * ✅ Create complete documentation generators
803
+ * ✅ Work with multiple schemas
804
+ * ✅ Handle edge cases safely
805
+
806
+ You're now ready to automate EXPRESS schema documentation generation!