expressir 2.1.30 → 2.2.0

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +99 -0
  3. data/.github/workflows/links.yml +100 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +11 -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 +267 -53
  10. data/Gemfile +2 -1
  11. data/README.adoc +993 -55
  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/formatter/formatter-architecture.adoc +401 -0
  27. data/docs/_guides/index.adoc +165 -0
  28. data/docs/_guides/ler/creating-packages.adoc +664 -0
  29. data/docs/_guides/ler/index.adoc +305 -0
  30. data/docs/_guides/ler/loading-packages.adoc +707 -0
  31. data/docs/_guides/ler/package-formats.adoc +748 -0
  32. data/docs/_guides/ler/querying-packages.adoc +826 -0
  33. data/docs/_guides/ler/validating-packages.adoc +750 -0
  34. data/docs/_guides/liquid/basic-templates.adoc +813 -0
  35. data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
  36. data/docs/_guides/liquid/drops-reference.adoc +829 -0
  37. data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
  38. data/docs/_guides/liquid/index.adoc +468 -0
  39. data/docs/_guides/manifests/creating-manifests.adoc +483 -0
  40. data/docs/_guides/manifests/index.adoc +307 -0
  41. data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
  42. data/docs/_guides/manifests/validating-manifests.adoc +713 -0
  43. data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
  44. data/docs/_guides/ruby-api/index.adoc +257 -0
  45. data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
  46. data/docs/_guides/ruby-api/search-engine.adoc +609 -0
  47. data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
  48. data/docs/_pages/data-model.adoc +665 -0
  49. data/docs/_pages/express-language.adoc +506 -0
  50. data/docs/_pages/getting-started.adoc +414 -0
  51. data/docs/_pages/index.adoc +116 -0
  52. data/docs/_pages/introduction.adoc +256 -0
  53. data/docs/_pages/ler-packages.adoc +837 -0
  54. data/docs/_pages/parsers.adoc +709 -0
  55. data/docs/_pages/schema-manifests.adoc +431 -0
  56. data/docs/_references/index.adoc +228 -0
  57. data/docs/_tutorials/creating-ler-package.adoc +735 -0
  58. data/docs/_tutorials/documentation-coverage.adoc +795 -0
  59. data/docs/_tutorials/index.adoc +221 -0
  60. data/docs/_tutorials/liquid-templates.adoc +806 -0
  61. data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
  62. data/docs/_tutorials/querying-schemas.adoc +751 -0
  63. data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
  64. data/docs/index.adoc +242 -0
  65. data/docs/lychee.toml +87 -0
  66. data/examples/demo_ler_usage.sh +86 -0
  67. data/examples/ler/README.md +111 -0
  68. data/examples/ler/simple_example.ler +0 -0
  69. data/examples/ler/simple_schema.exp +33 -0
  70. data/examples/ler_build.rb +75 -0
  71. data/examples/ler_cli.rb +79 -0
  72. data/examples/ler_demo_complete.rb +276 -0
  73. data/examples/ler_query.rb +91 -0
  74. data/examples/ler_query_examples.rb +305 -0
  75. data/examples/ler_stats.rb +81 -0
  76. data/examples/phase3_demo.rb +159 -0
  77. data/examples/query_demo_simple.rb +131 -0
  78. data/expressir.gemspec +4 -2
  79. data/lib/expressir/benchmark.rb +6 -6
  80. data/lib/expressir/cli.rb +21 -4
  81. data/lib/expressir/commands/format.rb +28 -0
  82. data/lib/expressir/commands/manifest.rb +427 -0
  83. data/lib/expressir/commands/package.rb +1274 -0
  84. data/lib/expressir/commands/validate.rb +70 -37
  85. data/lib/expressir/commands/validate_ascii.rb +607 -0
  86. data/lib/expressir/commands/validate_load.rb +88 -0
  87. data/lib/expressir/coverage.rb +15 -11
  88. data/lib/expressir/express/builder.rb +350 -0
  89. data/lib/expressir/express/builders/attribute_decl_builder.rb +38 -0
  90. data/lib/expressir/express/builders/built_in_builder.rb +88 -0
  91. data/lib/expressir/express/builders/constant_builder.rb +115 -0
  92. data/lib/expressir/express/builders/declaration_builder.rb +24 -0
  93. data/lib/expressir/express/builders/derive_clause_builder.rb +16 -0
  94. data/lib/expressir/express/builders/derived_attr_builder.rb +28 -0
  95. data/lib/expressir/express/builders/domain_rule_builder.rb +21 -0
  96. data/lib/expressir/express/builders/entity_decl_builder.rb +108 -0
  97. data/lib/expressir/express/builders/explicit_attr_builder.rb +52 -0
  98. data/lib/expressir/express/builders/expression_builder.rb +453 -0
  99. data/lib/expressir/express/builders/function_decl_builder.rb +84 -0
  100. data/lib/expressir/express/builders/helpers.rb +148 -0
  101. data/lib/expressir/express/builders/interface_builder.rb +171 -0
  102. data/lib/expressir/express/builders/inverse_attr_builder.rb +45 -0
  103. data/lib/expressir/express/builders/inverse_attr_type_builder.rb +36 -0
  104. data/lib/expressir/express/builders/inverse_clause_builder.rb +16 -0
  105. data/lib/expressir/express/builders/literal_builder.rb +107 -0
  106. data/lib/expressir/express/builders/procedure_decl_builder.rb +80 -0
  107. data/lib/expressir/express/builders/qualifier_builder.rb +128 -0
  108. data/lib/expressir/express/builders/reference_builder.rb +27 -0
  109. data/lib/expressir/express/builders/rule_decl_builder.rb +95 -0
  110. data/lib/expressir/express/builders/schema_body_decl_builder.rb +22 -0
  111. data/lib/expressir/express/builders/schema_decl_builder.rb +62 -0
  112. data/lib/expressir/express/builders/schema_version_builder.rb +40 -0
  113. data/lib/expressir/express/builders/simple_id_builder.rb +26 -0
  114. data/lib/expressir/express/builders/statement_builder.rb +250 -0
  115. data/lib/expressir/express/builders/subtype_constraint_builder.rb +188 -0
  116. data/lib/expressir/express/builders/syntax_builder.rb +19 -0
  117. data/lib/expressir/express/builders/token_builder.rb +15 -0
  118. data/lib/expressir/express/builders/type_builder.rb +264 -0
  119. data/lib/expressir/express/builders/type_decl_builder.rb +32 -0
  120. data/lib/expressir/express/builders/unique_clause_builder.rb +22 -0
  121. data/lib/expressir/express/builders/unique_rule_builder.rb +36 -0
  122. data/lib/expressir/express/builders/where_clause_builder.rb +22 -0
  123. data/lib/expressir/express/builders.rb +43 -0
  124. data/lib/expressir/express/error.rb +18 -2
  125. data/lib/expressir/express/formatter.rb +23 -1509
  126. data/lib/expressir/express/formatters/data_types_formatter.rb +317 -0
  127. data/lib/expressir/express/formatters/declarations_formatter.rb +689 -0
  128. data/lib/expressir/express/formatters/expressions_formatter.rb +160 -0
  129. data/lib/expressir/express/formatters/literals_formatter.rb +46 -0
  130. data/lib/expressir/express/formatters/references_formatter.rb +42 -0
  131. data/lib/expressir/express/formatters/remark_formatter.rb +296 -0
  132. data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
  133. data/lib/expressir/express/formatters/statements_formatter.rb +224 -0
  134. data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +48 -0
  135. data/lib/expressir/express/parser.rb +155 -7
  136. data/lib/expressir/express/pretty_formatter.rb +624 -0
  137. data/lib/expressir/express/remark_attacher.rb +1155 -0
  138. data/lib/expressir/express/resolve_references_model_visitor.rb +1 -0
  139. data/lib/expressir/express/streaming_builder.rb +467 -0
  140. data/lib/expressir/express/transformer/remark_handling.rb +196 -0
  141. data/lib/expressir/manifest/resolver.rb +213 -0
  142. data/lib/expressir/manifest/validator.rb +195 -0
  143. data/lib/expressir/model/declarations/entity.rb +6 -0
  144. data/lib/expressir/model/dependency_resolver.rb +270 -0
  145. data/lib/expressir/model/identifier.rb +1 -1
  146. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  147. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  148. data/lib/expressir/model/indexes/type_index.rb +149 -0
  149. data/lib/expressir/model/interface_validator.rb +384 -0
  150. data/lib/expressir/model/model_element.rb +30 -2
  151. data/lib/expressir/model/remark_info.rb +51 -0
  152. data/lib/expressir/model/repository.rb +400 -5
  153. data/lib/expressir/model/repository_validator.rb +295 -0
  154. data/lib/expressir/model/search_engine.rb +574 -0
  155. data/lib/expressir/model.rb +4 -94
  156. data/lib/expressir/package/builder.rb +200 -0
  157. data/lib/expressir/package/metadata.rb +81 -0
  158. data/lib/expressir/package/reader.rb +165 -0
  159. data/lib/expressir/schema_manifest.rb +11 -1
  160. data/lib/expressir/version.rb +1 -1
  161. data/lib/expressir.rb +20 -3
  162. metadata +168 -9
  163. data/docs/benchmarking.adoc +0 -107
  164. data/docs/liquid_drops.adoc +0 -1547
  165. data/lib/expressir/express/visitor.rb +0 -2815
@@ -0,0 +1,574 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Model
5
+ # SearchEngine for querying EXPRESS elements in a repository
6
+ # Handles pattern matching, wildcards, regex, and element collection
7
+ class SearchEngine
8
+ # Element types that can be searched
9
+ ELEMENT_TYPES = %w[
10
+ schema entity type attribute derived_attribute inverse_attribute
11
+ function procedure rule constant parameter variable
12
+ where_rule unique_rule enumeration_item interface
13
+ ].freeze
14
+
15
+ # Type categories for filtering
16
+ TYPE_CATEGORIES = %w[select enumeration aggregate defined].freeze
17
+
18
+ attr_reader :repository
19
+
20
+ # Initialize search engine with a repository
21
+ # @param repository [Repository] Repository to search
22
+ def initialize(repository)
23
+ @repository = repository
24
+ @repository.build_indexes if @repository.entity_index.nil?
25
+ end
26
+
27
+ # List all elements of a specific type
28
+ # @param type [String] Element type to list
29
+ # @param schema [String, nil] Filter by schema name
30
+ # @param category [String, nil] Type category filter (for type elements)
31
+ # @return [Array<Hash>] List of elements
32
+ def list(type:, schema: nil, category: nil)
33
+ elements = collect_elements(type: type, schema: schema,
34
+ category: category)
35
+ elements.map { |elem| element_to_hash(elem, type) }
36
+ end
37
+
38
+ # Search for elements matching a pattern
39
+ # @param pattern [String] Search pattern
40
+ # @param type [String, nil] Filter by element type
41
+ # @param schema [String, nil] Limit to specific schema
42
+ # @param category [String, nil] Type category filter
43
+ # @param case_sensitive [Boolean] Enable case-sensitive matching
44
+ # @param regex [Boolean] Treat pattern as regex
45
+ # @param exact [Boolean] Exact match only
46
+ # @return [Array<Hash>] Matching elements
47
+ def search(pattern:, type: nil, schema: nil, category: nil,
48
+ case_sensitive: false, regex: false, exact: false)
49
+ # Parse pattern
50
+ pattern_parts = parse_pattern(pattern, case_sensitive: case_sensitive)
51
+
52
+ # Determine types to search
53
+ types_to_search = type ? [type] : ELEMENT_TYPES
54
+
55
+ # Collect all matching elements
56
+ results = []
57
+ types_to_search.each do |elem_type|
58
+ elements = collect_elements(type: elem_type, schema: schema,
59
+ category: category)
60
+
61
+ elements.each do |elem|
62
+ if matches_pattern?(elem, pattern_parts, elem_type,
63
+ case_sensitive: case_sensitive,
64
+ regex: regex, exact: exact)
65
+ results << element_to_hash(elem, elem_type)
66
+ end
67
+ end
68
+ end
69
+
70
+ results
71
+ end
72
+
73
+ # Count elements matching criteria
74
+ # @param type [String] Element type
75
+ # @param schema [String, nil] Filter by schema
76
+ # @param category [String, nil] Type category filter
77
+ # @return [Integer] Count of elements
78
+ def count(type:, schema: nil, category: nil)
79
+ collect_elements(type: type, schema: schema, category: category).size
80
+ end
81
+
82
+ # Search with depth filtering
83
+ # @param pattern [String] Search pattern
84
+ # @param max_depth [Integer] Maximum path depth (1=schema, 2=entity, 3=attribute)
85
+ # @param options [Hash] Additional search options
86
+ # @return [Array<Hash>] Filtered results
87
+ def search_with_depth(pattern:, max_depth:, **options)
88
+ return [] if max_depth <= 0
89
+
90
+ results = search(pattern: pattern, **options)
91
+ filter_by_depth(results, max_depth)
92
+ end
93
+
94
+ # Search with relevance ranking
95
+ # @param pattern [String] Search pattern
96
+ # @param boost_exact [Integer] Score boost for exact matches
97
+ # @param boost_prefix [Integer] Score boost for prefix matches
98
+ # @param options [Hash] Additional search options
99
+ # @return [Array<Hash>] Ranked results with :relevance_score
100
+ def search_ranked(pattern:, boost_exact: 10, boost_prefix: 5, **options)
101
+ results = search(pattern: pattern, **options)
102
+ rank_results(results, pattern, boost_exact, boost_prefix)
103
+ end
104
+
105
+ # Advanced search with multiple filters
106
+ # @param pattern [String] Search pattern
107
+ # @param max_depth [Integer, nil] Maximum path depth
108
+ # @param ranked [Boolean] Enable relevance ranking
109
+ # @param options [Hash] Additional filters and search options
110
+ # @return [Array<Hash>] Filtered and optionally ranked results
111
+ def search_advanced(pattern:, max_depth: nil, ranked: false, **options)
112
+ results = search(pattern: pattern, **options)
113
+
114
+ results = filter_by_depth(results, max_depth) if max_depth
115
+ results = rank_results(results, pattern, 10, 5) if ranked
116
+
117
+ results
118
+ end
119
+
120
+ private
121
+
122
+ # Parse search pattern into components
123
+ # @param pattern [String] Pattern to parse
124
+ # @param case_sensitive [Boolean] Case sensitivity
125
+ # @return [Hash] Parsed pattern with parts
126
+ def parse_pattern(pattern, case_sensitive: false)
127
+ normalized = case_sensitive ? pattern : pattern.downcase
128
+ parts = normalized.split(".")
129
+
130
+ {
131
+ raw: pattern,
132
+ normalized: normalized,
133
+ parts: parts,
134
+ schema_part: parts[0],
135
+ element_parts: parts[1..] || [],
136
+ }
137
+ end
138
+
139
+ # Check if element matches pattern
140
+ # @param element [ModelElement] Element to check
141
+ # @param pattern_parts [Hash] Parsed pattern
142
+ # @param element_type [String] Type of element
143
+ # @param case_sensitive [Boolean] Case sensitivity
144
+ # @param regex [Boolean] Use regex matching
145
+ # @param exact [Boolean] Exact match only
146
+ # @return [Boolean] True if matches
147
+ def matches_pattern?(element, pattern_parts, _element_type,
148
+ case_sensitive: false, regex: false, exact: false)
149
+ # Duck typing - check if element responds to path/id
150
+ element_path = begin
151
+ element.path
152
+ rescue StandardError
153
+ element.id
154
+ end
155
+ return false unless element_path
156
+
157
+ element_id = begin
158
+ element.id
159
+ rescue StandardError
160
+ nil
161
+ end
162
+
163
+ search_path = case_sensitive ? element_path : element_path.downcase
164
+ search_id = element_id && !case_sensitive ? element_id.downcase : element_id
165
+ pattern = pattern_parts[:normalized]
166
+
167
+ if regex
168
+ match_regex(search_path,
169
+ pattern) || (search_id && match_regex(search_id, pattern))
170
+ elsif exact
171
+ search_path == pattern
172
+ elsif pattern_parts[:parts].size == 1
173
+ # For simple patterns (no dots or single part), also match against just the element ID
174
+ match_wildcard(search_path, pattern_parts) ||
175
+ (search_id && match_part(search_id, pattern))
176
+ else
177
+ match_wildcard(search_path, pattern_parts)
178
+ end
179
+ end
180
+
181
+ # Match using regex
182
+ # @param path [String] Element path
183
+ # @param pattern [String] Regex pattern
184
+ # @return [Boolean] True if matches
185
+ def match_regex(path, pattern)
186
+ Regexp.new(pattern).match?(path)
187
+ rescue RegexpError
188
+ false
189
+ end
190
+
191
+ # Match using wildcard pattern
192
+ # @param path [String] Element path
193
+ # @param pattern_parts [Hash] Parsed pattern
194
+ # @return [Boolean] True if matches
195
+ def match_wildcard(path, pattern_parts)
196
+ path_parts = path.split(".")
197
+ pattern_array = pattern_parts[:parts]
198
+
199
+ # Handle different wildcard scenarios
200
+ match_wildcard_parts(path_parts, pattern_array)
201
+ end
202
+
203
+ # Match path parts against pattern parts with wildcards
204
+ # @param path_parts [Array<String>] Path components
205
+ # @param pattern_parts [Array<String>] Pattern components
206
+ # @return [Boolean] True if matches
207
+ def match_wildcard_parts(path_parts, pattern_parts)
208
+ # If pattern has no wildcards and different length, no match
209
+ unless pattern_parts.include?("*")
210
+ return false if path_parts.size != pattern_parts.size
211
+
212
+ return path_parts.zip(pattern_parts).all? do |path_part, pattern_part|
213
+ match_part(path_part, pattern_part)
214
+ end
215
+ end
216
+
217
+ # Handle wildcards
218
+ match_with_wildcards(path_parts, pattern_parts)
219
+ end
220
+
221
+ # Match individual part with potential prefix/suffix wildcards
222
+ # @param part [String] Path part
223
+ # @param pattern [String] Pattern part
224
+ # @return [Boolean] True if matches
225
+ def match_part(part, pattern)
226
+ return true if pattern == "*"
227
+
228
+ if pattern.include?("*")
229
+ # Convert wildcard to regex
230
+ regex_pattern = "^#{Regexp.escape(pattern).gsub('\\*', '.*')}$"
231
+ Regexp.new(regex_pattern).match?(part)
232
+ else
233
+ # Support substring matching for simple patterns
234
+ part.include?(pattern)
235
+ end
236
+ end
237
+
238
+ # Match path with wildcard patterns
239
+ # @param path_parts [Array<String>] Path components
240
+ # @param pattern_parts [Array<String>] Pattern components with wildcards
241
+ # @return [Boolean] True if matches
242
+ def match_with_wildcards(path_parts, pattern_parts)
243
+ # Simple implementation: match each level
244
+ # If pattern is shorter but has *, try to match flexibly
245
+
246
+ pi = 0 # pattern index
247
+ li = 0 # path index
248
+
249
+ while pi < pattern_parts.size && li < path_parts.size
250
+ if pattern_parts[pi] == "*"
251
+ # Wildcard matches one element
252
+ pi += 1
253
+ li += 1
254
+ elsif match_part(path_parts[li], pattern_parts[pi])
255
+ pi += 1
256
+ li += 1
257
+ else
258
+ return false
259
+ end
260
+ end
261
+
262
+ # Check if we consumed both arrays
263
+ pi == pattern_parts.size && li == path_parts.size
264
+ end
265
+
266
+ # Collect elements of a specific type
267
+ # @param type [String] Element type
268
+ # @param schema [String, nil] Schema filter
269
+ # @param category [String, nil] Category filter
270
+ # @return [Array<ModelElement>] Collected elements
271
+ def collect_elements(type:, schema: nil, category: nil)
272
+ # Guard against nil schemas collection
273
+ return [] unless @repository.schemas
274
+
275
+ schemas_to_search = if schema
276
+ [@repository.schemas.find do |s|
277
+ s.id == schema
278
+ end].compact
279
+ else
280
+ @repository.schemas
281
+ end
282
+
283
+ case type
284
+ when "schema"
285
+ schemas_to_search
286
+ when "entity"
287
+ collect_from_schemas(schemas_to_search, :entities)
288
+ when "type"
289
+ types = collect_from_schemas(schemas_to_search, :types)
290
+ category ? filter_types_by_category(types, category) : types
291
+ when "attribute"
292
+ collect_attributes(schemas_to_search)
293
+ when "derived_attribute"
294
+ collect_derived_attributes(schemas_to_search)
295
+ when "inverse_attribute"
296
+ collect_inverse_attributes(schemas_to_search)
297
+ when "function"
298
+ collect_from_schemas(schemas_to_search, :functions)
299
+ when "procedure"
300
+ collect_from_schemas(schemas_to_search, :procedures)
301
+ when "rule"
302
+ collect_from_schemas(schemas_to_search, :rules)
303
+ when "constant"
304
+ collect_from_schemas(schemas_to_search, :constants)
305
+ when "parameter"
306
+ collect_parameters(schemas_to_search)
307
+ when "variable"
308
+ collect_variables(schemas_to_search)
309
+ when "where_rule"
310
+ collect_where_rules(schemas_to_search)
311
+ when "unique_rule"
312
+ collect_unique_rules(schemas_to_search)
313
+ when "enumeration_item"
314
+ collect_enumeration_items(schemas_to_search)
315
+ when "interface"
316
+ collect_from_schemas(schemas_to_search, :interfaces)
317
+ else
318
+ []
319
+ end
320
+ end
321
+
322
+ # Collect elements from schemas using a method
323
+ # @param schemas [Array<Schema>] Schemas to search
324
+ # @param method [Symbol] Method to call on schema
325
+ # @return [Array] Collected elements
326
+ def collect_from_schemas(schemas, method)
327
+ # Guard against nil schemas array
328
+ return [] unless schemas
329
+
330
+ schemas.flat_map { |s| s.send(method) || [] }
331
+ end
332
+
333
+ # Collect all attributes from entities
334
+ # @param schemas [Array<Schema>] Schemas to search
335
+ # @return [Array<Attribute>] All attributes
336
+ def collect_attributes(schemas)
337
+ # Guard against nil schemas
338
+ return [] unless schemas
339
+
340
+ entities = collect_from_schemas(schemas, :entities)
341
+ entities.flat_map { |e| e.attributes || [] }.select do |attr|
342
+ !attr.is_a?(Declarations::DerivedAttribute) &&
343
+ !attr.is_a?(Declarations::InverseAttribute)
344
+ end
345
+ end
346
+
347
+ # Collect derived attributes from entities
348
+ # @param schemas [Array<Schema>] Schemas to search
349
+ # @return [Array<DerivedAttribute>] Derived attributes
350
+ def collect_derived_attributes(schemas)
351
+ # Guard against nil schemas
352
+ return [] unless schemas
353
+
354
+ entities = collect_from_schemas(schemas, :entities)
355
+ entities.flat_map { |e| e.attributes || [] }.select do |attr|
356
+ attr.is_a?(Declarations::DerivedAttribute)
357
+ end
358
+ end
359
+
360
+ # Collect inverse attributes from entities
361
+ # @param schemas [Array<Schema>] Schemas to search
362
+ # @return [Array<InverseAttribute>] Inverse attributes
363
+ def collect_inverse_attributes(schemas)
364
+ # Guard against nil schemas
365
+ return [] unless schemas
366
+
367
+ entities = collect_from_schemas(schemas, :entities)
368
+ entities.flat_map { |e| e.attributes || [] }.select do |attr|
369
+ attr.is_a?(Declarations::InverseAttribute)
370
+ end
371
+ end
372
+
373
+ # Collect parameters from functions and procedures
374
+ # @param schemas [Array<Schema>] Schemas to search
375
+ # @return [Array<Parameter>] All parameters
376
+ def collect_parameters(schemas)
377
+ # Guard against nil schemas
378
+ return [] unless schemas
379
+
380
+ functions = collect_from_schemas(schemas, :functions)
381
+ procedures = collect_from_schemas(schemas, :procedures)
382
+ (functions + procedures).flat_map { |f| f.parameters || [] }
383
+ end
384
+
385
+ # Collect variables from functions and procedures
386
+ # @param schemas [Array<Schema>] Schemas to search
387
+ # @return [Array<Variable>] All variables
388
+ def collect_variables(schemas)
389
+ # Guard against nil schemas
390
+ return [] unless schemas
391
+
392
+ functions = collect_from_schemas(schemas, :functions)
393
+ procedures = collect_from_schemas(schemas, :procedures)
394
+ (functions + procedures).flat_map { |f| f.variables || [] }
395
+ end
396
+
397
+ # Collect where rules from entities and types
398
+ # @param schemas [Array<Schema>] Schemas to search
399
+ # @return [Array<WhereRule>] All where rules
400
+ def collect_where_rules(schemas)
401
+ # Guard against nil schemas
402
+ return [] unless schemas
403
+
404
+ entities = collect_from_schemas(schemas, :entities)
405
+ types = collect_from_schemas(schemas, :types)
406
+ (entities + types).flat_map { |e| e.where_rules || [] }
407
+ end
408
+
409
+ # Collect unique rules from entities
410
+ # @param schemas [Array<Schema>] Schemas to search
411
+ # @return [Array<UniqueRule>] All unique rules
412
+ def collect_unique_rules(schemas)
413
+ # Guard against nil schemas
414
+ return [] unless schemas
415
+
416
+ entities = collect_from_schemas(schemas, :entities)
417
+ entities.flat_map { |e| e.unique_rules || [] }
418
+ end
419
+
420
+ # Collect enumeration items from enumeration types
421
+ # @param schemas [Array<Schema>] Schemas to search
422
+ # @return [Array<EnumerationItem>] All enumeration items
423
+ def collect_enumeration_items(schemas)
424
+ # Guard against nil schemas
425
+ return [] unless schemas
426
+
427
+ types = collect_from_schemas(schemas, :types)
428
+ types.flat_map { |t| t.enumeration_items || [] }
429
+ end
430
+
431
+ # Filter types by category
432
+ # @param types [Array<Type>] Types to filter
433
+ # @param category [String] Category to filter by
434
+ # @return [Array<Type>] Filtered types
435
+ def filter_types_by_category(types, category)
436
+ types.select do |type|
437
+ @repository.type_index.categorize(type) == category
438
+ end
439
+ end
440
+
441
+ # Convert element to hash representation
442
+ # @param element [ModelElement] Element to convert
443
+ # @param type [String] Element type
444
+ # @return [Hash] Element data
445
+ def element_to_hash(element, type)
446
+ # Duck typing - try to get id/path from element
447
+ element_id = begin
448
+ element.id
449
+ rescue StandardError
450
+ nil
451
+ end
452
+
453
+ element_path = begin
454
+ element.path
455
+ rescue StandardError
456
+ nil
457
+ end
458
+
459
+ hash = {
460
+ id: element_id,
461
+ type: type,
462
+ path: element_path,
463
+ }
464
+
465
+ # Add schema for non-schema elements
466
+ if type != "schema"
467
+ begin
468
+ if element.parent
469
+ schema = find_parent_schema(element)
470
+ hash[:schema] = schema.id if schema
471
+ end
472
+ rescue StandardError
473
+ # Element doesn't have parent or parent chain
474
+ end
475
+ end
476
+
477
+ # Add category for types
478
+ if type == "type"
479
+ hash[:category] = @repository.type_index.categorize(element)
480
+ end
481
+
482
+ hash
483
+ end
484
+
485
+ # Find parent schema of an element
486
+ # @param element [ModelElement] Element to find schema for
487
+ # @return [Schema, nil] Parent schema
488
+ def find_parent_schema(element)
489
+ current = element
490
+ while current
491
+ # Duck typing - check if this looks like a schema
492
+ # A schema has no parent or is explicitly a Schema type
493
+ begin
494
+ # First check using is_a? for real objects
495
+ if current.is_a?(Declarations::Schema)
496
+ return current
497
+ end
498
+
499
+ # For mock objects, check if parent is nil and has id (likely a schema)
500
+ if !current.respond_to?(:parent) || current.parent.nil?
501
+ # This might be a schema - check if it has an id
502
+ if current.respond_to?(:id) && current.id
503
+ return current
504
+ end
505
+ end
506
+ rescue StandardError
507
+ # Ignore errors from type checking
508
+ end
509
+
510
+ current = begin
511
+ current.parent
512
+ rescue StandardError
513
+ nil
514
+ end
515
+ end
516
+ nil
517
+ end
518
+
519
+ # Filter results by maximum path depth
520
+ # @param results [Array<Hash>] Search results
521
+ # @param max_depth [Integer] Maximum depth
522
+ # @return [Array<Hash>] Filtered results
523
+ def filter_by_depth(results, max_depth)
524
+ results.select do |result|
525
+ next true unless result[:path]
526
+
527
+ result[:path].split(".").size <= max_depth
528
+ end
529
+ end
530
+
531
+ # Rank results by relevance
532
+ # @param results [Array<Hash>] Search results
533
+ # @param pattern [String] Search pattern
534
+ # @param boost_exact [Integer] Exact match boost
535
+ # @param boost_prefix [Integer] Prefix match boost
536
+ # @return [Array<Hash>] Sorted results with scores
537
+ def rank_results(results, pattern, boost_exact, boost_prefix)
538
+ normalized_pattern = pattern.downcase
539
+
540
+ results.map do |result|
541
+ score = calculate_relevance_score(result, normalized_pattern,
542
+ boost_exact, boost_prefix)
543
+ result.merge(relevance_score: score)
544
+ end.sort_by { |r| -r[:relevance_score] }
545
+ end
546
+
547
+ # Calculate relevance score for a result
548
+ # @param result [Hash] Search result
549
+ # @param pattern [String] Normalized search pattern
550
+ # @param boost_exact [Integer] Exact match boost
551
+ # @param boost_prefix [Integer] Prefix match boost
552
+ # @return [Integer] Relevance score
553
+ def calculate_relevance_score(result, pattern, boost_exact, boost_prefix)
554
+ score = 0
555
+ id = (result[:id] || "").downcase
556
+
557
+ # Exact match gets highest score
558
+ score += boost_exact if id == pattern
559
+
560
+ # Prefix match gets medium score
561
+ score += boost_prefix if id.start_with?(pattern)
562
+
563
+ # Shorter paths rank higher (max 10 points)
564
+ path_depth = result[:path]&.split(".")&.size || 1
565
+ score += [10 - path_depth, 0].max
566
+
567
+ # Schema-level results rank higher
568
+ score += 5 if result[:type] == "schema"
569
+
570
+ score
571
+ end
572
+ end
573
+ end
574
+ end
@@ -1,95 +1,5 @@
1
- # This file is kept for backward compatibility and to ensure all model classes are loaded
2
- # when explicitly requiring 'expressir/model'. The actual autoload definitions are in the main
3
- # expressir.rb file.
1
+ # frozen_string_literal: true
4
2
 
5
- # Ensure all model classes are loaded by referencing them
6
- # This triggers the autoload mechanism for each class
7
-
8
- # Core model classes
9
- Expressir::Model::ModelElement
10
- Expressir::Model::Cache
11
- Expressir::Model::Identifier
12
- Expressir::Model::Repository
13
-
14
- # Data types
15
- Expressir::Model::DataTypes::Aggregate
16
- Expressir::Model::DataTypes::Array
17
- Expressir::Model::DataTypes::Bag
18
- Expressir::Model::DataTypes::Binary
19
- Expressir::Model::DataTypes::Boolean
20
- Expressir::Model::DataTypes::Enumeration
21
- Expressir::Model::DataTypes::EnumerationItem
22
- Expressir::Model::DataTypes::GenericEntity
23
- Expressir::Model::DataTypes::Generic
24
- Expressir::Model::DataTypes::Integer
25
- Expressir::Model::DataTypes::List
26
- Expressir::Model::DataTypes::Logical
27
- Expressir::Model::DataTypes::Number
28
- Expressir::Model::DataTypes::Real
29
- Expressir::Model::DataTypes::Select
30
- Expressir::Model::DataTypes::Set
31
- Expressir::Model::DataTypes::String
32
-
33
- # Declarations
34
- Expressir::Model::Declarations::Attribute
35
- Expressir::Model::Declarations::Constant
36
- Expressir::Model::Declarations::DerivedAttribute
37
- Expressir::Model::Declarations::Entity
38
- Expressir::Model::Declarations::Function
39
- Expressir::Model::Declarations::Interface
40
- Expressir::Model::Declarations::InterfaceItem
41
- Expressir::Model::Declarations::InterfacedItem
42
- Expressir::Model::Declarations::InverseAttribute
43
- Expressir::Model::Declarations::Parameter
44
- Expressir::Model::Declarations::Procedure
45
- Expressir::Model::Declarations::RemarkItem
46
- Expressir::Model::Declarations::Rule
47
- Expressir::Model::Declarations::Schema
48
- Expressir::Model::Declarations::SchemaVersion
49
- Expressir::Model::Declarations::SchemaVersionItem
50
- Expressir::Model::Declarations::SubtypeConstraint
51
- Expressir::Model::Declarations::Type
52
- Expressir::Model::Declarations::UniqueRule
53
- Expressir::Model::Declarations::Variable
54
- Expressir::Model::Declarations::WhereRule
55
-
56
- # Expressions
57
- Expressir::Model::Expressions::AggregateInitializer
58
- Expressir::Model::Expressions::AggregateInitializerItem
59
- Expressir::Model::Expressions::BinaryExpression
60
- Expressir::Model::Expressions::EntityConstructor
61
- Expressir::Model::Expressions::FunctionCall
62
- Expressir::Model::Expressions::Interval
63
- Expressir::Model::Expressions::QueryExpression
64
- Expressir::Model::Expressions::UnaryExpression
65
-
66
- # Literals
67
- Expressir::Model::Literals::Binary
68
- Expressir::Model::Literals::Integer
69
- Expressir::Model::Literals::Logical
70
- Expressir::Model::Literals::Real
71
- Expressir::Model::Literals::String
72
-
73
- # References
74
- Expressir::Model::References::AttributeReference
75
- Expressir::Model::References::GroupReference
76
- Expressir::Model::References::IndexReference
77
- Expressir::Model::References::SimpleReference
78
-
79
- # Statements
80
- Expressir::Model::Statements::Alias
81
- Expressir::Model::Statements::Assignment
82
- Expressir::Model::Statements::Case
83
- Expressir::Model::Statements::CaseAction
84
- Expressir::Model::Statements::Compound
85
- Expressir::Model::Statements::Escape
86
- Expressir::Model::Statements::If
87
- Expressir::Model::Statements::Null
88
- Expressir::Model::Statements::ProcedureCall
89
- Expressir::Model::Statements::Repeat
90
- Expressir::Model::Statements::Return
91
- Expressir::Model::Statements::Skip
92
-
93
- # Supertype expressions
94
- Expressir::Model::SupertypeExpressions::BinarySupertypeExpression
95
- Expressir::Model::SupertypeExpressions::OneofSupertypeExpression
3
+ # This file is kept for backward compatibility when explicitly requiring 'expressir/model'
4
+ # The actual autoload definitions are in lib/expressir.rb
5
+ # This file does nothing - autoload handles everything automatically