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,1042 @@
1
+ ---
2
+ title: Documentation Generation
3
+ parent: Liquid
4
+ grand_parent: Guides
5
+ nav_order: 4
6
+ ---
7
+
8
+ = Documentation generation with Liquid
9
+
10
+ == Purpose
11
+
12
+ This guide demonstrates complete documentation generation workflows
13
+ using Liquid templates and Expressir. Learn how to build sophisticated
14
+ documentation systems that transform EXPRESS schemas into professional,
15
+ maintainable documentation in various formats.
16
+
17
+ == References
18
+
19
+ * link:basic-templates.html[Basic Templates] - Template fundamentals
20
+ * link:drops-reference.html[Drops Reference] - Available data
21
+ * link:filters-and-tags.html[Filters and Tags] - Transform operations
22
+ * link:../../_tutorials/liquid-templates.html[Liquid Templates Tutorial]
23
+ - Hands-on practice
24
+
25
+ == Concepts
26
+
27
+ Documentation generator:: A Ruby script that parses schemas, applies
28
+ templates, and produces documentation files.
29
+
30
+ Template organization:: A structured approach to organizing templates
31
+ for maintainability and reuse.
32
+
33
+ Multi-file output:: Generating multiple documentation files from a
34
+ single schema or repository.
35
+
36
+ Template inheritance:: Reusing common template elements across
37
+ different documentation pages.
38
+
39
+ Output format:: The target format for documentation (HTML, Markdown,
40
+ AsciiDoc, LaTeX, etc.).
41
+
42
+ == Documentation project structure
43
+
44
+ Organize your documentation project with clear separation:
45
+
46
+ [source]
47
+ ----
48
+ documentation/
49
+ ├── templates/ # Template files
50
+ │ ├── index.liquid # Repository overview
51
+ │ ├── schema.liquid # Schema documentation
52
+ │ ├── entity.liquid # Entity reference
53
+ │ ├── type.liquid # Type reference
54
+ │ └── partials/ # Reusable components
55
+ │ ├── header.liquid
56
+ │ ├── footer.liquid
57
+ │ └── toc.liquid
58
+ ├── schemas/ # EXPRESS source files
59
+ │ ├── action_schema.exp
60
+ │ └── support_schema.exp
61
+ ├── output/ # Generated documentation
62
+ │ ├── index.md
63
+ │ ├── schemas/
64
+ │ └── entities/
65
+ ├── generate.rb # Generator script
66
+ └── config.yml # Configuration
67
+ ----
68
+
69
+ == Basic documentation generator
70
+
71
+ === Simple generator script
72
+
73
+ .generate.rb
74
+ [source,ruby]
75
+ ----
76
+ require "expressir"
77
+ require "liquid"
78
+ require "fileutils"
79
+
80
+ class DocumentationGenerator
81
+ def initialize(schema_files, template_dir, output_dir)
82
+ @schema_files = schema_files
83
+ @template_dir = template_dir
84
+ @output_dir = output_dir
85
+ @templates = {}
86
+ end
87
+
88
+ def generate
89
+ # Parse schemas
90
+ puts "Parsing schemas..."
91
+ repo = Expressir::Express::Parser.from_files(@schema_files)
92
+ repo_drop = repo.to_liquid
93
+
94
+ # Create output directory
95
+ FileUtils.mkdir_p(@output_dir)
96
+
97
+ # Generate documentation
98
+ generate_index(repo_drop)
99
+ generate_schemas(repo_drop)
100
+
101
+ puts "Documentation generated in #{@output_dir}"
102
+ end
103
+
104
+ private
105
+
106
+ def load_template(name)
107
+ @templates[name] ||= begin
108
+ path = File.join(@template_dir, "#{name}.liquid")
109
+ Liquid::Template.parse(File.read(path))
110
+ end
111
+ end
112
+
113
+ def generate_index(repo_drop)
114
+ puts "Generating index..."
115
+ template = load_template("index")
116
+ output = template.render("repository" => repo_drop)
117
+ File.write(File.join(@output_dir, "index.md"), output)
118
+ end
119
+
120
+ def generate_schemas(repo_drop)
121
+ repo_drop.schemas.each do |schema|
122
+ generate_schema(schema)
123
+ end
124
+ end
125
+
126
+ def generate_schema(schema)
127
+ puts "Generating #{schema.id}..."
128
+ template = load_template("schema")
129
+ output = template.render("schema" => schema)
130
+
131
+ schema_dir = File.join(@output_dir, "schemas")
132
+ FileUtils.mkdir_p(schema_dir)
133
+ File.write(File.join(schema_dir, "#{schema.id}.md"), output)
134
+ end
135
+ end
136
+
137
+ # Usage
138
+ generator = DocumentationGenerator.new(
139
+ ["schemas/action_schema.exp", "schemas/support_schema.exp"],
140
+ "templates",
141
+ "output"
142
+ )
143
+ generator.generate
144
+ ----
145
+
146
+ === Index template
147
+
148
+ .templates/index.liquid
149
+ [source,liquid]
150
+ ----
151
+ # EXPRESS Schema Documentation
152
+
153
+ Generated: {{ "now" | date: "%Y-%m-%d %H:%M" }}
154
+
155
+ ## Overview
156
+
157
+ This documentation covers {{ repository.schemas.size }} EXPRESS schemas.
158
+
159
+ ## Schemas
160
+
161
+ {% for schema in repository.schemas %}
162
+ ### [{{ schema.id }}](schemas/{{ schema.id }}.md)
163
+
164
+ {% if schema.version %}
165
+ **Version**: {{ schema.version.value }}
166
+ {% endif %}
167
+
168
+ {% if schema.remarks.size > 0 %}
169
+ {{ schema.remarks | join: " " }}
170
+ {% endif %}
171
+
172
+ **Contents**:
173
+ - Entities: {{ schema.entities.size }}
174
+ - Types: {{ schema.types.size }}
175
+ - Functions: {{ schema.functions.size }}
176
+
177
+ {% endfor %}
178
+
179
+ ## Statistics
180
+
181
+ - **Total Schemas**: {{ repository.schemas.size }}
182
+ - **Total Entities**: {% assign total = 0 %}{% for s in repository.schemas %}{% assign total = total | plus: s.entities.size %}{% endfor %}{{ total }}
183
+ - **Total Types**: {% assign total = 0 %}{% for s in repository.schemas %}{% assign total = total | plus: s.types.size %}{% endfor %}{{ total }}
184
+ ----
185
+
186
+ === Schema template
187
+
188
+ .templates/schema.liquid
189
+ [source,liquid]
190
+ ----
191
+ # {{ schema.id }}
192
+
193
+ {% if schema.version %}
194
+ **Version**: {{ schema.version.value }}
195
+ {% endif %}
196
+
197
+ **File**: `{{ schema.file }}`
198
+
199
+ {% if schema.remarks.size > 0 %}
200
+ ## Description
201
+
202
+ {% for remark in schema.remarks %}
203
+ {{ remark }}
204
+ {% endfor %}
205
+ {% endif %}
206
+
207
+ ## Contents
208
+
209
+ - [Entities](#entities) ({{ schema.entities.size }})
210
+ - [Types](#types) ({{ schema.types.size }})
211
+ - [Functions](#functions) ({{ schema.functions.size }})
212
+
213
+ {% if schema.interfaces.size > 0 %}
214
+ ## Dependencies
215
+
216
+ {% for interface in schema.interfaces %}
217
+ **{{ interface.kind | upcase }} FROM** `{{ interface.schema.id }}`
218
+ {% if interface.items.size > 0 %}
219
+ - Items: {{ interface.items | map: "id" | join: ", " }}
220
+ {% endif %}
221
+ {% endfor %}
222
+ {% endif %}
223
+
224
+ ## Entities
225
+
226
+ {% for entity in schema.entities | sort: "id" %}
227
+ ### {{ entity.id }}
228
+
229
+ {% if entity.abstract %}
230
+ _Abstract entity_
231
+ {% endif %}
232
+
233
+ {% if entity.remarks.size > 0 %}
234
+ {{ entity.remarks | join: " " }}
235
+ {% endif %}
236
+
237
+ **Attributes** ({{ entity.attributes.size }}):
238
+ {% for attr in entity.attributes %}
239
+ - `{{ attr.id }}`: {{ attr.type }}{% if attr.optional %} (optional){% endif %}
240
+ {% endfor %}
241
+
242
+ {% if entity.where_rules.size > 0 %}
243
+ **Constraints** ({{ entity.where_rules.size }}):
244
+ {% for rule in entity.where_rules %}
245
+ - **{{ rule.id }}**: {{ rule.expression }}
246
+ {% endfor %}
247
+ {% endif %}
248
+
249
+ ---
250
+ {% endfor %}
251
+
252
+ ## Types
253
+
254
+ {% for type in schema.types | sort: "id" %}
255
+ ### {{ type.id }}
256
+
257
+ **Base type**: {{ type.underlying_type._class }}
258
+
259
+ {% if type.remarks.size > 0 %}
260
+ {{ type.remarks | join: " " }}
261
+ {% endif %}
262
+ {% endfor %}
263
+
264
+ ## Functions
265
+
266
+ {% for func in schema.functions | sort: "id" %}
267
+ ### {{ func.id }}
268
+
269
+ **Returns**: {{ func.return_type }}
270
+
271
+ {% if func.parameters.size > 0 %}
272
+ **Parameters**:
273
+ {% for param in func.parameters %}
274
+ - `{{ param.id }}`: {{ param.type }}
275
+ {% endfor %}
276
+ {% endif %}
277
+
278
+ {% if func.remarks.size > 0 %}
279
+ {{ func.remarks | join: " " }}
280
+ {% endif %}
281
+ {% endfor %}
282
+ ----
283
+
284
+ == Advanced generator with multiple outputs
285
+
286
+ === Enhanced generator
287
+
288
+ .generate_advanced.rb
289
+ [source,ruby]
290
+ ----
291
+ require "expressir"
292
+ require "liquid"
293
+ require "fileutils"
294
+ require "yaml"
295
+
296
+ class AdvancedDocumentationGenerator
297
+ def initialize(config_file)
298
+ @config = YAML.load_file(config_file)
299
+ @templates = {}
300
+ setup_directories
301
+ end
302
+
303
+ def generate
304
+ puts "Loading schemas..."
305
+ repo = load_repository
306
+ repo_drop = repo.to_liquid
307
+
308
+ puts "Generating documentation..."
309
+ generate_index(repo_drop)
310
+ generate_schemas(repo_drop)
311
+ generate_entities(repo_drop)
312
+ generate_types(repo_drop)
313
+ generate_cross_references(repo_drop)
314
+
315
+ puts "\nDocumentation generated successfully!"
316
+ print_summary(repo_drop)
317
+ end
318
+
319
+ private
320
+
321
+ def setup_directories
322
+ [@config["output_dir"],
323
+ "#{@config['output_dir']}/schemas",
324
+ "#{@config['output_dir']}/entities",
325
+ "#{@config['output_dir']}/types"].each do |dir|
326
+ FileUtils.mkdir_p(dir)
327
+ end
328
+ end
329
+
330
+ def load_repository
331
+ Expressir::Express::Parser.from_files(@config["schema_files"])
332
+ end
333
+
334
+ def load_template(name)
335
+ @templates[name] ||= begin
336
+ path = File.join(@config["template_dir"], "#{name}.liquid")
337
+ Liquid::Template.parse(File.read(path))
338
+ end
339
+ end
340
+
341
+ def generate_index(repo_drop)
342
+ puts " → index.md"
343
+ template = load_template("index")
344
+ output = template.render("repository" => repo_drop)
345
+ write_file("index.md", output)
346
+ end
347
+
348
+ def generate_schemas(repo_drop)
349
+ repo_drop.schemas.each do |schema|
350
+ puts " → schemas/#{schema.id}.md"
351
+ template = load_template("schema")
352
+ output = template.render("schema" => schema)
353
+ write_file("schemas/#{schema.id}.md", output)
354
+ end
355
+ end
356
+
357
+ def generate_entities(repo_drop)
358
+ repo_drop.schemas.each do |schema|
359
+ schema.entities.each do |entity|
360
+ puts " → entities/#{entity.id}.md"
361
+ template = load_template("entity")
362
+ output = template.render(
363
+ "entity" => entity,
364
+ "schema" => schema
365
+ )
366
+ write_file("entities/#{entity.id}.md", output)
367
+ end
368
+ end
369
+ end
370
+
371
+ def generate_types(repo_drop)
372
+ repo_drop.schemas.each do |schema|
373
+ schema.types.each do |type|
374
+ puts " → types/#{type.id}.md"
375
+ template = load_template("type")
376
+ output = template.render(
377
+ "type" => type,
378
+ "schema" => schema
379
+ )
380
+ write_file("types/#{type.id}.md", output)
381
+ end
382
+ end
383
+ end
384
+
385
+ def generate_cross_references(repo_drop)
386
+ puts " → cross-references.md"
387
+ template = load_template("cross_references")
388
+ output = template.render("repository" => repo_drop)
389
+ write_file("cross-references.md", output)
390
+ end
391
+
392
+ def write_file(path, content)
393
+ File.write(File.join(@config["output_dir"], path), content)
394
+ end
395
+
396
+ def print_summary(repo_drop)
397
+ total_entities = repo_drop.schemas.sum { |s| s.entities.size }
398
+ total_types = repo_drop.schemas.sum { |s| s.types.size }
399
+
400
+ puts "\nSummary:"
401
+ puts " Schemas: #{repo_drop.schemas.size}"
402
+ puts " Entities: #{total_entities}"
403
+ puts " Types: #{total_types}"
404
+ puts "\nOutput directory: #{@config['output_dir']}"
405
+ end
406
+ end
407
+
408
+ # Configuration file
409
+ config = {
410
+ "schema_files" => Dir["schemas/**/*.exp"],
411
+ "template_dir" => "templates",
412
+ "output_dir" => "output"
413
+ }
414
+ File.write("config.yml", config.to_yaml)
415
+
416
+ # Usage
417
+ generator = AdvancedDocumentationGenerator.new("config.yml")
418
+ generator.generate
419
+ ----
420
+
421
+ === Entity detail template
422
+
423
+ .templates/entity.liquid
424
+ [source,liquid]
425
+ ----
426
+ ---
427
+ title: {{ entity.id }}
428
+ schema: {{ schema.id }}
429
+ type: entity
430
+ ---
431
+
432
+ # {{ entity.id }}
433
+
434
+ **Schema**: [{{ schema.id }}](../schemas/{{ schema.id }}.md)
435
+
436
+ {% if entity.abstract %}
437
+ > **Abstract Entity** - Cannot be instantiated directly
438
+ {% endif %}
439
+
440
+ {% if entity.remarks.size > 0 %}
441
+ ## Description
442
+
443
+ {% for remark in entity.remarks %}
444
+ {{ remark }}
445
+ {% endfor %}
446
+ {% endif %}
447
+
448
+ ## Definition
449
+
450
+ ```express
451
+ ENTITY {{ entity.id }}{% if entity.abstract %} ABSTRACT{% endif %};
452
+ {% for attr in entity.attributes %}
453
+ {{ attr.id }} : {% if attr.optional %}OPTIONAL {% endif %}{{ attr.type }};
454
+ {% endfor %}
455
+ {% if entity.where_rules.size > 0 %}
456
+ WHERE
457
+ {% for rule in entity.where_rules %}
458
+ {{ rule.id }}: {{ rule.expression }};
459
+ {% endfor %}
460
+ {% endif %}
461
+ END_ENTITY;
462
+ ```
463
+
464
+ ## Attributes
465
+
466
+ | Name | Type | Optional | Description |
467
+ |------|------|----------|-------------|
468
+ {% for attr in entity.attributes %}
469
+ | `{{ attr.id }}` | {{ attr.type }} | {{ attr.optional }} | {% if attr.remarks.size > 0 %}{{ attr.remarks | join: " " }}{% else %}-{% endif %} |
470
+ {% endfor %}
471
+
472
+ {% if entity.subtype_of.size > 0 %}
473
+ ## Supertypes
474
+
475
+ {% for super in entity.subtype_of %}
476
+ - {{ super }}
477
+ {% endfor %}
478
+ {% endif %}
479
+
480
+ {% if entity.where_rules.size > 0 %}
481
+ ## Constraints
482
+
483
+ {% for rule in entity.where_rules %}
484
+ ### {{ rule.id }}
485
+
486
+ ```express
487
+ {{ rule.expression }}
488
+ ```
489
+
490
+ {% if rule.remarks.size > 0 %}
491
+ {{ rule.remarks | join: "\n" }}
492
+ {% endif %}
493
+ {% endfor %}
494
+ {% endif %}
495
+
496
+ {% if entity.unique_rules.size > 0 %}
497
+ ## Unique Rules
498
+
499
+ {% for rule in entity.unique_rules %}
500
+ ### {{ rule.id }}
501
+
502
+ Attributes: {{ rule.attributes | map: "id" | join: ", " }}
503
+
504
+ {% if rule.remarks.size > 0 %}
505
+ {{ rule.remarks | join: "\n" }}
506
+ {% endif %}
507
+ {% endfor %}
508
+ {% endif %}
509
+
510
+ ---
511
+
512
+ [Back to {{ schema.id }}](../schemas/{{ schema.id }}.md) |
513
+ [All Entities](../index.md#entities)
514
+ ----
515
+
516
+ === Cross-reference template
517
+
518
+ .templates/cross_references.liquid
519
+ [source,liquid]
520
+ ----
521
+ # Cross-Reference Tables
522
+
523
+ ## All Entities by Schema
524
+
525
+ | Schema | Entity | Attributes | Constraints |
526
+ |--------|--------|------------|-------------|
527
+ {% for schema in repository.schemas | sort: "id" %}
528
+ {% for entity in schema.entities | sort: "id" %}
529
+ | [{{ schema.id }}](schemas/{{ schema.id }}.md) | [{{ entity.id }}](entities/{{ entity.id }}.md) | {{ entity.attributes.size }} | {{ entity.where_rules.size }} |
530
+ {% endfor %}
531
+ {% endfor %}
532
+
533
+ ## All Types by Schema
534
+
535
+ | Schema | Type | Category |
536
+ |--------|------|----------|
537
+ {% for schema in repository.schemas | sort: "id" %}
538
+ {% for type in schema.types | sort: "id" %}
539
+ | [{{ schema.id }}](schemas/{{ schema.id }}.md) | [{{ type.id }}](types/{{ type.id }}.md) | {{ type.underlying_type._class | split: "::" | last }} |
540
+ {% endfor %}
541
+ {% endfor %}
542
+
543
+ ## Schema Dependencies
544
+
545
+ {% for schema in repository.schemas | sort: "id" %}
546
+ {% if schema.interfaces.size > 0 %}
547
+ ### {{ schema.id }}
548
+
549
+ {% for interface in schema.interfaces %}
550
+ - **{{ interface.kind | upcase }} FROM** {{ interface.schema.id }}
551
+ {% if interface.items.size > 0 %}
552
+ - Items: {{ interface.items | map: "id" | join: ", " }}
553
+ {% endif %}
554
+ {% endfor %}
555
+ {% endif %}
556
+ {% endfor %}
557
+ ----
558
+
559
+ == HTML documentation generation
560
+
561
+ === HTML template
562
+
563
+ .templates/html_entity.liquid
564
+ [source,liquid]
565
+ ----
566
+ <!DOCTYPE html>
567
+ <html lang="en">
568
+ <head>
569
+ <meta charset="UTF-8">
570
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
571
+ <title>{{ entity.id }} - {{ schema.id }}</title>
572
+ <style>
573
+ body {
574
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
575
+ max-width: 900px;
576
+ margin: 0 auto;
577
+ padding: 2rem;
578
+ line-height: 1.6;
579
+ }
580
+ .header {
581
+ border-bottom: 2px solid #333;
582
+ padding-bottom: 1rem;
583
+ margin-bottom: 2rem;
584
+ }
585
+ .meta {
586
+ color: #666;
587
+ font-size: 0.9rem;
588
+ }
589
+ .abstract-badge {
590
+ background: #f0ad4e;
591
+ color: white;
592
+ padding: 0.25rem 0.5rem;
593
+ border-radius: 3px;
594
+ font-size: 0.8rem;
595
+ }
596
+ table {
597
+ width: 100%;
598
+ border-collapse: collapse;
599
+ margin: 1rem 0;
600
+ }
601
+ th, td {
602
+ border: 1px solid #ddd;
603
+ padding: 0.75rem;
604
+ text-align: left;
605
+ }
606
+ th {
607
+ background: #f5f5f5;
608
+ font-weight: 600;
609
+ }
610
+ code {
611
+ background: #f5f5f5;
612
+ padding: 0.2rem 0.4rem;
613
+ border-radius: 3px;
614
+ font-family: monospace;
615
+ }
616
+ pre {
617
+ background: #f5f5f5;
618
+ padding: 1rem;
619
+ border-radius: 5px;
620
+ overflow-x: auto;
621
+ }
622
+ </style>
623
+ </head>
624
+ <body>
625
+ <div class="header">
626
+ <h1>{{ entity.id }}</h1>
627
+ <div class="meta">
628
+ Schema: <a href="../schemas/{{ schema.id }}.html">{{ schema.id }}</a>
629
+ {% if entity.abstract %}
630
+ <span class="abstract-badge">ABSTRACT</span>
631
+ {% endif %}
632
+ </div>
633
+ </div>
634
+
635
+ {% if entity.remarks.size > 0 %}
636
+ <section>
637
+ <h2>Description</h2>
638
+ {% for remark in entity.remarks %}
639
+ <p>{{ remark }}</p>
640
+ {% endfor %}
641
+ </section>
642
+ {% endif %}
643
+
644
+ <section>
645
+ <h2>Definition</h2>
646
+ <pre><code>ENTITY {{ entity.id }}{% if entity.abstract %} ABSTRACT{% endif %};
647
+ {% for attr in entity.attributes %} {{ attr.id }} : {% if attr.optional %}OPTIONAL {% endif %}{{ attr.type }};
648
+ {% endfor %}{% if entity.where_rules.size > 0 %}WHERE
649
+ {% for rule in entity.where_rules %} {{ rule.id }}: {{ rule.expression }};
650
+ {% endfor %}{% endif %}END_ENTITY;</code></pre>
651
+ </section>
652
+
653
+ <section>
654
+ <h2>Attributes</h2>
655
+ <table>
656
+ <thead>
657
+ <tr>
658
+ <th>Name</th>
659
+ <th>Type</th>
660
+ <th>Optional</th>
661
+ <th>Description</th>
662
+ </tr>
663
+ </thead>
664
+ <tbody>
665
+ {% for attr in entity.attributes %}
666
+ <tr>
667
+ <td><code>{{ attr.id }}</code></td>
668
+ <td>{{ attr.type }}</td>
669
+ <td>{{ attr.optional }}</td>
670
+ <td>{% if attr.remarks.size > 0 %}{{ attr.remarks | join: " " }}{% else %}-{% endif %}</td>
671
+ </tr>
672
+ {% endfor %}
673
+ </tbody>
674
+ </table>
675
+ </section>
676
+
677
+ {% if entity.where_rules.size > 0 %}
678
+ <section>
679
+ <h2>Constraints</h2>
680
+ {% for rule in entity.where_rules %}
681
+ <h3>{{ rule.id }}</h3>
682
+ <pre><code>{{ rule.expression }}</code></pre>
683
+ {% if rule.remarks.size > 0 %}
684
+ <p>{{ rule.remarks | join: " " }}</p>
685
+ {% endif %}
686
+ {% endfor %}
687
+ </section>
688
+ {% endif %}
689
+
690
+ <footer>
691
+ <p><a href="../index.html">← Back to Index</a></p>
692
+ </footer>
693
+ </body>
694
+ </html>
695
+ ----
696
+
697
+ == Template organization strategies
698
+
699
+ === Using partials
700
+
701
+ Break templates into reusable components:
702
+
703
+ .templates/partials/navigation.liquid
704
+ [source,liquid]
705
+ ----
706
+ <nav>
707
+ <a href="../index.html">Home</a> |
708
+ <a href="../schemas.html">Schemas</a> |
709
+ <a href="../entities.html">Entities</a> |
710
+ <a href="../types.html">Types</a>
711
+ </nav>
712
+ ----
713
+
714
+ Include partials (if your Liquid setup supports includes):
715
+
716
+ [source,liquid]
717
+ ----
718
+ {% include 'partials/navigation' %}
719
+ ----
720
+
721
+ Or use template concatenation in Ruby:
722
+
723
+ [source,ruby]
724
+ ----
725
+ header = File.read("templates/partials/header.liquid")
726
+ footer = File.read("templates/partials/footer.liquid")
727
+ content = File.read("templates/entity.liquid")
728
+
729
+ full_template = header + content + footer
730
+ template = Liquid::Template.parse(full_template)
731
+ ----
732
+
733
+ === Template inheritance pattern
734
+
735
+ .templates/base.liquid
736
+ [source,liquid]
737
+ ----
738
+ <!DOCTYPE html>
739
+ <html>
740
+ <head>
741
+ <title>{{ title }}</title>
742
+ {{ head_extra }}
743
+ </head>
744
+ <body>
745
+ <header>{{ header_content }}</header>
746
+
747
+ <main>
748
+ {{ content }}
749
+ </main>
750
+
751
+ <footer>{{ footer_content }}</footer>
752
+ </body>
753
+ </html>
754
+ ----
755
+
756
+ Use in Ruby:
757
+
758
+ [source,ruby]
759
+ ----
760
+ base = Liquid::Template.parse(File.read("templates/base.liquid"))
761
+
762
+ entity_content = entity_template.render(...)
763
+ output = base.render(
764
+ "title" => "Entity: #{entity.id}",
765
+ "content" => entity_content,
766
+ "header_content" => header,
767
+ "footer_content" => footer
768
+ )
769
+ ----
770
+
771
+ == Complete workflow example
772
+
773
+ === Full production generator
774
+
775
+ .lib/doc_generator.rb
776
+ [source,ruby]
777
+ ----
778
+ require "expressir"
779
+ require "liquid"
780
+ require "fileutils"
781
+ require "yaml"
782
+
783
+ module DocGenerator
784
+ class Generator
785
+ attr_reader :config, :stats
786
+
787
+ def initialize(config_path)
788
+ @config = YAML.load_file(config_path)
789
+ @templates = {}
790
+ @stats = { files: 0, schemas: 0, entities: 0, types: 0 }
791
+ end
792
+
793
+ def generate
794
+ start_time = Time.now
795
+
796
+ puts "🚀 Starting documentation generation..."
797
+
798
+ setup
799
+ repo_drop = load_schemas
800
+ generate_all(repo_drop)
801
+
802
+ elapsed = Time.now - start_time
803
+ print_summary(elapsed)
804
+ end
805
+
806
+ private
807
+
808
+ def setup
809
+ puts "📁 Setting up directories..."
810
+ [@config["output"]["dir"],
811
+ schemas_dir,
812
+ entities_dir,
813
+ types_dir,
814
+ assets_dir].each { |dir| FileUtils.mkdir_p(dir) }
815
+
816
+ copy_assets if @config["assets"]
817
+ end
818
+
819
+ def load_schemas
820
+ puts "📖 Loading schemas..."
821
+ files = @config["schemas"]["files"]
822
+ repo = Expressir::Express::Parser.from_files(files) do |file, schemas, error|
823
+ if error
824
+ puts " ❌ Error in #{file}: #{error.message}"
825
+ else
826
+ puts " ✓ Loaded #{file}"
827
+ end
828
+ end
829
+
830
+ @stats[:schemas] = repo.schemas.size
831
+ repo.to_liquid
832
+ end
833
+
834
+ def generate_all(repo_drop)
835
+ puts "✍️ Generating documentation..."
836
+
837
+ generate_index(repo_drop)
838
+ generate_schemas(repo_drop)
839
+ generate_entities(repo_drop)
840
+ generate_types(repo_drop) if @config["generate"]["types"]
841
+ generate_search_index(repo_drop) if @config["generate"]["search"]
842
+ end
843
+
844
+ def generate_index(repo_drop)
845
+ render_and_write("index", { "repository" => repo_drop }, "index.html")
846
+ end
847
+
848
+ def generate_schemas(repo_drop)
849
+ repo_drop.schemas.each do |schema|
850
+ render_and_write(
851
+ "schema",
852
+ { "schema" => schema },
853
+ "schemas/#{schema.id}.html"
854
+ )
855
+ end
856
+ end
857
+
858
+ def generate_entities(repo_drop)
859
+ repo_drop.schemas.each do |schema|
860
+ schema.entities.each do |entity|
861
+ @stats[:entities] += 1
862
+ render_and_write(
863
+ "entity",
864
+ { "entity" => entity, "schema" => schema },
865
+ "entities/#{entity.id}.html"
866
+ )
867
+ end
868
+ end
869
+ end
870
+
871
+ def generate_types(repo_drop)
872
+ repo_drop.schemas.each do |schema|
873
+ schema.types.each do |type|
874
+ @stats[:types] += 1
875
+ render_and_write(
876
+ "type",
877
+ { "type" => type, "schema" => schema },
878
+ "types/#{type.id}.html"
879
+ )
880
+ end
881
+ end
882
+ end
883
+
884
+ def generate_search_index(repo_drop)
885
+ # Generate JSON index for client-side search
886
+ index = build_search_index(repo_drop)
887
+ path = File.join(@config["output"]["dir"], "search-index.json")
888
+ File.write(path, JSON.pretty_generate(index))
889
+ puts " ✓ search-index.json"
890
+ end
891
+
892
+ def render_and_write(template_name, context, output_path)
893
+ template = load_template(template_name)
894
+ output = template.render(context)
895
+
896
+ full_path = File.join(@config["output"]["dir"], output_path)
897
+ File.write(full_path, output)
898
+
899
+ @stats[:files] += 1
900
+ puts " ✓ #{output_path}"
901
+ end
902
+
903
+ def load_template(name)
904
+ @templates[name] ||= begin
905
+ path = File.join(@config["templates"]["dir"], "#{name}.liquid")
906
+ Liquid::Template.parse(File.read(path))
907
+ end
908
+ end
909
+
910
+ def schemas_dir
911
+ File.join(@config["output"]["dir"], "schemas")
912
+ end
913
+
914
+ def entities_dir
915
+ File.join(@config["output"]["dir"], "entities")
916
+ end
917
+
918
+ def types_dir
919
+ File.join(@config["output"]["dir"], "types")
920
+ end
921
+
922
+ def assets_dir
923
+ File.join(@config["output"]["dir"], "assets")
924
+ end
925
+
926
+ def copy_assets
927
+ FileUtils.cp_r(@config["assets"]["dir"], assets_dir)
928
+ end
929
+
930
+ def build_search_index(repo_drop)
931
+ items = []
932
+
933
+ repo_drop.schemas.each do |schema|
934
+ schema.entities.each do |entity|
935
+ items << {
936
+ type: "entity",
937
+ id: entity.id,
938
+ schema: schema.id,
939
+ url: "entities/#{entity.id}.html",
940
+ description: entity.remarks.join(" ")
941
+ }
942
+ end
943
+ end
944
+
945
+ { items: items }
946
+ end
947
+
948
+ def print_summary(elapsed)
949
+ puts "\n✅ Documentation generated successfully!"
950
+ puts "\nStatistics:"
951
+ puts " Schemas: #{@stats[:schemas]}"
952
+ puts " Entities: #{@stats[:entities]}"
953
+ puts " Types: #{@stats[:types]}"
954
+ puts " Files: #{@stats[:files]}"
955
+ puts "\n⏱ Time: #{elapsed.round(2)}s"
956
+ puts "📂 Output: #{@config['output']['dir']}"
957
+ end
958
+ end
959
+ end
960
+
961
+ # Usage
962
+ if __FILE__ == $PROGRAM_NAME
963
+ generator = DocGenerator::Generator.new("config.yml")
964
+ generator.generate
965
+ end
966
+ ----
967
+
968
+ === Configuration file
969
+
970
+ .config.yml
971
+ [source,yaml]
972
+ ----
973
+ schemas:
974
+ files:
975
+ - "schemas/**/*.exp"
976
+
977
+ templates:
978
+ dir: "templates"
979
+
980
+ output:
981
+ dir: "docs"
982
+
983
+ assets:
984
+ dir: "assets"
985
+
986
+ generate:
987
+ types: true
988
+ search: true
989
+ ----
990
+
991
+ == Best practices
992
+
993
+ **Organize templates logically**::
994
+ Group related templates together, use descriptive names, and document
995
+ template purpose.
996
+
997
+ **Cache parsed templates**::
998
+ Load and parse templates once, reuse for multiple documents.
999
+
1000
+ **Validate input data**::
1001
+ Check for nil values and empty collections before rendering.
1002
+
1003
+ **Handle errors gracefully**::
1004
+ Catch and report parsing errors, continue with other files.
1005
+
1006
+ **Provide progress feedback**::
1007
+ Show what's being generated for long-running operations.
1008
+
1009
+ **Use consistent naming**::
1010
+ Follow conventions for output file names and directory structure.
1011
+
1012
+ **Test with various schemas**::
1013
+ Ensure templates work with different schema structures.
1014
+
1015
+ **Version control templates**::
1016
+ Track template changes alongside code.
1017
+
1018
+ == Next steps
1019
+
1020
+ Explore related topics:
1021
+
1022
+ * link:../cli/index.html[CLI Guides] - Command-line tools
1023
+ * link:../ruby-api/index.html[Ruby API Guides] - Programmatic usage
1024
+ * link:../../_tutorials/liquid-templates.html[Liquid Templates Tutorial]
1025
+ - Practice examples
1026
+
1027
+ == Summary
1028
+
1029
+ Complete documentation generation includes:
1030
+
1031
+ * ✅ Structured project organization
1032
+ * ✅ Reusable template components
1033
+ * ✅ Multiple output formats (HTML, Markdown, etc.)
1034
+ * ✅ Multi-file documentation generation
1035
+ * ✅ Cross-reference tables and indexes
1036
+ * ✅ Progress tracking and error handling
1037
+ * ✅ Configurable generation options
1038
+ * ✅ Scalable architecture for large schemas
1039
+
1040
+ With these patterns and examples, you can build sophisticated
1041
+ documentation systems that transform EXPRESS schemas into professional,
1042
+ maintainable documentation automatically.