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,295 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "interface_validator"
4
+
5
+ module Expressir
6
+ module Model
7
+ # Validator for repository consistency checks
8
+ # Handles schema reference validation, interface item validation,
9
+ # circular dependency detection, and unused schema warnings
10
+ # Coordinates with InterfaceValidator for detailed interface validation
11
+ class RepositoryValidator
12
+ # Initialize validator with schemas and reference index
13
+ # @param schemas [Array<Declarations::Schema>] Schemas to validate
14
+ # @param reference_index [Indexes::ReferenceIndex] Reference index for dependency tracking
15
+ def initialize(schemas, reference_index)
16
+ @schemas = schemas
17
+ @reference_index = reference_index
18
+ end
19
+
20
+ # Validate repository consistency
21
+ # @param strict [Boolean] Enable strict validation (unused schemas)
22
+ # @param check_interfaces [Boolean] Enable detailed interface validation
23
+ # @param check_completeness [Boolean] Check for schema completeness
24
+ # @param detailed [Boolean] Generate detailed reports
25
+ # @param check_duplicates [Boolean] Check for duplicate interface aliases
26
+ # @param check_self_references [Boolean] Check for self-referencing interfaces
27
+ # @return [Hash] Validation results with :valid?, :errors, :warnings
28
+ def validate(
29
+ strict: false,
30
+ check_interfaces: false,
31
+ check_completeness: false,
32
+ detailed: false,
33
+ check_duplicates: false,
34
+ check_self_references: false
35
+ )
36
+ errors = []
37
+ warnings = []
38
+
39
+ # Existing basic validations (always run)
40
+ validate_schema_references(errors)
41
+ validate_interface_items(errors)
42
+ detect_circular_dependencies(warnings)
43
+
44
+ if strict
45
+ detect_unused_schemas(warnings)
46
+ end
47
+
48
+ # NEW: Detailed interface validation
49
+ interface_result = nil
50
+ if check_interfaces
51
+ begin
52
+ interface_result = run_interface_validation(
53
+ detailed: detailed,
54
+ check_duplicates: check_duplicates,
55
+ check_self_references: check_self_references,
56
+ )
57
+
58
+ # Merge results, avoiding duplicates
59
+ merge_validation_results(errors, warnings, interface_result)
60
+ rescue StandardError => e
61
+ # If interface validation fails, add a warning instead of crashing
62
+ warnings << {
63
+ type: :interface_validation_error,
64
+ message: "Could not perform detailed interface validation: #{e.message}",
65
+ }
66
+ end
67
+ end
68
+
69
+ # NEW: Completeness checks
70
+ if check_completeness
71
+ check_schema_completeness(warnings)
72
+ end
73
+
74
+ result = {
75
+ valid?: errors.empty?,
76
+ errors: errors,
77
+ warnings: warnings,
78
+ }
79
+
80
+ # Include detailed report if requested and available
81
+ if detailed && interface_result && interface_result[:interface_report]
82
+ result[:interface_report] = interface_result[:interface_report]
83
+ end
84
+
85
+ result
86
+ end
87
+
88
+ private
89
+
90
+ # Run InterfaceValidator
91
+ # @param options [Hash] Validation options
92
+ # @return [Hash] Interface validation results
93
+ def run_interface_validation(options = {})
94
+ # Build temporary repository for InterfaceValidator
95
+ repository = build_temporary_repository
96
+ interface_validator = InterfaceValidator.new(repository)
97
+ interface_validator.validate(options)
98
+ end
99
+
100
+ # Build a temporary repository from schemas
101
+ # @return [Repository] Temporary repository
102
+ def build_temporary_repository
103
+ # Find the parent repository if schemas have one
104
+ repo = @schemas.first&.parent
105
+ return repo if repo.is_a?(Repository)
106
+
107
+ # Create temporary repository
108
+ temp_repo = Repository.new
109
+ temp_repo.schemas = @schemas
110
+ temp_repo
111
+ end
112
+
113
+ # Merge interface validation results, avoiding duplicates
114
+ # @param errors [Array] Existing errors
115
+ # @param warnings [Array] Existing warnings
116
+ # @param interface_result [Hash] Interface validation results
117
+ # @return [void]
118
+ def merge_validation_results(errors, warnings, interface_result)
119
+ # Add interface errors, avoiding duplicates
120
+ interface_result[:errors].each do |error|
121
+ # Check if this error type already exists for the same schema/item
122
+ unless errors.any? { |e| error_matches?(e, error) }
123
+ errors << error
124
+ end
125
+ end
126
+
127
+ # Add interface warnings, avoiding duplicates
128
+ interface_result[:warnings].each do |warning|
129
+ unless warnings.any? { |w| warning_matches?(w, warning) }
130
+ warnings << warning
131
+ end
132
+ end
133
+ end
134
+
135
+ # Check if two errors match (to avoid duplicates)
136
+ # @param e1 [Hash] First error
137
+ # @param e2 [Hash] Second error
138
+ # @return [Boolean] True if errors match
139
+ def error_matches?(e1, e2)
140
+ e1[:type] == e2[:type] &&
141
+ e1[:schema] == e2[:schema] &&
142
+ e1[:referenced_schema] == e2[:referenced_schema] &&
143
+ e1[:item] == e2[:item]
144
+ end
145
+
146
+ # Check if two warnings match (to avoid duplicates)
147
+ # @param w1 [Hash] First warning
148
+ # @param w2 [Hash] Second warning
149
+ # @return [Boolean] True if warnings match
150
+ def warning_matches?(w1, w2)
151
+ w1[:type] == w2[:type] &&
152
+ w1[:schema] == w2[:schema]
153
+ end
154
+
155
+ # Check schema completeness
156
+ # @param warnings [Array] Accumulator for warnings
157
+ # @return [void]
158
+ def check_schema_completeness(warnings)
159
+ @schemas.each do |schema|
160
+ # Warn if schema has no entities
161
+ if schema.entities.nil? || schema.entities.empty?
162
+ warnings << {
163
+ type: :empty_schema,
164
+ schema: schema.id,
165
+ message: "Schema '#{schema.id}' has no entities defined",
166
+ }
167
+ end
168
+
169
+ # Warn if schema has entities but no types
170
+ if schema.entities&.any? && (schema.types.nil? || schema.types.empty?)
171
+ warnings << {
172
+ type: :no_types,
173
+ schema: schema.id,
174
+ message: "Schema '#{schema.id}' has entities but no types defined (common but may indicate incomplete schema)",
175
+ }
176
+ end
177
+ end
178
+ end
179
+
180
+ # Validate that all referenced schemas exist
181
+ # @param errors [Array] Accumulator for errors
182
+ # @return [void]
183
+ def validate_schema_references(errors)
184
+ @schemas.each do |schema|
185
+ next unless schema.interfaces
186
+
187
+ schema.interfaces.each do |interface|
188
+ referenced_schema = find_schema(interface.schema.id)
189
+
190
+ if referenced_schema.nil?
191
+ errors << {
192
+ type: :missing_schema_reference,
193
+ schema: schema.id,
194
+ interface_type: interface.kind,
195
+ referenced_schema: interface.schema.id,
196
+ message: "Schema '#{schema.id}' references non-existent schema '#{interface.schema.id}'",
197
+ }
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # Validate that all interface items exist in referenced schemas
204
+ # @param errors [Array] Accumulator for errors
205
+ # @return [void]
206
+ def validate_interface_items(errors)
207
+ @schemas.each do |schema|
208
+ next unless schema.interfaces
209
+
210
+ schema.interfaces.each do |interface|
211
+ referenced_schema = find_schema(interface.schema.id)
212
+ next if referenced_schema.nil? # Already caught by validate_schema_references
213
+
214
+ interface.items.each do |item|
215
+ ref_id = item.ref.id.safe_downcase
216
+ found = item_exists_in_schema?(referenced_schema, ref_id)
217
+
218
+ unless found
219
+ errors << {
220
+ type: :missing_interface_item,
221
+ schema: schema.id,
222
+ interface_type: interface.kind,
223
+ referenced_schema: interface.schema.id,
224
+ item: item.ref.id,
225
+ message: "Interface item '#{item.ref.id}' not found in schema '#{interface.schema.id}'",
226
+ }
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ # Detect circular dependencies using the reference index
234
+ # Note: Circular dependencies are valid in EXPRESS (mutual USE FROM)
235
+ # @param warnings [Array] Accumulator for warnings
236
+ # @return [void]
237
+ def detect_circular_dependencies(warnings)
238
+ cycles = @reference_index.detect_circular_dependencies
239
+
240
+ cycles.each do |cycle|
241
+ warnings << {
242
+ type: :circular_dependency,
243
+ cycle: cycle,
244
+ message: "Circular dependency (valid in EXPRESS): #{cycle.join(' -> ')}",
245
+ }
246
+ end
247
+ end
248
+
249
+ # Detect unused schemas in strict mode
250
+ # @param warnings [Array] Accumulator for warnings
251
+ # @return [void]
252
+ def detect_unused_schemas(warnings)
253
+ # Don't warn for single schema repositories
254
+ return if @schemas.size == 1
255
+
256
+ used_schemas = Set.new
257
+ @schemas.each do |schema|
258
+ next unless schema.interfaces
259
+
260
+ schema.interfaces.each do |interface|
261
+ used_schemas << interface.schema.id.safe_downcase
262
+ end
263
+
264
+ schema_id = schema.id.safe_downcase
265
+ next if used_schemas.include?(schema_id)
266
+
267
+ warnings << {
268
+ type: :unused_schema,
269
+ schema: schema.id,
270
+ message: "Schema '#{schema.id}' is not referenced by any other schema",
271
+ }
272
+ end
273
+ end
274
+
275
+ # Find schema by name
276
+ # @param schema_name [String] Schema name
277
+ # @return [Declarations::Schema, nil] Found schema or nil
278
+ def find_schema(schema_name)
279
+ @schemas.find { |s| s.id.safe_downcase == schema_name.safe_downcase }
280
+ end
281
+
282
+ # Check if an item exists in a schema
283
+ # @param schema [Declarations::Schema] Schema to search
284
+ # @param ref_id [String] Item reference ID (normalized)
285
+ # @return [Boolean] True if item exists
286
+ def item_exists_in_schema?(schema, ref_id)
287
+ schema.entities&.any? { |e| e.id.safe_downcase == ref_id } ||
288
+ schema.types&.any? { |t| t.id.safe_downcase == ref_id } ||
289
+ schema.functions&.any? { |f| f.id.safe_downcase == ref_id } ||
290
+ schema.procedures&.any? { |p| p.id.safe_downcase == ref_id } ||
291
+ schema.constants&.any? { |c| c.id.safe_downcase == ref_id }
292
+ end
293
+ end
294
+ end
295
+ end