expressir 2.1.18 → 2.1.20
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/.rubocop_todo.yml +30 -9
- data/README.adoc +354 -3
- data/expressir.gemspec +2 -1
- data/lib/expressir/cli.rb +29 -193
- data/lib/expressir/commands/base.rb +32 -0
- data/lib/expressir/commands/benchmark.rb +68 -0
- data/lib/expressir/commands/benchmark_cache.rb +88 -0
- data/lib/expressir/commands/clean.rb +20 -0
- data/lib/expressir/commands/coverage.rb +342 -0
- data/lib/expressir/commands/format.rb +13 -0
- data/lib/expressir/commands/validate.rb +53 -0
- data/lib/expressir/commands/version.rb +9 -0
- data/lib/expressir/coverage.rb +474 -0
- data/lib/expressir/express/formatter.rb +4 -2
- data/lib/expressir/express/visitor.rb +10 -6
- data/lib/expressir/model/declarations/derived_attribute.rb +25 -0
- data/lib/expressir/model/declarations/inverse_attribute.rb +25 -0
- data/lib/expressir/model/model_element.rb +2 -0
- data/lib/expressir/model.rb +94 -71
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +122 -6
- metadata +33 -8
data/lib/expressir/cli.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
require "thor"
|
2
2
|
require "yaml"
|
3
|
+
require "terminal-table"
|
4
|
+
|
5
|
+
require_relative "commands/base"
|
6
|
+
require_relative "commands/format"
|
7
|
+
require_relative "commands/clean"
|
8
|
+
require_relative "commands/benchmark"
|
9
|
+
require_relative "commands/benchmark_cache"
|
10
|
+
require_relative "commands/validate"
|
11
|
+
require_relative "commands/coverage"
|
12
|
+
require_relative "commands/version"
|
3
13
|
|
4
14
|
module Expressir
|
5
15
|
class Cli < Thor
|
16
|
+
# Exit with error code on command failures
|
17
|
+
def self.exit_on_failure?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
6
21
|
desc "format PATH", "pretty print EXPRESS schema located at PATH"
|
7
22
|
def format(path)
|
8
|
-
|
9
|
-
repository.schemas.each do |schema|
|
10
|
-
puts "\n(* Expressir formatted schema: #{schema.id} *)\n"
|
11
|
-
puts schema.to_s(no_remarks: true)
|
12
|
-
end
|
23
|
+
Commands::Format.new(options).run(path)
|
13
24
|
end
|
14
25
|
|
15
26
|
desc "clean PATH", "Strip remarks and prettify EXPRESS schema at PATH"
|
16
27
|
method_option :output, type: :string, desc: "Output file path (defaults to stdout)"
|
17
28
|
def clean(path)
|
18
|
-
|
19
|
-
formatted_schemas = repository.schemas.map do |schema|
|
20
|
-
# Format schema without remarks
|
21
|
-
schema.to_s(no_remarks: true)
|
22
|
-
end.join("\n\n")
|
23
|
-
|
24
|
-
if options[:output]
|
25
|
-
File.write(options[:output], formatted_schemas)
|
26
|
-
puts "Cleaned schema written to #{options[:output]}"
|
27
|
-
else
|
28
|
-
puts formatted_schemas
|
29
|
-
end
|
29
|
+
Commands::Clean.new(options).run(path)
|
30
30
|
end
|
31
31
|
|
32
32
|
desc "benchmark FILE_OR_YAML", "Benchmark schema loading performance for a file or list of files from YAML"
|
@@ -35,18 +35,7 @@ module Expressir
|
|
35
35
|
method_option :save, type: :boolean, desc: "Save benchmark results to file"
|
36
36
|
method_option :format, type: :string, desc: "Output format (json, csv, default)"
|
37
37
|
def benchmark(path)
|
38
|
-
|
39
|
-
Expressir.configuration.benchmark_enabled = true
|
40
|
-
Expressir.configuration.benchmark_ips = options[:ips]
|
41
|
-
Expressir.configuration.benchmark_verbose = options[:verbose]
|
42
|
-
Expressir.configuration.benchmark_save = options[:save]
|
43
|
-
Expressir.configuration.benchmark_format = options[:format] if options[:format]
|
44
|
-
|
45
|
-
if [".yml", ".yaml"].include?(File.extname(path).downcase)
|
46
|
-
benchmark_from_yaml(path)
|
47
|
-
else
|
48
|
-
benchmark_file(path)
|
49
|
-
end
|
38
|
+
Commands::Benchmark.new(options).run(path)
|
50
39
|
end
|
51
40
|
|
52
41
|
desc "benchmark-cache FILE_OR_YAML", "Benchmark schema loading with caching"
|
@@ -56,178 +45,25 @@ module Expressir
|
|
56
45
|
method_option :format, type: :string, desc: "Output format (json, csv, default)"
|
57
46
|
method_option :cache_path, type: :string, desc: "Path to store the cache file"
|
58
47
|
def benchmark_cache(path)
|
59
|
-
|
60
|
-
Expressir.configuration.benchmark_enabled = true
|
61
|
-
Expressir.configuration.benchmark_ips = options[:ips]
|
62
|
-
Expressir.configuration.benchmark_verbose = options[:verbose]
|
63
|
-
Expressir.configuration.benchmark_save = options[:save]
|
64
|
-
Expressir.configuration.benchmark_format = options[:format] if options[:format]
|
65
|
-
|
66
|
-
# Run benchmarks with cache
|
67
|
-
cache_path = options[:cache_path] || generate_temp_cache_path
|
68
|
-
|
69
|
-
if [".yml", ".yaml"].include?(File.extname(path).downcase)
|
70
|
-
benchmark_cache_from_yaml(path, cache_path)
|
71
|
-
else
|
72
|
-
benchmark_cache_file(path, cache_path)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
no_commands do
|
77
|
-
def _validate_schema(path)
|
78
|
-
repository = Expressir::Express::Parser.from_file(path)
|
79
|
-
repository.schemas.inject([]) do |acc, schema|
|
80
|
-
acc << schema.id unless schema.version&.value
|
81
|
-
acc
|
82
|
-
end
|
83
|
-
rescue StandardError
|
84
|
-
nil
|
85
|
-
end
|
86
|
-
|
87
|
-
def _print_validation_errors(type, array)
|
88
|
-
return if array.empty?
|
89
|
-
|
90
|
-
puts "#{'*' * 20} RESULTS: #{type.to_s.upcase.tr('_', ' ')} #{'*' * 20}"
|
91
|
-
array.each do |msg|
|
92
|
-
puts msg
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def benchmark_file(path)
|
97
|
-
puts "Express Schema Loading Benchmark"
|
98
|
-
puts "--------------------------------"
|
99
|
-
|
100
|
-
repository = Expressir::Benchmark.measure_file(path) do
|
101
|
-
Expressir::Express::Parser.from_file(path)
|
102
|
-
end
|
103
|
-
|
104
|
-
if repository
|
105
|
-
puts "Loaded #{repository.schemas.size} schemas"
|
106
|
-
else
|
107
|
-
puts "Failed to load schema"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def benchmark_from_yaml(yaml_path)
|
112
|
-
puts "Express Schema Loading Benchmark from YAML"
|
113
|
-
puts "--------------------------------"
|
114
|
-
|
115
|
-
# Load schema list from YAML
|
116
|
-
schema_list = YAML.load_file(yaml_path)
|
117
|
-
if schema_list.is_a?(Hash) && schema_list["schemas"]
|
118
|
-
# Handle format: { "schemas": ["path1", "path2", ...] }
|
119
|
-
schema_files = schema_list["schemas"]
|
120
|
-
elsif schema_list.is_a?(Array)
|
121
|
-
# Handle format: ["path1", "path2", ...]
|
122
|
-
schema_files = schema_list
|
123
|
-
else
|
124
|
-
puts "Invalid YAML format. Expected an array of schema paths or a hash with a 'schemas' key."
|
125
|
-
return
|
126
|
-
end
|
127
|
-
|
128
|
-
puts "YAML File: #{yaml_path}"
|
129
|
-
puts "Number of schemas in list: #{schema_files.size}"
|
130
|
-
puts "--------------------------------"
|
131
|
-
|
132
|
-
# Load the schemas
|
133
|
-
Expressir::Benchmark.measure_collection(schema_files) do |file|
|
134
|
-
repository = Expressir::Express::Parser.from_file(file)
|
135
|
-
repository.schemas
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def benchmark_cache_file(path, cache_path)
|
140
|
-
puts "Express Schema Loading Benchmark with Caching"
|
141
|
-
puts "--------------------------------"
|
142
|
-
puts "Schema: #{path}"
|
143
|
-
puts "Cache: #{cache_path}"
|
144
|
-
puts "--------------------------------"
|
145
|
-
|
146
|
-
# Benchmark with caching
|
147
|
-
results = Expressir::Benchmark.measure_with_cache(path, cache_path) do |file|
|
148
|
-
Expressir::Express::Parser.from_file(file)
|
149
|
-
end
|
150
|
-
|
151
|
-
if results[:repository]
|
152
|
-
schema_count = results[:repository].schemas.size
|
153
|
-
puts "Loaded #{schema_count} schemas"
|
154
|
-
else
|
155
|
-
puts "Failed to load schema"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def benchmark_cache_from_yaml(yaml_path, cache_path)
|
160
|
-
puts "Express Schema Loading Benchmark with Caching from YAML"
|
161
|
-
puts "--------------------------------"
|
162
|
-
|
163
|
-
# Load schema list from YAML
|
164
|
-
schema_list = YAML.load_file(yaml_path)
|
165
|
-
if schema_list.is_a?(Hash) && schema_list["schemas"]
|
166
|
-
# Handle format: { "schemas": ["path1", "path2", ...] }
|
167
|
-
schema_files = schema_list["schemas"]
|
168
|
-
elsif schema_list.is_a?(Array)
|
169
|
-
# Handle format: ["path1", "path2", ...]
|
170
|
-
schema_files = schema_list
|
171
|
-
else
|
172
|
-
puts "Invalid YAML format. Expected an array of schema paths or a hash with a 'schemas' key."
|
173
|
-
return
|
174
|
-
end
|
175
|
-
|
176
|
-
puts "YAML File: #{yaml_path}"
|
177
|
-
puts "Number of schemas in list: #{schema_files.size}"
|
178
|
-
puts "Cache: #{cache_path}"
|
179
|
-
puts "--------------------------------"
|
180
|
-
|
181
|
-
# Process each file with caching
|
182
|
-
schema_files.each_with_index do |file, index|
|
183
|
-
puts "Processing file #{index + 1}/#{schema_files.size}: #{file}"
|
184
|
-
|
185
|
-
# Benchmark with caching
|
186
|
-
Expressir::Benchmark.measure_with_cache(file, cache_path) do |path|
|
187
|
-
Expressir::Express::Parser.from_file(path)
|
188
|
-
end
|
189
|
-
|
190
|
-
puts "--------------------------------"
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def generate_temp_cache_path
|
195
|
-
require "tempfile"
|
196
|
-
Tempfile.new(["expressir_cache", ".bin"]).path
|
197
|
-
end
|
48
|
+
Commands::BenchmarkCache.new(options).run(path)
|
198
49
|
end
|
199
50
|
|
200
51
|
desc "validate *PATH", "validate EXPRESS schema located at PATH"
|
201
|
-
def validate(*paths)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
paths.each do |path|
|
206
|
-
x = Pathname.new(path).realpath.relative_path_from(Dir.pwd)
|
207
|
-
puts "Validating #{x}"
|
208
|
-
ret = _validate_schema(path)
|
209
|
-
|
210
|
-
if ret.nil?
|
211
|
-
no_valid << "Failed to parse: #{x}"
|
212
|
-
next
|
213
|
-
end
|
214
|
-
|
215
|
-
ret.each do |schema_id|
|
216
|
-
no_version << "Missing version string: schema `#{schema_id}` | #{x}"
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
_print_validation_errors(:failed_to_parse, no_valid)
|
221
|
-
_print_validation_errors(:missing_version_string, no_version)
|
222
|
-
|
223
|
-
exit 1 unless [no_valid, no_version].all?(&:empty?)
|
52
|
+
def validate(*paths)
|
53
|
+
Commands::Validate.new(options).run(paths)
|
54
|
+
end
|
224
55
|
|
225
|
-
|
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)"
|
60
|
+
def coverage(*paths)
|
61
|
+
Commands::Coverage.new(options).run(paths)
|
226
62
|
end
|
227
63
|
|
228
64
|
desc "version", "Expressir Version"
|
229
65
|
def version
|
230
|
-
|
66
|
+
Commands::Version.new(options).run
|
231
67
|
end
|
232
68
|
end
|
233
69
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Expressir
|
2
|
+
module Commands
|
3
|
+
class Base
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
# Common utilities that all commands might need
|
11
|
+
def say(message)
|
12
|
+
# Use instance variable for testability - if output is set, use it
|
13
|
+
if defined?(@output) && @output
|
14
|
+
@output.puts(message)
|
15
|
+
else
|
16
|
+
puts(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def exit_with_error(message, exit_code = 1)
|
21
|
+
say message
|
22
|
+
# In test mode, raise an exception instead of exiting
|
23
|
+
# This makes it easier to test error cases
|
24
|
+
if defined?(@test_mode) && @test_mode
|
25
|
+
raise message # Just raise the message as an exception
|
26
|
+
else
|
27
|
+
exit exit_code
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Expressir
|
2
|
+
module Commands
|
3
|
+
class Benchmark < Base
|
4
|
+
def run(path)
|
5
|
+
configure_benchmarking
|
6
|
+
|
7
|
+
if [".yml", ".yaml"].include?(File.extname(path).downcase)
|
8
|
+
benchmark_from_yaml(path)
|
9
|
+
else
|
10
|
+
benchmark_file(path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def configure_benchmarking
|
17
|
+
Expressir.configuration.benchmark_enabled = true
|
18
|
+
Expressir.configuration.benchmark_ips = options[:ips]
|
19
|
+
Expressir.configuration.benchmark_verbose = options[:verbose]
|
20
|
+
Expressir.configuration.benchmark_save = options[:save]
|
21
|
+
Expressir.configuration.benchmark_format = options[:format] if options[:format]
|
22
|
+
end
|
23
|
+
|
24
|
+
def benchmark_file(path)
|
25
|
+
say "Express Schema Loading Benchmark"
|
26
|
+
say "--------------------------------"
|
27
|
+
|
28
|
+
repository = Expressir::Benchmark.measure_file(path) do
|
29
|
+
Expressir::Express::Parser.from_file(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
if repository
|
33
|
+
say "Loaded #{repository.schemas.size} schemas"
|
34
|
+
else
|
35
|
+
say "Failed to load schema"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def benchmark_from_yaml(yaml_path)
|
40
|
+
say "Express Schema Loading Benchmark from YAML"
|
41
|
+
say "--------------------------------"
|
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
|
55
|
+
|
56
|
+
say "YAML File: #{yaml_path}"
|
57
|
+
say "Number of schemas in list: #{schema_files.size}"
|
58
|
+
say "--------------------------------"
|
59
|
+
|
60
|
+
# Load the schemas
|
61
|
+
Expressir::Benchmark.measure_collection(schema_files) do |file|
|
62
|
+
repository = Expressir::Express::Parser.from_file(file)
|
63
|
+
repository.schemas
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Expressir
|
2
|
+
module Commands
|
3
|
+
class BenchmarkCache < Base
|
4
|
+
def run(path)
|
5
|
+
configure_benchmarking
|
6
|
+
|
7
|
+
# Run benchmarks with cache
|
8
|
+
cache_path = options[:cache_path] || generate_temp_cache_path
|
9
|
+
|
10
|
+
if [".yml", ".yaml"].include?(File.extname(path).downcase)
|
11
|
+
benchmark_cache_from_yaml(path, cache_path)
|
12
|
+
else
|
13
|
+
benchmark_cache_file(path, cache_path)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def configure_benchmarking
|
20
|
+
Expressir.configuration.benchmark_enabled = true
|
21
|
+
Expressir.configuration.benchmark_ips = options[:ips]
|
22
|
+
Expressir.configuration.benchmark_verbose = options[:verbose]
|
23
|
+
Expressir.configuration.benchmark_save = options[:save]
|
24
|
+
Expressir.configuration.benchmark_format = options[:format] if options[:format]
|
25
|
+
end
|
26
|
+
|
27
|
+
def benchmark_cache_file(path, cache_path)
|
28
|
+
say "Express Schema Loading Benchmark with Caching"
|
29
|
+
say "--------------------------------"
|
30
|
+
say "Schema: #{path}"
|
31
|
+
say "Cache: #{cache_path}"
|
32
|
+
say "--------------------------------"
|
33
|
+
|
34
|
+
# Benchmark with caching
|
35
|
+
results = Expressir::Benchmark.measure_with_cache(path, cache_path) do |file|
|
36
|
+
Expressir::Express::Parser.from_file(file)
|
37
|
+
end
|
38
|
+
|
39
|
+
if results[:repository]
|
40
|
+
schema_count = results[:repository].schemas.size
|
41
|
+
say "Loaded #{schema_count} schemas"
|
42
|
+
else
|
43
|
+
say "Failed to load schema"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def benchmark_cache_from_yaml(yaml_path, cache_path)
|
48
|
+
say "Express Schema Loading Benchmark with Caching from YAML"
|
49
|
+
say "--------------------------------"
|
50
|
+
|
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
|
63
|
+
|
64
|
+
say "YAML File: #{yaml_path}"
|
65
|
+
say "Number of schemas in list: #{schema_files.size}"
|
66
|
+
say "Cache: #{cache_path}"
|
67
|
+
say "--------------------------------"
|
68
|
+
|
69
|
+
# Process each file with caching
|
70
|
+
schema_files.each_with_index do |file, index|
|
71
|
+
say "Processing file #{index + 1}/#{schema_files.size}: #{file}"
|
72
|
+
|
73
|
+
# Benchmark with caching
|
74
|
+
Expressir::Benchmark.measure_with_cache(file, cache_path) do |path|
|
75
|
+
Expressir::Express::Parser.from_file(path)
|
76
|
+
end
|
77
|
+
|
78
|
+
say "--------------------------------"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def generate_temp_cache_path
|
83
|
+
require "tempfile"
|
84
|
+
Tempfile.new(["expressir_cache", ".bin"]).path
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Expressir
|
2
|
+
module Commands
|
3
|
+
class Clean < Base
|
4
|
+
def run(path)
|
5
|
+
repository = Expressir::Express::Parser.from_file(path)
|
6
|
+
formatted_schemas = repository.schemas.map do |schema|
|
7
|
+
# Format schema without remarks
|
8
|
+
schema.to_s(no_remarks: true)
|
9
|
+
end.join("\n\n")
|
10
|
+
|
11
|
+
if options[:output]
|
12
|
+
File.write(options[:output], formatted_schemas)
|
13
|
+
say "Cleaned schema written to #{options[:output]}"
|
14
|
+
else
|
15
|
+
say formatted_schemas
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|