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.
- 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 +244 -39
- data/Gemfile +2 -1
- data/README.adoc +621 -54
- 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/cli.rb +12 -4
- 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 +15 -2
- metadata +114 -4
- data/docs/benchmarking.adoc +0 -107
- 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.
|