expressir 2.1.21 → 2.1.23

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.
data/lib/expressir/cli.rb CHANGED
@@ -24,26 +24,33 @@ module Expressir
24
24
  end
25
25
 
26
26
  desc "clean PATH", "Strip remarks and prettify EXPRESS schema at PATH"
27
- method_option :output, type: :string, desc: "Output file path (defaults to stdout)"
27
+ method_option :output, type: :string,
28
+ desc: "Output file path (defaults to stdout)"
28
29
  def clean(path)
29
30
  Commands::Clean.new(options).run(path)
30
31
  end
31
32
 
32
- desc "benchmark FILE_OR_YAML", "Benchmark schema loading performance for a file or list of files from YAML"
33
- method_option :ips, type: :boolean, desc: "Use benchmark-ips for detailed statistics"
33
+ desc "benchmark FILE_OR_YAML",
34
+ "Benchmark schema loading performance for a file or list of files from YAML"
35
+ method_option :ips, type: :boolean,
36
+ desc: "Use benchmark-ips for detailed statistics"
34
37
  method_option :verbose, type: :boolean, desc: "Show verbose output"
35
38
  method_option :save, type: :boolean, desc: "Save benchmark results to file"
36
- method_option :format, type: :string, desc: "Output format (json, csv, default)"
39
+ method_option :format, type: :string,
40
+ desc: "Output format (json, csv, default)"
37
41
  def benchmark(path)
38
42
  Commands::Benchmark.new(options).run(path)
39
43
  end
40
44
 
41
45
  desc "benchmark-cache FILE_OR_YAML", "Benchmark schema loading with caching"
42
- method_option :ips, type: :boolean, desc: "Use benchmark-ips for detailed statistics"
46
+ method_option :ips, type: :boolean,
47
+ desc: "Use benchmark-ips for detailed statistics"
43
48
  method_option :verbose, type: :boolean, desc: "Show verbose output"
44
49
  method_option :save, type: :boolean, desc: "Save benchmark results to file"
45
- method_option :format, type: :string, desc: "Output format (json, csv, default)"
46
- method_option :cache_path, type: :string, desc: "Path to store the cache file"
50
+ method_option :format, type: :string,
51
+ desc: "Output format (json, csv, default)"
52
+ method_option :cache_path, type: :string,
53
+ desc: "Path to store the cache file"
47
54
  def benchmark_cache(path)
48
55
  Commands::BenchmarkCache.new(options).run(path)
49
56
  end
@@ -53,10 +60,16 @@ module Expressir
53
60
  Commands::Validate.new(options).run(paths)
54
61
  end
55
62
 
56
- desc "coverage *PATH", "List EXPRESS entities and check documentation coverage"
57
- method_option :format, type: :string, desc: "Output format (text, json, yaml)", default: "text"
58
- method_option :exclude, type: :string, desc: "Comma-separated list of EXPRESS entity types to skip from coverage (e.g., TYPE,CONSTANT,TYPE:SELECT)"
59
- method_option :output, type: :string, desc: "Output file path for JSON/YAML formats (defaults to coverage_report.json/yaml)"
63
+ desc "coverage *PATH",
64
+ "List EXPRESS entities and check documentation coverage"
65
+ method_option :format, type: :string,
66
+ desc: "Output format (text, json, yaml)", default: "text"
67
+ method_option :exclude, type: :string,
68
+ desc: "Comma-separated list of EXPRESS entity types to skip from coverage (e.g., TYPE,CONSTANT,TYPE:SELECT)"
69
+ method_option :output, type: :string,
70
+ desc: "Output file path for JSON/YAML formats (defaults to coverage_report.json/yaml)"
71
+ method_option :ignore_files, type: :string,
72
+ desc: "Path to YAML file containing array of files to ignore from overall coverage calculation"
60
73
  def coverage(*paths)
61
74
  Commands::Coverage.new(options).run(paths)
62
75
  end
@@ -5,7 +5,7 @@ module Expressir
5
5
  configure_benchmarking
6
6
 
7
7
  if [".yml", ".yaml"].include?(File.extname(path).downcase)
8
- benchmark_from_yaml(path)
8
+ benchmark_from_manifest(path)
9
9
  else
10
10
  benchmark_file(path)
11
11
  end
@@ -36,24 +36,15 @@ module Expressir
36
36
  end
37
37
  end
38
38
 
39
- def benchmark_from_yaml(yaml_path)
40
- say "Express Schema Loading Benchmark from YAML"
39
+ def benchmark_from_manifest(manifest_path)
40
+ say "Express Schema Loading Benchmark from Manifest"
41
41
  say "--------------------------------"
42
42
 
43
- # Load schema list from YAML
44
- schema_list = YAML.load_file(yaml_path)
45
- if schema_list.is_a?(Hash) && schema_list["schemas"]
46
- # Handle format: { "schemas": ["path1", "path2", ...] }
47
- schema_files = schema_list["schemas"]
48
- elsif schema_list.is_a?(Array)
49
- # Handle format: ["path1", "path2", ...]
50
- schema_files = schema_list
51
- else
52
- say "Invalid YAML format. Expected an array of schema paths or a hash with a 'schemas' key."
53
- return
54
- end
43
+ # Load schema manifest
44
+ manifest = Expressir::SchemaManifest.from_file(manifest_path)
45
+ schema_files = manifest.schemas.map(&:path)
55
46
 
56
- say "YAML File: #{yaml_path}"
47
+ say "Manifest File: #{manifest_path}"
57
48
  say "Number of schemas in list: #{schema_files.size}"
58
49
  say "--------------------------------"
59
50
 
@@ -8,7 +8,7 @@ module Expressir
8
8
  cache_path = options[:cache_path] || generate_temp_cache_path
9
9
 
10
10
  if [".yml", ".yaml"].include?(File.extname(path).downcase)
11
- benchmark_cache_from_yaml(path, cache_path)
11
+ benchmark_cache_from_manifest(path, cache_path)
12
12
  else
13
13
  benchmark_cache_file(path, cache_path)
14
14
  end
@@ -32,7 +32,8 @@ module Expressir
32
32
  say "--------------------------------"
33
33
 
34
34
  # Benchmark with caching
35
- results = Expressir::Benchmark.measure_with_cache(path, cache_path) do |file|
35
+ results = Expressir::Benchmark.measure_with_cache(path,
36
+ cache_path) do |file|
36
37
  Expressir::Express::Parser.from_file(file)
37
38
  end
38
39
 
@@ -44,24 +45,15 @@ module Expressir
44
45
  end
45
46
  end
46
47
 
47
- def benchmark_cache_from_yaml(yaml_path, cache_path)
48
- say "Express Schema Loading Benchmark with Caching from YAML"
48
+ def benchmark_cache_from_manifest(manifest_path, cache_path)
49
+ say "Express Schema Loading Benchmark with Caching from Manifest"
49
50
  say "--------------------------------"
50
51
 
51
- # Load schema list from YAML
52
- schema_list = YAML.load_file(yaml_path)
53
- if schema_list.is_a?(Hash) && schema_list["schemas"]
54
- # Handle format: { "schemas": ["path1", "path2", ...] }
55
- schema_files = schema_list["schemas"]
56
- elsif schema_list.is_a?(Array)
57
- # Handle format: ["path1", "path2", ...]
58
- schema_files = schema_list
59
- else
60
- say "Invalid YAML format. Expected an array of schema paths or a hash with a 'schemas' key."
61
- return
62
- end
52
+ # Load schema manifest
53
+ manifest = Expressir::SchemaManifest.from_file(manifest_path)
54
+ schema_files = manifest.schemas.map(&:path)
63
55
 
64
- say "YAML File: #{yaml_path}"
56
+ say "Manifest File: #{manifest_path}"
65
57
  say "Number of schemas in list: #{schema_files.size}"
66
58
  say "Cache: #{cache_path}"
67
59
  say "--------------------------------"
@@ -32,27 +32,28 @@ module Expressir
32
32
 
33
33
  def collect_reports(paths)
34
34
  reports = []
35
+ ignored_files = parse_ignore_files
35
36
 
36
37
  paths.each do |path|
37
- handle_path(path, reports)
38
+ handle_path(path, reports, ignored_files)
38
39
  end
39
40
 
40
41
  reports
41
42
  end
42
43
 
43
- def handle_path(path, reports)
44
+ def handle_path(path, reports, ignored_files)
44
45
  if File.directory?(path)
45
- handle_directory(path, reports)
46
+ handle_directory(path, reports, ignored_files)
46
47
  elsif File.extname(path).downcase == ".exp"
47
- handle_express_file(path, reports)
48
+ handle_express_file(path, reports, ignored_files)
48
49
  elsif [".yml", ".yaml"].include?(File.extname(path).downcase)
49
- handle_yaml_manifest(path, reports)
50
+ handle_schema_manifest(path, reports, ignored_files)
50
51
  else
51
52
  say "Unsupported file type: #{path}"
52
53
  end
53
54
  end
54
55
 
55
- def handle_directory(path, reports)
56
+ def handle_directory(path, reports, ignored_files)
56
57
  say "Processing directory: #{path}"
57
58
  exp_files = Dir.glob(File.join(path, "**", "*.exp"))
58
59
  if exp_files.empty?
@@ -79,54 +80,38 @@ module Expressir
79
80
  progress.increment
80
81
  end
81
82
  skip_types = parse_skip_types
82
- report = Expressir::Coverage::Report.from_repository(repository, skip_types)
83
+ report = Expressir::Coverage::Report.from_repository(
84
+ repository,
85
+ skip_types,
86
+ ignored_files,
87
+ )
83
88
  reports << report
84
89
  rescue StandardError => e
85
90
  say "Error processing directory #{path}: #{e.message}"
86
91
  end
87
92
  end
88
93
 
89
- def handle_express_file(path, reports)
94
+ def handle_express_file(path, reports, ignored_files)
90
95
  say "Processing file: #{path}"
91
96
  begin
92
97
  # For a single file, we don't need a progress bar
93
98
  skip_types = parse_skip_types
94
- report = Expressir::Coverage::Report.from_file(path, skip_types)
99
+ report = Expressir::Coverage::Report.from_file(path, skip_types,
100
+ ignored_files)
95
101
  reports << report
96
102
  rescue StandardError => e
97
103
  say "Error processing file #{path}: #{e.message}"
98
104
  end
99
105
  end
100
106
 
101
- def handle_yaml_manifest(path, reports)
102
- say "Processing YAML manifest: #{path}"
107
+ def handle_schema_manifest(path, reports, ignored_files)
108
+ say "Processing schema manifest: #{path}"
103
109
  begin
104
- schema_list = YAML.load_file(path)
105
- manifest_dir = File.dirname(path)
106
-
107
- if schema_list.is_a?(Hash) && schema_list["schemas"]
108
- schemas_data = schema_list["schemas"]
109
-
110
- # Handle the nested structure with schema name keys and path values
111
- if schemas_data.is_a?(Hash)
112
- schema_files = schemas_data.values.map do |schema_data|
113
- if schema_data.is_a?(Hash) && schema_data["path"]
114
- # Make path relative to the manifest location
115
- File.expand_path(schema_data["path"], manifest_dir)
116
- end
117
- end.compact
118
-
119
- say "Found #{schema_files.size} schema files to process"
120
- else
121
- # If it's a direct array of paths (old format)
122
- schema_files = schemas_data
123
- end
124
- elsif schema_list.is_a?(Array)
125
- schema_files = schema_list
126
- else
127
- say "Invalid YAML format. Expected an array of schema paths or a hash with a 'schemas' key."
128
- return
129
- end
110
+ # Load schema manifest
111
+ manifest = Expressir::SchemaManifest.from_file(path)
112
+ schema_files = manifest.schemas.map(&:path)
113
+
114
+ say "Found #{schema_files.size} schema files to process"
130
115
 
131
116
  # Initialize progress bar
132
117
  if schema_files && !schema_files.empty?
@@ -149,12 +134,15 @@ module Expressir
149
134
 
150
135
  # Create and add the report
151
136
  skip_types = parse_skip_types
152
- report = Expressir::Coverage::Report.from_repository(repository, skip_types)
137
+ report = Expressir::Coverage::Report.from_repository(
138
+ repository,
139
+ skip_types,
140
+ ignored_files,
141
+ )
153
142
  reports << report
154
143
  end
155
144
  rescue StandardError => e
156
- say "Error processing YAML manifest #{path}: #{e.message}"
157
- say "Debug: schema_list structure: #{schema_list.class}" if schema_list
145
+ say "Error processing schema manifest #{path}: #{e.message}"
158
146
  end
159
147
  end
160
148
 
@@ -199,7 +187,8 @@ module Expressir
199
187
  # Add rows
200
188
  dirs.each do |dir, stats|
201
189
  coverage = stats["total"].positive? ? (stats["documented"].to_f / stats["total"] * 100).round(2) : 100.0
202
- table.add_row [dir, stats["total"], stats["documented"], "#{coverage}%"]
190
+ table.add_row [dir, stats["total"], stats["documented"],
191
+ "#{coverage}%"]
203
192
  end
204
193
 
205
194
  say table
@@ -249,34 +238,66 @@ module Expressir
249
238
  },
250
239
  )
251
240
 
252
- table.add_row ["Coverage Percentage", "#{overall['coverage_percentage']}%"]
241
+ table.add_row ["Coverage Percentage",
242
+ "#{overall['coverage_percentage']}%"]
253
243
  table.add_row ["Total Entities", overall["total_entities"]]
254
244
  table.add_row ["Documented Entities", overall["documented_entities"]]
255
- table.add_row ["Undocumented Entities", overall["undocumented_entities"]]
245
+ table.add_row ["Undocumented Entities",
246
+ overall["undocumented_entities"]]
256
247
 
257
248
  say table
258
249
  end
259
250
 
260
251
  def build_structured_report(reports)
261
- {
262
- "overall" => {
263
- "total_entities" => reports.sum { |r| r.total_entities.size },
264
- "documented_entities" => reports.sum { |r| r.documented_entities.size },
265
- "undocumented_entities" => reports.sum { |r| r.undocumented_entities.size },
266
- "coverage_percentage" => if reports.sum { |r| r.total_entities.size }.positive?
267
- (reports.sum { |r| r.documented_entities.size }.to_f / reports.sum { |r| r.total_entities.size } * 100).round(2)
268
- else
269
- 100.0
270
- end,
271
- },
252
+ # Calculate ignored file statistics
253
+ ignored_files = reports.flat_map(&:ignored_file_reports)
254
+ ignored_entities_count = ignored_files.sum { |f| f["total"] }
255
+
256
+ overall_stats = {
257
+ "total_entities" => reports.sum { |r| r.total_entities.size },
258
+ "documented_entities" => reports.sum do |r|
259
+ r.documented_entities.size
260
+ end,
261
+ "undocumented_entities" => reports.sum do |r|
262
+ r.undocumented_entities.size
263
+ end,
264
+ "coverage_percentage" => if reports.sum do |r|
265
+ r.total_entities.size
266
+ end.positive?
267
+ (reports.sum do |r|
268
+ r.documented_entities.size
269
+ end.to_f / reports.sum do |r|
270
+ r.total_entities.size
271
+ end * 100).round(2)
272
+ else
273
+ 100.0
274
+ end,
275
+ }
276
+
277
+ # Add ignored file information if there are any
278
+ if ignored_files.any?
279
+ overall_stats["ignored_files_count"] = ignored_files.size
280
+ overall_stats["ignored_entities_count"] = ignored_entities_count
281
+ end
282
+
283
+ structured_report = {
284
+ "overall" => overall_stats,
272
285
  "files" => reports.flat_map(&:file_reports),
273
286
  "directories" => reports.flat_map(&:directory_reports),
274
287
  }
288
+
289
+ # Add ignored files section if there are any
290
+ if ignored_files.any?
291
+ structured_report["ignored_files"] = ignored_files
292
+ end
293
+
294
+ structured_report
275
295
  end
276
296
 
277
297
  def display_json_output(reports)
278
298
  output_file = options[:output] || "coverage_report.json"
279
- File.write(output_file, JSON.pretty_generate(build_structured_report(reports)))
299
+ File.write(output_file,
300
+ JSON.pretty_generate(build_structured_report(reports)))
280
301
  say "JSON coverage report written to: #{output_file}"
281
302
  end
282
303
 
@@ -343,6 +364,55 @@ module Expressir
343
364
  end
344
365
  end
345
366
  end
367
+
368
+ # Parse and expand ignore files from YAML
369
+ # @return [Hash] Hash mapping absolute file paths to their matched patterns
370
+ def parse_ignore_files
371
+ ignore_files_option = options["ignore_files"] || options[:ignore_files]
372
+ return {} unless ignore_files_option
373
+
374
+ unless File.exist?(ignore_files_option)
375
+ say "Warning: Ignore files YAML not found: #{ignore_files_option}"
376
+ return {}
377
+ end
378
+
379
+ begin
380
+ patterns = YAML.load_file(ignore_files_option)
381
+ unless patterns.is_a?(Array)
382
+ say "Warning: Invalid ignore files YAML format. Expected an array of file patterns."
383
+ return {}
384
+ end
385
+
386
+ ignore_files_dir = File.dirname(File.expand_path(ignore_files_option))
387
+ expanded_files = {}
388
+
389
+ patterns.each do |pattern|
390
+ # Resolve pattern relative to the YAML file's directory
391
+ full_pattern = File.expand_path(pattern, ignore_files_dir)
392
+
393
+ # Expand glob pattern
394
+ matched_files = Dir.glob(full_pattern)
395
+
396
+ if matched_files.empty?
397
+ say "Warning: No files matched pattern: #{pattern}"
398
+ else
399
+ matched_files.each do |file_path|
400
+ # Store absolute path and the original pattern that matched it
401
+ expanded_files[File.expand_path(file_path)] = pattern
402
+ end
403
+ end
404
+ end
405
+
406
+ if expanded_files.any?
407
+ say "Found #{expanded_files.size} files to ignore from patterns"
408
+ end
409
+
410
+ expanded_files
411
+ rescue StandardError => e
412
+ say "Warning: Error processing ignore files YAML #{ignore_files_option}: #{e.message}"
413
+ {}
414
+ end
415
+ end
346
416
  end
347
417
  end
348
418
  end
@@ -59,14 +59,16 @@ module Expressir
59
59
  # Represents a documentation coverage report for EXPRESS schemas
60
60
  class Report
61
61
  attr_reader :repository, :schema_reports, :total_entities, :documented_entities,
62
- :undocumented_entities
62
+ :undocumented_entities, :ignored_files
63
63
 
64
64
  # Initialize a coverage report
65
65
  # @param repository [Expressir::Model::Repository] The repository to analyze
66
66
  # @param skip_types [Array<String>] Array of entity type names to skip from coverage
67
- def initialize(repository, skip_types = [])
67
+ # @param ignored_files [Hash] Hash mapping absolute file paths to their matched patterns
68
+ def initialize(repository, skip_types = [], ignored_files = {})
68
69
  @repository = repository
69
70
  @skip_types = skip_types
71
+ @ignored_files = ignored_files
70
72
  @schema_reports = []
71
73
  @total_entities = []
72
74
  @documented_entities = []
@@ -78,18 +80,20 @@ module Expressir
78
80
  # Create a report from a repository
79
81
  # @param repository [Expressir::Model::Repository] The repository to analyze
80
82
  # @param skip_types [Array<String>] Array of entity type names to skip from coverage
83
+ # @param ignored_files [Hash] Hash mapping absolute file paths to their matched patterns
81
84
  # @return [Report] The coverage report
82
- def self.from_repository(repository, skip_types = [])
83
- new(repository, skip_types)
85
+ def self.from_repository(repository, skip_types = [], ignored_files = {})
86
+ new(repository, skip_types, ignored_files)
84
87
  end
85
88
 
86
89
  # Create a report from a schema file
87
90
  # @param path [String] Path to the schema file
88
91
  # @param skip_types [Array<String>] Array of entity type names to skip from coverage
92
+ # @param ignored_files [Hash] Hash mapping absolute file paths to their matched patterns
89
93
  # @return [Report] The coverage report
90
- def self.from_file(path, skip_types = [])
94
+ def self.from_file(path, skip_types = [], ignored_files = {})
91
95
  repository = Expressir::Express::Parser.from_file(path)
92
- new(repository, skip_types)
96
+ new(repository, skip_types, ignored_files)
93
97
  end
94
98
 
95
99
  # Calculate the overall coverage percentage
@@ -113,6 +117,39 @@ module Expressir
113
117
  absolute_path
114
118
  end
115
119
 
120
+ file_report = {
121
+ "file" => relative_path,
122
+ "file_basename" => File.basename(absolute_path),
123
+ "directory" => File.dirname(absolute_path),
124
+ "total" => report[:total].size,
125
+ "documented" => report[:documented].size,
126
+ "undocumented" => report[:undocumented],
127
+ "coverage" => report[:coverage],
128
+ "ignored" => report[:ignored] || false,
129
+ }
130
+
131
+ # Add matched pattern for ignored files
132
+ if report[:ignored] && report[:matched_pattern]
133
+ file_report["matched_pattern"] = report[:matched_pattern]
134
+ end
135
+
136
+ file_report
137
+ end
138
+ end
139
+
140
+ # Get ignored file reports
141
+ # @return [Array<Hash>] Array of ignored file report hashes
142
+ def ignored_file_reports
143
+ @schema_reports.select { |report| report[:ignored] }.map do |report|
144
+ absolute_path = report[:schema].file
145
+ relative_path = begin
146
+ Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
147
+ rescue ArgumentError
148
+ # If paths are on different drives or otherwise incompatible,
149
+ # fall back to the absolute path
150
+ absolute_path
151
+ end
152
+
116
153
  {
117
154
  "file" => relative_path,
118
155
  "file_basename" => File.basename(absolute_path),
@@ -121,6 +158,7 @@ module Expressir
121
158
  "documented" => report[:documented].size,
122
159
  "undocumented" => report[:undocumented],
123
160
  "coverage" => report[:coverage],
161
+ "matched_pattern" => report[:matched_pattern],
124
162
  }
125
163
  end
126
164
  end
@@ -182,11 +220,14 @@ module Expressir
182
220
  schema_report = process_schema(schema)
183
221
  @schema_reports << schema_report
184
222
 
185
- @total_entities.concat(schema_report[:total])
186
- @documented_entities.concat(schema_report[:documented])
187
- @undocumented_entities.concat(schema_report[:undocumented].map do |entity|
188
- { schema: schema.id, entity: entity }
189
- end)
223
+ # Only include non-ignored files in overall statistics
224
+ unless schema_report[:ignored]
225
+ @total_entities.concat(schema_report[:total])
226
+ @documented_entities.concat(schema_report[:documented])
227
+ @undocumented_entities.concat(schema_report[:undocumented].map do |entity|
228
+ { schema: schema.id, entity: entity }
229
+ end)
230
+ end
190
231
  end
191
232
  end
192
233
 
@@ -195,17 +236,26 @@ module Expressir
195
236
  # @return [Hash] A hash with coverage information
196
237
  def process_schema(schema)
197
238
  entities = Expressir::Coverage.find_entities(schema, @skip_types)
198
- documented = entities.select { |e| Expressir::Coverage.entity_documented?(e) }
239
+ documented = entities.select do |e|
240
+ Expressir::Coverage.entity_documented?(e)
241
+ end
199
242
  undocumented = entities - documented
200
243
 
201
244
  coverage = entities.empty? ? 100.0 : (documented.size.to_f / entities.size) * 100
202
245
 
246
+ # Check if this schema file is ignored
247
+ schema_file = File.expand_path(schema.file) if schema.file
248
+ ignored = @ignored_files.key?(schema_file)
249
+ matched_pattern = @ignored_files[schema_file] if ignored
250
+
203
251
  {
204
252
  schema: schema,
205
253
  total: entities,
206
254
  documented: documented,
207
255
  undocumented: undocumented.map { |e| format_entity(e) },
208
256
  coverage: coverage,
257
+ ignored: ignored,
258
+ matched_pattern: matched_pattern,
209
259
  }
210
260
  end
211
261
 
@@ -352,10 +402,18 @@ module Expressir
352
402
  entities.concat(container.subtype_constraints) if container.subtype_constraints
353
403
 
354
404
  # Recursively find nested entities in nested containers
355
- container.types&.each { |type| entities.concat(find_nested_entities(type)) }
356
- container.entities&.each { |entity| entities.concat(find_nested_entities(entity)) }
357
- container.functions&.each { |function| entities.concat(find_nested_entities(function)) }
358
- container.procedures&.each { |procedure| entities.concat(find_nested_entities(procedure)) }
405
+ container.types&.each do |type|
406
+ entities.concat(find_nested_entities(type))
407
+ end
408
+ container.entities&.each do |entity|
409
+ entities.concat(find_nested_entities(entity))
410
+ end
411
+ container.functions&.each do |function|
412
+ entities.concat(find_nested_entities(function))
413
+ end
414
+ container.procedures&.each do |procedure|
415
+ entities.concat(find_nested_entities(procedure))
416
+ end
359
417
 
360
418
  when Expressir::Model::Declarations::Rule
361
419
  # Rule nested entities
@@ -368,10 +426,18 @@ module Expressir
368
426
  entities.concat(container.subtype_constraints) if container.subtype_constraints
369
427
 
370
428
  # Recursively find nested entities in nested containers
371
- container.types&.each { |type| entities.concat(find_nested_entities(type)) }
372
- container.entities&.each { |entity| entities.concat(find_nested_entities(entity)) }
373
- container.functions&.each { |function| entities.concat(find_nested_entities(function)) }
374
- container.procedures&.each { |procedure| entities.concat(find_nested_entities(procedure)) }
429
+ container.types&.each do |type|
430
+ entities.concat(find_nested_entities(type))
431
+ end
432
+ container.entities&.each do |entity|
433
+ entities.concat(find_nested_entities(entity))
434
+ end
435
+ container.functions&.each do |function|
436
+ entities.concat(find_nested_entities(function))
437
+ end
438
+ container.procedures&.each do |procedure|
439
+ entities.concat(find_nested_entities(procedure))
440
+ end
375
441
 
376
442
  when Expressir::Model::Declarations::Procedure
377
443
  # Procedure nested entities
@@ -385,10 +451,18 @@ module Expressir
385
451
  entities.concat(container.subtype_constraints) if container.subtype_constraints
386
452
 
387
453
  # Recursively find nested entities in nested containers
388
- container.types&.each { |type| entities.concat(find_nested_entities(type)) }
389
- container.entities&.each { |entity| entities.concat(find_nested_entities(entity)) }
390
- container.functions&.each { |function| entities.concat(find_nested_entities(function)) }
391
- container.procedures&.each { |procedure| entities.concat(find_nested_entities(procedure)) }
454
+ container.types&.each do |type|
455
+ entities.concat(find_nested_entities(type))
456
+ end
457
+ container.entities&.each do |entity|
458
+ entities.concat(find_nested_entities(entity))
459
+ end
460
+ container.functions&.each do |function|
461
+ entities.concat(find_nested_entities(function))
462
+ end
463
+ container.procedures&.each do |procedure|
464
+ entities.concat(find_nested_entities(procedure))
465
+ end
392
466
 
393
467
  when Expressir::Model::Declarations::Interface
394
468
  # Interface nested entities
@@ -450,7 +524,7 @@ module Expressir
450
524
  type_subtype_skips.include?(entity_subtype)
451
525
  # Check FUNCTION:INNER exclusions
452
526
  elsif entity_class == "Expressir::Model::Declarations::Function" && function_subtype_skips.include?("INNER")
453
- is_inner_function?(entity)
527
+ inner_function?(entity)
454
528
  else
455
529
  false
456
530
  end
@@ -480,7 +554,7 @@ module Expressir
480
554
  # Check if a function is an inner function (nested within another function, rule, or procedure)
481
555
  # @param function_entity [Expressir::Model::Declarations::Function] The function entity to check
482
556
  # @return [Boolean] True if the function is nested within another function, rule, or procedure
483
- def self.is_inner_function?(function_entity)
557
+ def self.inner_function?(function_entity)
484
558
  return false unless function_entity.respond_to?(:parent) && function_entity.parent
485
559
 
486
560
  # Check if the parent is a function, rule, or procedure (not a schema)
@@ -9,7 +9,8 @@ module Expressir
9
9
  # @param root_path [String] Express repository root path, to be stripped from Express file paths to create a portable cache file
10
10
  # @param test_overwrite_version [String] don't use, only for tests
11
11
  # @return [nil]
12
- def self.to_file(file, content, root_path: nil, test_overwrite_version: nil)
12
+ def self.to_file(file, content, root_path: nil,
13
+ test_overwrite_version: nil)
13
14
  version = test_overwrite_version || VERSION
14
15
 
15
16
  cache = Model::Cache.new(