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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +209 -55
- data/Gemfile +2 -1
- data/README.adoc +650 -83
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/changes/schema_change.rb +32 -22
- data/lib/expressir/changes/{edition_change.rb → version_change.rb} +3 -3
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/changes_import_eengine.rb +2 -2
- data/lib/expressir/commands/changes_validate.rb +1 -1
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +16 -3
- metadata +115 -5
- data/docs/benchmarking.adoc +0 -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!
|