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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +209 -55
- data/Gemfile +2 -1
- data/README.adoc +650 -83
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/changes/schema_change.rb +32 -22
- data/lib/expressir/changes/{edition_change.rb → version_change.rb} +3 -3
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/changes_import_eengine.rb +2 -2
- data/lib/expressir/commands/changes_validate.rb +1 -1
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +16 -3
- metadata +115 -5
- data/docs/benchmarking.adoc +0 -107
- data/docs/liquid_drops.adoc +0 -1547
|
@@ -0,0 +1,525 @@
|
|
|
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
|
+
element_path = element.respond_to?(:path) ? element.path : element.id
|
|
150
|
+
return false unless element_path
|
|
151
|
+
|
|
152
|
+
element_id = element.respond_to?(:id) ? element.id : nil
|
|
153
|
+
|
|
154
|
+
search_path = case_sensitive ? element_path : element_path.downcase
|
|
155
|
+
search_id = element_id && !case_sensitive ? element_id.downcase : element_id
|
|
156
|
+
pattern = pattern_parts[:normalized]
|
|
157
|
+
|
|
158
|
+
if regex
|
|
159
|
+
match_regex(search_path,
|
|
160
|
+
pattern) || (search_id && match_regex(search_id, pattern))
|
|
161
|
+
elsif exact
|
|
162
|
+
search_path == pattern
|
|
163
|
+
elsif pattern_parts[:parts].size == 1
|
|
164
|
+
# For simple patterns (no dots or single part), also match against just the element ID
|
|
165
|
+
match_wildcard(search_path, pattern_parts) ||
|
|
166
|
+
(search_id && match_part(search_id, pattern))
|
|
167
|
+
else
|
|
168
|
+
match_wildcard(search_path, pattern_parts)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Match using regex
|
|
173
|
+
# @param path [String] Element path
|
|
174
|
+
# @param pattern [String] Regex pattern
|
|
175
|
+
# @return [Boolean] True if matches
|
|
176
|
+
def match_regex(path, pattern)
|
|
177
|
+
Regexp.new(pattern).match?(path)
|
|
178
|
+
rescue RegexpError
|
|
179
|
+
false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Match using wildcard pattern
|
|
183
|
+
# @param path [String] Element path
|
|
184
|
+
# @param pattern_parts [Hash] Parsed pattern
|
|
185
|
+
# @return [Boolean] True if matches
|
|
186
|
+
def match_wildcard(path, pattern_parts)
|
|
187
|
+
path_parts = path.split(".")
|
|
188
|
+
pattern_array = pattern_parts[:parts]
|
|
189
|
+
|
|
190
|
+
# Handle different wildcard scenarios
|
|
191
|
+
match_wildcard_parts(path_parts, pattern_array)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Match path parts against pattern parts with wildcards
|
|
195
|
+
# @param path_parts [Array<String>] Path components
|
|
196
|
+
# @param pattern_parts [Array<String>] Pattern components
|
|
197
|
+
# @return [Boolean] True if matches
|
|
198
|
+
def match_wildcard_parts(path_parts, pattern_parts)
|
|
199
|
+
# If pattern has no wildcards and different length, no match
|
|
200
|
+
unless pattern_parts.include?("*")
|
|
201
|
+
return false if path_parts.size != pattern_parts.size
|
|
202
|
+
|
|
203
|
+
return path_parts.zip(pattern_parts).all? do |path_part, pattern_part|
|
|
204
|
+
match_part(path_part, pattern_part)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Handle wildcards
|
|
209
|
+
match_with_wildcards(path_parts, pattern_parts)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Match individual part with potential prefix/suffix wildcards
|
|
213
|
+
# @param part [String] Path part
|
|
214
|
+
# @param pattern [String] Pattern part
|
|
215
|
+
# @return [Boolean] True if matches
|
|
216
|
+
def match_part(part, pattern)
|
|
217
|
+
return true if pattern == "*"
|
|
218
|
+
|
|
219
|
+
if pattern.include?("*")
|
|
220
|
+
# Convert wildcard to regex
|
|
221
|
+
regex_pattern = "^#{Regexp.escape(pattern).gsub('\\*', '.*')}$"
|
|
222
|
+
Regexp.new(regex_pattern).match?(part)
|
|
223
|
+
else
|
|
224
|
+
# Support substring matching for simple patterns
|
|
225
|
+
part.include?(pattern)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Match path with wildcard patterns
|
|
230
|
+
# @param path_parts [Array<String>] Path components
|
|
231
|
+
# @param pattern_parts [Array<String>] Pattern components with wildcards
|
|
232
|
+
# @return [Boolean] True if matches
|
|
233
|
+
def match_with_wildcards(path_parts, pattern_parts)
|
|
234
|
+
# Simple implementation: match each level
|
|
235
|
+
# If pattern is shorter but has *, try to match flexibly
|
|
236
|
+
|
|
237
|
+
pi = 0 # pattern index
|
|
238
|
+
li = 0 # path index
|
|
239
|
+
|
|
240
|
+
while pi < pattern_parts.size && li < path_parts.size
|
|
241
|
+
if pattern_parts[pi] == "*"
|
|
242
|
+
# Wildcard matches one element
|
|
243
|
+
pi += 1
|
|
244
|
+
li += 1
|
|
245
|
+
elsif match_part(path_parts[li], pattern_parts[pi])
|
|
246
|
+
pi += 1
|
|
247
|
+
li += 1
|
|
248
|
+
else
|
|
249
|
+
return false
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Check if we consumed both arrays
|
|
254
|
+
pi == pattern_parts.size && li == path_parts.size
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Collect elements of a specific type
|
|
258
|
+
# @param type [String] Element type
|
|
259
|
+
# @param schema [String, nil] Schema filter
|
|
260
|
+
# @param category [String, nil] Category filter
|
|
261
|
+
# @return [Array<ModelElement>] Collected elements
|
|
262
|
+
def collect_elements(type:, schema: nil, category: nil)
|
|
263
|
+
# Guard against nil schemas collection
|
|
264
|
+
return [] unless @repository.schemas
|
|
265
|
+
|
|
266
|
+
schemas_to_search = if schema
|
|
267
|
+
[@repository.schemas.find do |s|
|
|
268
|
+
s.id == schema
|
|
269
|
+
end].compact
|
|
270
|
+
else
|
|
271
|
+
@repository.schemas
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
case type
|
|
275
|
+
when "schema"
|
|
276
|
+
schemas_to_search
|
|
277
|
+
when "entity"
|
|
278
|
+
collect_from_schemas(schemas_to_search, :entities)
|
|
279
|
+
when "type"
|
|
280
|
+
types = collect_from_schemas(schemas_to_search, :types)
|
|
281
|
+
category ? filter_types_by_category(types, category) : types
|
|
282
|
+
when "attribute"
|
|
283
|
+
collect_attributes(schemas_to_search)
|
|
284
|
+
when "derived_attribute"
|
|
285
|
+
collect_derived_attributes(schemas_to_search)
|
|
286
|
+
when "inverse_attribute"
|
|
287
|
+
collect_inverse_attributes(schemas_to_search)
|
|
288
|
+
when "function"
|
|
289
|
+
collect_from_schemas(schemas_to_search, :functions)
|
|
290
|
+
when "procedure"
|
|
291
|
+
collect_from_schemas(schemas_to_search, :procedures)
|
|
292
|
+
when "rule"
|
|
293
|
+
collect_from_schemas(schemas_to_search, :rules)
|
|
294
|
+
when "constant"
|
|
295
|
+
collect_from_schemas(schemas_to_search, :constants)
|
|
296
|
+
when "parameter"
|
|
297
|
+
collect_parameters(schemas_to_search)
|
|
298
|
+
when "variable"
|
|
299
|
+
collect_variables(schemas_to_search)
|
|
300
|
+
when "where_rule"
|
|
301
|
+
collect_where_rules(schemas_to_search)
|
|
302
|
+
when "unique_rule"
|
|
303
|
+
collect_unique_rules(schemas_to_search)
|
|
304
|
+
when "enumeration_item"
|
|
305
|
+
collect_enumeration_items(schemas_to_search)
|
|
306
|
+
when "interface"
|
|
307
|
+
collect_from_schemas(schemas_to_search, :interfaces)
|
|
308
|
+
else
|
|
309
|
+
[]
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Collect elements from schemas using a method
|
|
314
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
315
|
+
# @param method [Symbol] Method to call on schema
|
|
316
|
+
# @return [Array] Collected elements
|
|
317
|
+
def collect_from_schemas(schemas, method)
|
|
318
|
+
# Guard against nil schemas array
|
|
319
|
+
return [] unless schemas
|
|
320
|
+
|
|
321
|
+
schemas.flat_map { |s| s.send(method) || [] }
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Collect all attributes from entities
|
|
325
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
326
|
+
# @return [Array<Attribute>] All attributes
|
|
327
|
+
def collect_attributes(schemas)
|
|
328
|
+
# Guard against nil schemas
|
|
329
|
+
return [] unless schemas
|
|
330
|
+
|
|
331
|
+
entities = collect_from_schemas(schemas, :entities)
|
|
332
|
+
entities.flat_map { |e| e.attributes || [] }.select do |attr|
|
|
333
|
+
!attr.is_a?(Declarations::DerivedAttribute) &&
|
|
334
|
+
!attr.is_a?(Declarations::InverseAttribute)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Collect derived attributes from entities
|
|
339
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
340
|
+
# @return [Array<DerivedAttribute>] Derived attributes
|
|
341
|
+
def collect_derived_attributes(schemas)
|
|
342
|
+
# Guard against nil schemas
|
|
343
|
+
return [] unless schemas
|
|
344
|
+
|
|
345
|
+
entities = collect_from_schemas(schemas, :entities)
|
|
346
|
+
entities.flat_map { |e| e.attributes || [] }.select do |attr|
|
|
347
|
+
attr.is_a?(Declarations::DerivedAttribute)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Collect inverse attributes from entities
|
|
352
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
353
|
+
# @return [Array<InverseAttribute>] Inverse attributes
|
|
354
|
+
def collect_inverse_attributes(schemas)
|
|
355
|
+
# Guard against nil schemas
|
|
356
|
+
return [] unless schemas
|
|
357
|
+
|
|
358
|
+
entities = collect_from_schemas(schemas, :entities)
|
|
359
|
+
entities.flat_map { |e| e.attributes || [] }.select do |attr|
|
|
360
|
+
attr.is_a?(Declarations::InverseAttribute)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Collect parameters from functions and procedures
|
|
365
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
366
|
+
# @return [Array<Parameter>] All parameters
|
|
367
|
+
def collect_parameters(schemas)
|
|
368
|
+
# Guard against nil schemas
|
|
369
|
+
return [] unless schemas
|
|
370
|
+
|
|
371
|
+
functions = collect_from_schemas(schemas, :functions)
|
|
372
|
+
procedures = collect_from_schemas(schemas, :procedures)
|
|
373
|
+
(functions + procedures).flat_map { |f| f.parameters || [] }
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Collect variables from functions and procedures
|
|
377
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
378
|
+
# @return [Array<Variable>] All variables
|
|
379
|
+
def collect_variables(schemas)
|
|
380
|
+
# Guard against nil schemas
|
|
381
|
+
return [] unless schemas
|
|
382
|
+
|
|
383
|
+
functions = collect_from_schemas(schemas, :functions)
|
|
384
|
+
procedures = collect_from_schemas(schemas, :procedures)
|
|
385
|
+
(functions + procedures).flat_map { |f| f.variables || [] }
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Collect where rules from entities and types
|
|
389
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
390
|
+
# @return [Array<WhereRule>] All where rules
|
|
391
|
+
def collect_where_rules(schemas)
|
|
392
|
+
# Guard against nil schemas
|
|
393
|
+
return [] unless schemas
|
|
394
|
+
|
|
395
|
+
entities = collect_from_schemas(schemas, :entities)
|
|
396
|
+
types = collect_from_schemas(schemas, :types)
|
|
397
|
+
(entities + types).flat_map { |e| e.where_rules || [] }
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Collect unique rules from entities
|
|
401
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
402
|
+
# @return [Array<UniqueRule>] All unique rules
|
|
403
|
+
def collect_unique_rules(schemas)
|
|
404
|
+
# Guard against nil schemas
|
|
405
|
+
return [] unless schemas
|
|
406
|
+
|
|
407
|
+
entities = collect_from_schemas(schemas, :entities)
|
|
408
|
+
entities.flat_map { |e| e.unique_rules || [] }
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Collect enumeration items from enumeration types
|
|
412
|
+
# @param schemas [Array<Schema>] Schemas to search
|
|
413
|
+
# @return [Array<EnumerationItem>] All enumeration items
|
|
414
|
+
def collect_enumeration_items(schemas)
|
|
415
|
+
# Guard against nil schemas
|
|
416
|
+
return [] unless schemas
|
|
417
|
+
|
|
418
|
+
types = collect_from_schemas(schemas, :types)
|
|
419
|
+
types.flat_map { |t| t.enumeration_items || [] }
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Filter types by category
|
|
423
|
+
# @param types [Array<Type>] Types to filter
|
|
424
|
+
# @param category [String] Category to filter by
|
|
425
|
+
# @return [Array<Type>] Filtered types
|
|
426
|
+
def filter_types_by_category(types, category)
|
|
427
|
+
types.select do |type|
|
|
428
|
+
@repository.type_index.categorize(type) == category
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Convert element to hash representation
|
|
433
|
+
# @param element [ModelElement] Element to convert
|
|
434
|
+
# @param type [String] Element type
|
|
435
|
+
# @return [Hash] Element data
|
|
436
|
+
def element_to_hash(element, type)
|
|
437
|
+
hash = {
|
|
438
|
+
id: element.respond_to?(:id) ? element.id : nil,
|
|
439
|
+
type: type,
|
|
440
|
+
path: element.respond_to?(:path) ? element.path : nil,
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# Add schema for non-schema elements
|
|
444
|
+
if type != "schema" && element.respond_to?(:parent)
|
|
445
|
+
schema = find_parent_schema(element)
|
|
446
|
+
hash[:schema] = schema.id if schema
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Add category for types
|
|
450
|
+
if type == "type"
|
|
451
|
+
hash[:category] = @repository.type_index.categorize(element)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
hash
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Find parent schema of an element
|
|
458
|
+
# @param element [ModelElement] Element to find schema for
|
|
459
|
+
# @return [Schema, nil] Parent schema
|
|
460
|
+
def find_parent_schema(element)
|
|
461
|
+
current = element
|
|
462
|
+
while current
|
|
463
|
+
return current if current.is_a?(Declarations::Schema)
|
|
464
|
+
|
|
465
|
+
current = current.parent
|
|
466
|
+
end
|
|
467
|
+
nil
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# Filter results by maximum path depth
|
|
471
|
+
# @param results [Array<Hash>] Search results
|
|
472
|
+
# @param max_depth [Integer] Maximum depth
|
|
473
|
+
# @return [Array<Hash>] Filtered results
|
|
474
|
+
def filter_by_depth(results, max_depth)
|
|
475
|
+
results.select do |result|
|
|
476
|
+
next true unless result[:path]
|
|
477
|
+
|
|
478
|
+
result[:path].split(".").size <= max_depth
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Rank results by relevance
|
|
483
|
+
# @param results [Array<Hash>] Search results
|
|
484
|
+
# @param pattern [String] Search pattern
|
|
485
|
+
# @param boost_exact [Integer] Exact match boost
|
|
486
|
+
# @param boost_prefix [Integer] Prefix match boost
|
|
487
|
+
# @return [Array<Hash>] Sorted results with scores
|
|
488
|
+
def rank_results(results, pattern, boost_exact, boost_prefix)
|
|
489
|
+
normalized_pattern = pattern.downcase
|
|
490
|
+
|
|
491
|
+
results.map do |result|
|
|
492
|
+
score = calculate_relevance_score(result, normalized_pattern,
|
|
493
|
+
boost_exact, boost_prefix)
|
|
494
|
+
result.merge(relevance_score: score)
|
|
495
|
+
end.sort_by { |r| -r[:relevance_score] }
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Calculate relevance score for a result
|
|
499
|
+
# @param result [Hash] Search result
|
|
500
|
+
# @param pattern [String] Normalized search pattern
|
|
501
|
+
# @param boost_exact [Integer] Exact match boost
|
|
502
|
+
# @param boost_prefix [Integer] Prefix match boost
|
|
503
|
+
# @return [Integer] Relevance score
|
|
504
|
+
def calculate_relevance_score(result, pattern, boost_exact, boost_prefix)
|
|
505
|
+
score = 0
|
|
506
|
+
id = (result[:id] || "").downcase
|
|
507
|
+
|
|
508
|
+
# Exact match gets highest score
|
|
509
|
+
score += boost_exact if id == pattern
|
|
510
|
+
|
|
511
|
+
# Prefix match gets medium score
|
|
512
|
+
score += boost_prefix if id.start_with?(pattern)
|
|
513
|
+
|
|
514
|
+
# Shorter paths rank higher (max 10 points)
|
|
515
|
+
path_depth = result[:path]&.split(".")&.size || 1
|
|
516
|
+
score += [10 - path_depth, 0].max
|
|
517
|
+
|
|
518
|
+
# Schema-level results rank higher
|
|
519
|
+
score += 5 if result[:type] == "schema"
|
|
520
|
+
|
|
521
|
+
score
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
end
|
data/lib/expressir/model.rb
CHANGED
|
@@ -1,95 +1,5 @@
|
|
|
1
|
-
#
|
|
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
|
-
#
|
|
6
|
-
#
|
|
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
|