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
@@ -1,21 +1,416 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../schema_manifest"
4
+ require_relative "../schema_manifest_entry"
5
+
1
6
  module Expressir
2
7
  module Model
3
- # Multi-schema global scope
8
+ # Multi-schema global scope with enhanced repository features
9
+ # Focuses on schema management and delegates indexing/validation to specialized classes
4
10
  class Repository < ModelElement
5
11
  attribute :schemas, Expressir::Model::Declarations::Schema,
6
- collection: true
12
+ collection: true,
13
+ initialize_empty: true
7
14
  attribute :_class, :string, default: -> { send(:name) }
8
15
 
16
+ # Base directory for schema files
17
+ attr_accessor :base_dir
18
+
19
+ # Index instances (lazy-loaded)
20
+ attr_reader :entity_index, :type_index, :reference_index
21
+
9
22
  key_value do
10
23
  map "_class", to: :_class, render_default: true
11
24
  map "schemas", to: :schemas
12
25
  end
13
26
 
27
+ def initialize(*args, base_dir: nil, **kwargs)
28
+ super(*args, **kwargs)
29
+ @base_dir = base_dir
30
+ @entity_index = nil
31
+ @type_index = nil
32
+ @reference_index = nil
33
+ end
34
+
14
35
  # @return [Array<Declaration>]
15
36
  def children
16
- [
17
- *schemas,
18
- ]
37
+ schemas
38
+ end
39
+
40
+ # Build indexes for entities, types, and references
41
+ # @return [void]
42
+ def build_indexes
43
+ @entity_index = Indexes::EntityIndex.new(schemas)
44
+ @type_index = Indexes::TypeIndex.new(schemas)
45
+ @reference_index = Indexes::ReferenceIndex.new(schemas)
46
+ end
47
+
48
+ # Find entity by qualified name
49
+ # @param qualified_name [String] Entity qualified name (e.g., "action_schema.action")
50
+ # @return [Declarations::Entity, nil] Found entity or nil
51
+ def find_entity(qualified_name:)
52
+ ensure_indexes_built
53
+ @entity_index.find(qualified_name)
54
+ end
55
+
56
+ # Find type by qualified name
57
+ # @param qualified_name [String] Type qualified name
58
+ # @return [Declarations::Type, nil] Found type or nil
59
+ def find_type(qualified_name:)
60
+ ensure_indexes_built
61
+ @type_index.find(qualified_name)
62
+ end
63
+
64
+ # List all entities
65
+ # @param schema [String, nil] Filter by schema name
66
+ # @param format [Symbol] Output format (:object, :hash, :json, :yaml)
67
+ # @return [Array] List of entities
68
+ def list_entities(schema: nil, format: :object)
69
+ ensure_indexes_built
70
+
71
+ entities = @entity_index.list(schema: schema)
72
+
73
+ format_output(entities, format) do |entity|
74
+ { id: entity.id, schema: entity.parent.id, path: entity.path }
75
+ end
76
+ end
77
+
78
+ # List all types
79
+ # @param schema [String, nil] Filter by schema name
80
+ # @param category [String, nil] Filter by type category (select, enumeration, etc.)
81
+ # @param format [Symbol] Output format (:object, :hash, :json, :yaml)
82
+ # @return [Array] List of types
83
+ def list_types(schema: nil, category: nil, format: :object)
84
+ ensure_indexes_built
85
+
86
+ types = @type_index.list(schema: schema, category: category)
87
+
88
+ format_output(types, format) do |type|
89
+ { id: type.id, schema: type.parent.id, path: type.path,
90
+ category: @type_index.categorize(type) }
91
+ end
92
+ end
93
+
94
+ # Resolve all references across schemas
95
+ # Uses the existing ResolveReferencesModelVisitor
96
+ # @return [void]
97
+ def resolve_all_references
98
+ visitor = Expressir::Express::ResolveReferencesModelVisitor.new
99
+ visitor.visit(self)
100
+ end
101
+
102
+ # Validate repository consistency
103
+ # @param strict [Boolean] Enable strict validation
104
+ # @return [Hash] Validation results with :valid?, :errors, :warnings
105
+ def validate(strict: false)
106
+ ensure_indexes_built
107
+ validator = RepositoryValidator.new(schemas, @reference_index)
108
+ validator.validate(strict: strict)
109
+ end
110
+
111
+ # Get statistics
112
+ # @param format [Symbol] Output format (:hash, :json, :yaml)
113
+ # @return [Hash, String] Repository statistics
114
+ def statistics(format: :hash)
115
+ ensure_indexes_built
116
+
117
+ stats = {
118
+ total_schemas: schemas.size,
119
+ total_entities: count_entities,
120
+ total_types: count_types,
121
+ total_functions: count_functions,
122
+ total_rules: count_rules,
123
+ total_procedures: count_procedures,
124
+ entities_by_schema: entities_by_schema_counts,
125
+ types_by_category: types_by_category_counts,
126
+ interfaces: interface_counts,
127
+ }
128
+
129
+ case format
130
+ when :json
131
+ require "json"
132
+ stats.to_json
133
+ when :yaml
134
+ require "yaml"
135
+ stats.to_yaml
136
+ else
137
+ stats
138
+ end
139
+ end
140
+
141
+ # Group schemas by category based on their contents
142
+ # @return [Hash] Hash with category keys and schema arrays
143
+ def schemas_by_category
144
+ {
145
+ with_entities: schemas.select { |s| s.entities&.any? },
146
+ with_types: schemas.select { |s| s.types&.any? },
147
+ with_functions: schemas.select { |s| s.functions&.any? },
148
+ with_rules: schemas.select { |s| s.rules&.any? },
149
+ interface_only: schemas.select do |s|
150
+ s.interfaces&.any? && !s.entities&.any? && !s.types&.any?
151
+ end,
152
+ empty: schemas.select do |s|
153
+ !s.entities&.any? && !s.types&.any? && !s.functions&.any?
154
+ end,
155
+ }
156
+ end
157
+
158
+ # Get largest schemas by total element count
159
+ # @param limit [Integer] Maximum number of schemas to return
160
+ # @return [Array<Hash>] Array of hashes with :schema and :total_elements
161
+ def largest_schemas(limit = 10)
162
+ schemas.map do |s|
163
+ {
164
+ schema: s,
165
+ total_elements: count_schema_elements(s),
166
+ }
167
+ end.sort_by { |item| -item[:total_elements] }.take(limit)
168
+ end
169
+
170
+ # Calculate schema complexity score
171
+ # Entities=2, Types=1, Functions=3, Procedures=3, Rules=4, Interfaces=2
172
+ # @param schema [Declarations::Schema] Schema to analyze
173
+ # @return [Integer] Complexity score
174
+ def schema_complexity(schema)
175
+ score = 0
176
+ score += (schema.entities&.size || 0) * 2
177
+ score += (schema.types&.size || 0) * 1
178
+ score += (schema.functions&.size || 0) * 3
179
+ score += (schema.procedures&.size || 0) * 3
180
+ score += (schema.rules&.size || 0) * 4
181
+ score += (schema.interfaces&.size || 0) * 2
182
+ score
183
+ end
184
+
185
+ # Get schemas sorted by complexity
186
+ # @param limit [Integer] Maximum number of schemas to return
187
+ # @return [Array<Hash>] Array of hashes with :schema and :complexity
188
+ def schemas_by_complexity(limit = 10)
189
+ schemas.map { |s| { schema: s, complexity: schema_complexity(s) } }
190
+ .sort_by { |item| -item[:complexity] }
191
+ .take(limit)
192
+ end
193
+
194
+ # Get dependency statistics
195
+ # @return [Hash] Statistics about interface dependencies
196
+ def dependency_statistics
197
+ stats = {
198
+ total_interfaces: 0,
199
+ use_from_count: 0,
200
+ reference_from_count: 0,
201
+ most_referenced: [],
202
+ most_dependent: [],
203
+ }
204
+
205
+ reference_counts = Hash.new(0)
206
+ dependency_counts = Hash.new(0)
207
+
208
+ schemas.each do |schema|
209
+ next unless schema.interfaces&.any?
210
+
211
+ stats[:total_interfaces] += schema.interfaces.size
212
+ dependency_counts[schema.id] = schema.interfaces.size
213
+
214
+ schema.interfaces.each do |interface|
215
+ stats[:use_from_count] += 1 if interface.kind == Declarations::Interface::USE
216
+ stats[:reference_from_count] += 1 if interface.kind == Declarations::Interface::REFERENCE
217
+ reference_counts[interface.schema.id] += 1 if interface.schema
218
+ end
219
+ end
220
+
221
+ stats[:most_referenced] = reference_counts.sort_by do |_, v|
222
+ -v
223
+ end.take(10).to_h
224
+ stats[:most_dependent] = dependency_counts.sort_by do |_, v|
225
+ -v
226
+ end.take(10).to_h
227
+
228
+ stats
229
+ end
230
+
231
+ # Generate SchemaManifest from repository
232
+ # @return [SchemaManifest] Manifest describing all schemas
233
+ def to_manifest
234
+ manifest = Expressir::SchemaManifest.new
235
+ schemas.each do |schema|
236
+ manifest.schemas << Expressir::SchemaManifestEntry.new(
237
+ id: schema.id,
238
+ path: schema.file || "#{schema.id}.exp",
239
+ )
240
+ end
241
+ manifest
242
+ end
243
+
244
+ # Build repository from list of schema files
245
+ # @param file_paths [Array<String>] Schema file paths
246
+ # @param base_dir [String, nil] Base directory for path resolution
247
+ # @return [Repository] Built repository with all schemas
248
+ def self.from_files(file_paths, base_dir: nil)
249
+ repo = new(base_dir: base_dir)
250
+
251
+ file_paths.each do |path|
252
+ parsed = Expressir::Express::Parser.from_file(path)
253
+ parsed&.schemas&.each { |schema| repo.schemas << schema }
254
+ end
255
+
256
+ repo.resolve_all_references
257
+ repo
258
+ end
259
+
260
+ # Load repository from LER package
261
+ # @param package_path [String] Path to .ler package file
262
+ # @return [Repository] Loaded repository
263
+ def self.from_package(package_path)
264
+ require_relative "../package/reader"
265
+ Package::Reader.load(package_path)
266
+ end
267
+
268
+ # Export repository to LER package
269
+ # @param output_path [String] Path for output .ler file
270
+ # @param options [Hash] Package options
271
+ # @option options [String] :name Package name
272
+ # @option options [String] :version Package version
273
+ # @option options [String] :description Package description
274
+ # @option options [String] :express_mode ('include_all') Bundling mode
275
+ # @option options [String] :resolution_mode ('resolved') Resolution mode
276
+ # @option options [String] :serialization_format ('marshal') Serialization format
277
+ # @return [String] Path to created package
278
+ def export_to_package(output_path, options = {})
279
+ require_relative "../package/builder"
280
+ builder = Package::Builder.new
281
+ builder.build(self, output_path, options)
282
+ end
283
+
284
+ private
285
+
286
+ # Count total elements in schema
287
+ # @param schema [Declarations::Schema] Schema to count elements from
288
+ # @return [Integer] Total count of all elements
289
+ def count_schema_elements(schema)
290
+ (schema.entities&.size || 0) +
291
+ (schema.types&.size || 0) +
292
+ (schema.functions&.size || 0) +
293
+ (schema.procedures&.size || 0) +
294
+ (schema.rules&.size || 0) +
295
+ (schema.constants&.size || 0)
296
+ end
297
+
298
+ # Ensure indexes are built before use
299
+ # @return [void]
300
+ def ensure_indexes_built
301
+ build_indexes if @entity_index.nil? || @entity_index.empty?
302
+ end
303
+
304
+ # Format output based on requested format
305
+ # @param items [Array] Items to format
306
+ # @param format [Symbol] Output format
307
+ # @yield [item] Block to convert item to hash
308
+ # @return [Array, String] Formatted output
309
+ def format_output(items, format, &block)
310
+ case format
311
+ when :hash
312
+ items.map(&block)
313
+ when :json
314
+ require "json"
315
+ items.map(&block).to_json
316
+ when :yaml
317
+ require "yaml"
318
+ items.map(&block).to_yaml
319
+ else
320
+ items
321
+ end
322
+ end
323
+
324
+ # Count entities by schema
325
+ # @return [Hash<String, Integer>] Entity counts per schema
326
+ def entities_by_schema_counts
327
+ counts = {}
328
+ schemas.each do |schema|
329
+ counts[schema.id] = schema.entities&.size || 0
330
+ end
331
+ counts
332
+ end
333
+
334
+ # Count types by category
335
+ # @return [Hash<Symbol, Integer>] Type counts per category
336
+ def types_by_category_counts
337
+ counts = {
338
+ select: 0,
339
+ enumeration: 0,
340
+ aggregate: 0,
341
+ defined: 0,
342
+ }
343
+
344
+ schemas.each do |schema|
345
+ next unless schema.types
346
+
347
+ schema.types.each do |type|
348
+ category = @type_index.categorize(type).to_sym
349
+ counts[category] ||= 0
350
+ counts[category] += 1
351
+ end
352
+ end
353
+
354
+ counts
355
+ end
356
+
357
+ # Count interfaces by type
358
+ # @return [Hash<Symbol, Integer>] Interface counts
359
+ def interface_counts
360
+ counts = {
361
+ use_from: 0,
362
+ reference_from: 0,
363
+ }
364
+
365
+ schemas.each do |schema|
366
+ next unless schema.interfaces
367
+
368
+ schema.interfaces.each do |interface|
369
+ if interface.kind == Declarations::Interface::USE
370
+ counts[:use_from] += 1
371
+ elsif interface.kind == Declarations::Interface::REFERENCE
372
+ counts[:reference_from] += 1
373
+ end
374
+ end
375
+ end
376
+
377
+ counts
378
+ end
379
+
380
+ # Count total entities across all schemas
381
+ # Uses indexes if available, falls back to schemas collection
382
+ # @return [Integer] Total entity count
383
+ def count_entities
384
+ return @entity_index.count if @entity_index && !@entity_index.empty?
385
+
386
+ schemas.sum { |schema| schema.entities&.size || 0 }
387
+ end
388
+
389
+ # Count total types across all schemas
390
+ # Uses indexes if available, falls back to schemas collection
391
+ # @return [Integer] Total type count
392
+ def count_types
393
+ return @type_index.count if @type_index && !@type_index.empty?
394
+
395
+ schemas.sum { |schema| schema.types&.size || 0 }
396
+ end
397
+
398
+ # Count total functions across all schemas
399
+ # @return [Integer] Total function count
400
+ def count_functions
401
+ schemas.sum { |schema| schema.functions&.size || 0 }
402
+ end
403
+
404
+ # Count total rules across all schemas
405
+ # @return [Integer] Total rule count
406
+ def count_rules
407
+ schemas.sum { |schema| schema.rules&.size || 0 }
408
+ end
409
+
410
+ # Count total procedures across all schemas
411
+ # @return [Integer] Total procedure count
412
+ def count_procedures
413
+ schemas.sum { |schema| schema.procedures&.size || 0 }
19
414
  end
20
415
  end
21
416
  end