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,257 @@
1
+ ---
2
+ title: Ruby API
3
+ nav_order: 2
4
+ has_children: true
5
+ ---
6
+
7
+ = Ruby API
8
+
9
+ == Purpose
10
+
11
+ The Expressir Ruby API provides programmatic access to EXPRESS schema
12
+ parsing, analysis, and manipulation. This section covers the core APIs for
13
+ working with EXPRESS models in Ruby applications.
14
+
15
+ == When to use the API vs CLI
16
+
17
+ Use the Ruby API when:
18
+
19
+ * Integrating Expressir into your Ruby application
20
+ * Building custom tools or workflows
21
+ * Performing programmatic schema analysis
22
+ * Creating automated processing pipelines
23
+ * Generating custom reports or documentation
24
+
25
+ Use the CLI when:
26
+
27
+ * Performing one-off operations
28
+ * Quick validation or formatting tasks
29
+ * Command-line scripting
30
+ * CI/CD pipeline integration
31
+ * Interactive exploration of schemas
32
+
33
+ == Installation
34
+
35
+ Add Expressir to your application's `Gemfile`:
36
+
37
+ [source,ruby]
38
+ ----
39
+ gem "expressir", "~> 0.2"
40
+ ----
41
+
42
+ Then execute:
43
+
44
+ [source,bash]
45
+ ----
46
+ bundle install
47
+ ----
48
+
49
+ Or install directly:
50
+
51
+ [source,bash]
52
+ ----
53
+ gem install expressir
54
+ ----
55
+
56
+ == Basic usage
57
+
58
+ .Parsing a schema and accessing its contents
59
+ [example]
60
+ ====
61
+ [source,ruby]
62
+ ----
63
+ require "expressir"
64
+
65
+ # Parse a schema file
66
+ repository = Expressir::Express::Parser.from_file("schema.exp")
67
+
68
+ # Access schemas and entities
69
+ repository.schemas.each do |schema|
70
+ puts "Schema: #{schema.id}"
71
+
72
+ schema.entities&.each do |entity|
73
+ puts " Entity: #{entity.id}"
74
+ end
75
+ end
76
+ ----
77
+ ====
78
+
79
+ == Core API components
80
+
81
+ The Expressir Ruby API consists of several key components:
82
+
83
+ === Parser API
84
+
85
+ The link:parsing-files.html[Parser] provides methods for reading and parsing
86
+ EXPRESS schema files into Ruby model objects.
87
+
88
+ Key classes:
89
+
90
+ * `Expressir::Express::Parser` - Main parser interface
91
+ * `Expressir::Express::Error::SchemaParseFailure` - Parse error handling
92
+
93
+ === Repository API
94
+
95
+ The link:working-with-repository.html[Repository] represents a collection of
96
+ schemas with methods for querying, validation, and export.
97
+
98
+ Key classes:
99
+
100
+ * `Expressir::Model::Repository` - Schema collection container
101
+ * `Expressir::Model::Declarations::Schema` - Individual schema representation
102
+
103
+ === SearchEngine API
104
+
105
+ The link:search-engine.html[SearchEngine] enables powerful querying and
106
+ filtering of EXPRESS elements within a repository.
107
+
108
+ Key classes:
109
+
110
+ * `Expressir::Model::SearchEngine` - Main search interface
111
+ * Pattern matching and type filtering capabilities
112
+
113
+ === Formatter API
114
+
115
+ The link:formatting-schemas.html[Formatter] converts EXPRESS models back to
116
+ formatted EXPRESS language text.
117
+
118
+ Key classes:
119
+
120
+ * `Expressir::Express::Formatter` - Main formatting interface
121
+ * `Expressir::Express::HyperLinkFormatter` - HTML hyperlink generation
122
+
123
+ == Common patterns
124
+
125
+ === Parse and validate
126
+
127
+ [source,ruby]
128
+ ----
129
+ repository = Expressir::Express::Parser.from_file("schema.exp")
130
+
131
+ # Validate the repository
132
+ result = repository.validate(strict: true)
133
+
134
+ if result[:valid?]
135
+ puts "Schema is valid"
136
+ else
137
+ puts "Validation errors:"
138
+ result[:errors].each { |err| puts " - #{err}" }
139
+ end
140
+ ----
141
+
142
+ === Search for entities
143
+
144
+ [source,ruby]
145
+ ----
146
+ repository = Expressir::Express::Parser.from_file("schema.exp")
147
+ engine = Expressir::Model::SearchEngine.new(repository)
148
+
149
+ # Find all entities matching a pattern
150
+ results = engine.search(
151
+ pattern: "action*",
152
+ type: "entity",
153
+ case_sensitive: false
154
+ )
155
+
156
+ results.each do |result|
157
+ puts "Found: #{result[:path]}"
158
+ end
159
+ ----
160
+
161
+ === Format and output
162
+
163
+ [source,ruby]
164
+ ----
165
+ repository = Expressir::Express::Parser.from_file("schema.exp")
166
+ formatter = Expressir::Express::Formatter.new(no_remarks: true)
167
+
168
+ # Format each schema
169
+ repository.schemas.each do |schema|
170
+ output = formatter.format(schema)
171
+ File.write("formatted_#{schema.id}.exp", output)
172
+ end
173
+ ----
174
+
175
+ == Error handling
176
+
177
+ Expressir provides specific exception classes for different error scenarios:
178
+
179
+ [source,ruby]
180
+ ----
181
+ begin
182
+ repository = Expressir::Express::Parser.from_file("schema.exp")
183
+ rescue Expressir::Express::Error::SchemaParseFailure => e
184
+ puts "Parse error in #{e.filename}"
185
+ puts "Cause: #{e.parse_failure_cause.message}"
186
+ puts e.parse_failure_cause.ascii_tree
187
+ rescue Errno::ENOENT => e
188
+ puts "File not found: #{e.message}"
189
+ end
190
+ ----
191
+
192
+ == Performance considerations
193
+
194
+ === Caching
195
+
196
+ For repeatedly parsing the same files, consider using caching:
197
+
198
+ [source,ruby]
199
+ ----
200
+ # Enable caching for better performance
201
+ Expressir::Express::Cache.cache_path = ".cache"
202
+ repository = Expressir::Express::Parser.from_file("schema.exp")
203
+ ----
204
+
205
+ === Skipping references
206
+
207
+ When you don't need resolved references, skip them for better performance:
208
+
209
+ [source,ruby]
210
+ ----
211
+ repository = Expressir::Express::Parser.from_file(
212
+ "schema.exp",
213
+ skip_references: true
214
+ )
215
+ ----
216
+
217
+ === Batch processing
218
+
219
+ Use `from_files` with progress tracking for processing multiple schemas:
220
+
221
+ [source,ruby]
222
+ ----
223
+ files = Dir.glob("schemas/**/*.exp")
224
+
225
+ repository = Expressir::Express::Parser.from_files(files) do |file, schemas, error|
226
+ if error
227
+ warn "Error parsing #{file}: #{error.message}"
228
+ else
229
+ puts "Loaded #{schemas.size} schemas from #{file}"
230
+ end
231
+ end
232
+ ----
233
+
234
+ == Next steps
235
+
236
+ Explore the detailed guides for each API component:
237
+
238
+ * link:parsing-files.html[Parsing Files] - Learn about the Parser API
239
+ * link:working-with-repository.html[Working with Repository] - Manage schema collections
240
+ * link:search-engine.html[Search Engine] - Query and filter schemas
241
+ * link:formatting-schemas.html[Formatting Schemas] - Generate EXPRESS output
242
+
243
+ == Summary
244
+
245
+ The Expressir Ruby API provides a comprehensive set of tools for working with
246
+ EXPRESS schemas programmatically. The API is designed to be intuitive and
247
+ follows Ruby best practices while providing powerful capabilities for schema
248
+ analysis and manipulation.
249
+
250
+ Key takeaways:
251
+
252
+ * Parser API handles all EXPRESS file parsing
253
+ * Repository API manages schema collections
254
+ * SearchEngine API enables powerful querying
255
+ * Formatter API generates EXPRESS output
256
+ * Proper error handling ensures robust applications
257
+ * Performance optimizations available for large schemas
@@ -0,0 +1,421 @@
1
+ ---
2
+ title: Parsing Files
3
+ parent: Ruby API
4
+ grand_parent: Guides
5
+ nav_order: 1
6
+ ---
7
+
8
+ = Parsing Files
9
+
10
+ == Purpose
11
+
12
+ This guide covers using the Expressir Parser API to read and parse EXPRESS
13
+ schema files into Ruby model objects. The Parser handles all aspects of
14
+ EXPRESS language parsing and provides options for reference resolution and
15
+ source preservation.
16
+
17
+ == References
18
+
19
+ * link:../express-language.html[EXPRESS Language] - Understanding EXPRESS syntax
20
+ * link:working-with-repository.html[Working with Repository] - Using parsed results
21
+ * link:../../tutorials/parsing-your-first-schema.html[Tutorial: Parsing Your First Schema]
22
+
23
+ == Concepts
24
+
25
+ parser:: The component that reads EXPRESS files and converts them to Ruby models
26
+ repository:: The container object that holds all parsed schemas
27
+ reference_resolution:: The process of linking references between schemas
28
+ skip_references:: Option to disable reference resolution for faster parsing
29
+ include_source:: Option to preserve original source code in the model
30
+
31
+ == The Parser API
32
+
33
+ === Main methods
34
+
35
+ The `Expressir::Express::Parser` class provides three main parsing methods:
36
+
37
+ `from_file(file, options)`:: Parse a single EXPRESS file
38
+ `from_files(files, options, &block)`:: Parse multiple EXPRESS files
39
+ `from_exp(content, options)`:: Parse EXPRESS content from a string
40
+
41
+ All methods return a `Expressir::Model::Repository` object containing the
42
+ parsed schemas.
43
+
44
+ == Parsing a single file
45
+
46
+ === Basic usage
47
+
48
+ [source,ruby]
49
+ ----
50
+ require "expressir"
51
+
52
+ # Parse a single schema file
53
+ repository = Expressir::Express::Parser.from_file("schema.exp")
54
+
55
+ # Access the parsed schemas
56
+ repository.schemas.each do |schema|
57
+ puts "Schema: #{schema.id}"
58
+ puts "Version: #{schema.version&.value}"
59
+ end
60
+ ----
61
+
62
+ === With options
63
+
64
+ The parser accepts several options to control parsing behavior:
65
+
66
+ [source,ruby]
67
+ ----
68
+ repository = Expressir::Express::Parser.from_file(
69
+ "schema.exp",
70
+ skip_references: false, # Resolve references between schemas
71
+ include_source: true, # Preserve original source in model
72
+ root_path: "/base/path" # Base path for relative file paths
73
+ )
74
+ ----
75
+
76
+ ==== Option: skip_references
77
+
78
+ When `skip_references` is `false` (default), the parser resolves all
79
+ references between schemas, entities, and types. This connects
80
+ `USE FROM` and `REFERENCE FROM` statements to their targets.
81
+
82
+ Set to `true` for faster parsing when you don't need resolved references:
83
+
84
+ [source,ruby]
85
+ ----
86
+ # Faster parsing without reference resolution
87
+ repository = Expressir::Express::Parser.from_file(
88
+ "schema.exp",
89
+ skip_references: true
90
+ )
91
+ ----
92
+
93
+ ==== Option: include_source
94
+
95
+ When `include_source` is `true`, the parser preserves the original source
96
+ code text in model element objects. This is useful for:
97
+
98
+ * Generating documentation with original formatting
99
+ * Preserving comments and remarks
100
+ * Source code analysis
101
+
102
+ [source,ruby]
103
+ ----
104
+ repository = Expressir::Express::Parser.from_file(
105
+ "schema.exp",
106
+ include_source: true
107
+ )
108
+
109
+ # Access original source
110
+ schema = repository.schemas.first
111
+ puts schema.source if schema.respond_to?(:source)
112
+ ----
113
+
114
+ ==== Option: root_path
115
+
116
+ The `root_path` option sets a base directory for resolving relative file
117
+ paths in the model:
118
+
119
+ [source,ruby]
120
+ ----
121
+ repository = Expressir::Express::Parser.from_file(
122
+ "/full/path/to/schema.exp",
123
+ root_path: "/full/path/to"
124
+ )
125
+
126
+ # Schema file will be stored as relative path: "schema.exp"
127
+ puts repository.schemas.first.file # => "schema.exp"
128
+ ----
129
+
130
+ == Parsing multiple files
131
+
132
+ === Basic usage
133
+
134
+ Use `from_files` to parse multiple EXPRESS files in one operation:
135
+
136
+ [source,ruby]
137
+ ----
138
+ files = [
139
+ "schema1.exp",
140
+ "schema2.exp",
141
+ "schema3.exp"
142
+ ]
143
+
144
+ repository = Expressir::Express::Parser.from_files(files)
145
+
146
+ # All schemas are in one repository
147
+ puts "Loaded #{repository.schemas.size} schemas"
148
+ ----
149
+
150
+ === With progress tracking
151
+
152
+ Provide a block to track parsing progress and handle errors:
153
+
154
+ [source,ruby]
155
+ ----
156
+ files = Dir.glob("schemas/**/*.exp")
157
+
158
+ repository = Expressir::Express::Parser.from_files(files) do |filename, schemas, error|
159
+ if error
160
+ warn "Failed to parse #{filename}"
161
+ warn " Error: #{error.message}"
162
+ # Continue with remaining files
163
+ else
164
+ puts "✓ Loaded #{schemas.size} schemas from #{filename}"
165
+ end
166
+ end
167
+
168
+ puts "Total schemas loaded: #{repository.schemas.size}"
169
+ ----
170
+
171
+ The block receives three parameters:
172
+
173
+ `filename`:: The file being processed
174
+ `schemas`:: Array of schemas parsed from the file (nil if error)
175
+ `error`:: Exception that occurred (nil if successful)
176
+
177
+ === Error handling behavior
178
+
179
+ When parsing multiple files:
180
+
181
+ * Individual file failures don't stop the entire operation
182
+ * `SchemaParseFailure` exceptions are caught and passed to the block
183
+ * Other exceptions are re-raised
184
+ * Successfully parsed schemas are included in the final repository
185
+
186
+ [source,ruby]
187
+ ----
188
+ successful = []
189
+ failed = []
190
+
191
+ repository = Expressir::Express::Parser.from_files(files) do |file, schemas, error|
192
+ if error && error.is_a?(Expressir::Express::Error::SchemaParseFailure)
193
+ failed << { file: file, error: error.message }
194
+ elsif schemas
195
+ successful << file
196
+ end
197
+ end
198
+
199
+ puts "Successfully parsed: #{successful.size}"
200
+ puts "Failed to parse: #{failed.size}"
201
+ ----
202
+
203
+ == Parsing from strings
204
+
205
+ Parse EXPRESS content directly from a string:
206
+
207
+ [source,ruby]
208
+ ----
209
+ express_code = <<~EXPRESS
210
+ SCHEMA example_schema;
211
+ ENTITY example_entity;
212
+ name : STRING;
213
+ END_ENTITY;
214
+ END_SCHEMA;
215
+ EXPRESS
216
+
217
+ repository = Expressir::Express::Parser.from_exp(express_code)
218
+
219
+ schema = repository.schemas.first
220
+ puts "Schema: #{schema.id}" # => "example_schema"
221
+ ----
222
+
223
+ This is useful for:
224
+
225
+ * Testing and development
226
+ * Generating schemas programmatically
227
+ * Processing schemas from non-file sources
228
+
229
+ == Error handling
230
+
231
+ === SchemaParseFailure exception
232
+
233
+ When parsing fails, the parser raises
234
+ `Expressir::Express::Error::SchemaParseFailure`:
235
+
236
+ [source,ruby]
237
+ ----
238
+ begin
239
+ repository = Expressir::Express::Parser.from_file("bad_schema.exp")
240
+ rescue Expressir::Express::Error::SchemaParseFailure => e
241
+ puts "Parse failed: #{e.filename}"
242
+ puts "Root cause: #{e.parse_failure_cause.message}"
243
+
244
+ # Display parse tree for debugging
245
+ puts "\nParse error details:"
246
+ puts e.parse_failure_cause.ascii_tree
247
+ end
248
+ ----
249
+
250
+ The exception provides:
251
+
252
+ `filename`:: The file that failed to parse
253
+ `parse_failure_cause`:: The underlying Parslet parse error
254
+ `message`:: Human-readable error description
255
+
256
+ === Other common errors
257
+
258
+ [source,ruby]
259
+ ----
260
+ begin
261
+ repository = Expressir::Express::Parser.from_file("schema.exp")
262
+ rescue Expressir::Express::Error::SchemaParseFailure => e
263
+ # Handle parse errors
264
+ puts "Invalid EXPRESS syntax: #{e.message}"
265
+ rescue Errno::ENOENT => e
266
+ # Handle missing files
267
+ puts "File not found: #{e.message}"
268
+ rescue Errno::EACCES => e
269
+ # Handle permission errors
270
+ puts "Permission denied: #{e.message}"
271
+ rescue => e
272
+ # Handle unexpected errors
273
+ puts "Unexpected error: #{e.class.name} - #{e.message}"
274
+ end
275
+ ----
276
+
277
+ == Performance considerations
278
+
279
+ === Skip references for speed
280
+
281
+ Reference resolution can be time-consuming for large schema sets. Skip it
282
+ when not needed:
283
+
284
+ [source,ruby]
285
+ ----
286
+ # Fast parsing without references
287
+ repository = Expressir::Express::Parser.from_file(
288
+ "large_schema.exp",
289
+ skip_references: true
290
+ )
291
+
292
+ # Resolve references later if needed
293
+ repository.resolve_all_references
294
+ ----
295
+
296
+ === Process files in parallel
297
+
298
+ For very large schema sets, consider parallel processing:
299
+
300
+ [source,ruby]
301
+ ----
302
+ require "parallel"
303
+
304
+ files = Dir.glob("schemas/**/*.exp")
305
+
306
+ # Parse files in parallel
307
+ repositories = Parallel.map(files, in_threads: 4) do |file|
308
+ Expressir::Express::Parser.from_file(file, skip_references: true)
309
+ end
310
+
311
+ # Combine into single repository
312
+ combined = Expressir::Model::Repository.new
313
+ repositories.each do |repo|
314
+ combined.schemas.concat(repo.schemas)
315
+ end
316
+
317
+ # Resolve references once at the end
318
+ combined.resolve_all_references
319
+ ----
320
+
321
+ === Use caching for repeated operations
322
+
323
+ Enable caching for schemas that are parsed repeatedly:
324
+
325
+ [source,ruby]
326
+ ----
327
+ # Set cache directory
328
+ Expressir::Express::Cache.cache_path = ".cache"
329
+
330
+ # First parse writes to cache
331
+ repository = Expressir::Express::Parser.from_file("schema.exp")
332
+
333
+ # Subsequent parses use cache (much faster)
334
+ repository = Expressir::Express::Parser.from_file("schema.exp")
335
+ ----
336
+
337
+ == Working with parsed results
338
+
339
+ === Accessing schemas
340
+
341
+ [source,ruby]
342
+ ----
343
+ repository = Expressir::Express::Parser.from_file("schema.exp")
344
+
345
+ # Iterate through all schemas
346
+ repository.schemas.each do |schema|
347
+ puts "Schema: #{schema.id}"
348
+ puts "File: #{schema.file}"
349
+ puts "Version: #{schema.version&.value}"
350
+ end
351
+
352
+ # Find a specific schema
353
+ schema = repository.schemas.find { |s| s.id == "action_schema" }
354
+ ----
355
+
356
+ === Accessing entities and types
357
+
358
+ [source,ruby]
359
+ ----
360
+ schema = repository.schemas.first
361
+
362
+ # Access entities
363
+ schema.entities&.each do |entity|
364
+ puts "Entity: #{entity.id}"
365
+
366
+ # Access attributes
367
+ entity.attributes&.each do |attr|
368
+ puts " Attribute: #{attr.id}"
369
+ end
370
+ end
371
+
372
+ # Access types
373
+ schema.types&.each do |type|
374
+ puts "Type: #{type.id}"
375
+ end
376
+ ----
377
+
378
+ === Checking parse results
379
+
380
+ [source,ruby]
381
+ ----
382
+ repository = Expressir::Express::Parser.from_file("schema.exp")
383
+
384
+ puts "Schemas parsed: #{repository.schemas.size}"
385
+ puts "Total entities: #{repository.schemas.sum { |s| s.entities&.size || 0 }}"
386
+ puts "Total types: #{repository.schemas.sum { |s| s.types&.size || 0 }}"
387
+
388
+ # Validate completeness
389
+ repository.schemas.each do |schema|
390
+ if schema.entities&.empty? && schema.types&.empty?
391
+ warn "Warning: Schema #{schema.id} appears to be empty"
392
+ end
393
+ end
394
+ ----
395
+
396
+ == Next steps
397
+
398
+ After parsing files, you'll want to:
399
+
400
+ * link:working-with-repository.html[Work with Repository] - Query and manipulate schemas
401
+ * link:search-engine.html[Use Search Engine] - Find specific elements
402
+ * link:formatting-schemas.html[Format Schemas] - Convert back to EXPRESS
403
+
404
+ == Summary
405
+
406
+ The Parser API provides flexible options for reading EXPRESS schemas:
407
+
408
+ * `from_file` for single files
409
+ * `from_files` for multiple files with progress tracking
410
+ * `from_exp` for string content
411
+ * Options control reference resolution and source preservation
412
+ * Robust error handling with detailed parse failure information
413
+ * Performance optimizations available for large schemas
414
+
415
+ Key takeaways:
416
+
417
+ * Always handle `SchemaParseFailure` exceptions
418
+ * Use `skip_references: true` for faster parsing when references aren't needed
419
+ * Track progress with blocks when parsing multiple files
420
+ * Consider caching for frequently-parsed schemas
421
+ * The parsed `Repository` contains all schemas and provides querying capabilities