expressir 2.4.0 → 2.4.1

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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.bugs/01-stale-transformer-autoload.md +39 -0
  3. data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
  4. data/TODO.bugs/03-builder-mutable-state.md +43 -0
  5. data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
  6. data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
  7. data/TODO.bugs/06-collection-registry-single-source.md +53 -0
  8. data/TODO.bugs/07-require-relative-cleanup.md +42 -0
  9. data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
  10. data/TODO.bugs/09-parser-split.md +53 -0
  11. data/TODO.bugs/10-to-s-override.md +42 -0
  12. data/TODO.bugs/11-parser-class-variables.md +39 -0
  13. data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
  14. data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
  15. data/TODO.bugs/14-model-formatting-leak.md +30 -0
  16. data/TODO.bugs/15-expression-children-macro.md +27 -0
  17. data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
  18. data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
  19. data/TODO.bugs/18-const-get-private-constants.md +30 -0
  20. data/TODO.bugs/19-format-methods-public.md +22 -0
  21. data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
  22. data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
  23. data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
  24. data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
  25. data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
  26. data/TODO.bugs/25-debug-puts-in-production.md +21 -0
  27. data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
  28. data/TODO.bugs/27-package-build-god-method.md +19 -0
  29. data/TODO.bugs/28-package-god-class.md +30 -0
  30. data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
  31. data/TODO.bugs/30-unicode-map-extraction.md +19 -0
  32. data/TODO.bugs/README.md +43 -0
  33. data/docs/_tutorials/formatting-schemas.adoc +89 -0
  34. data/docs/_tutorials/index.adoc +10 -0
  35. data/docs/lychee.toml +3 -0
  36. data/expressir.gemspec +4 -2
  37. data/lib/expressir/commands/changes_import_eengine.rb +0 -6
  38. data/lib/expressir/commands/changes_validate.rb +0 -2
  39. data/lib/expressir/commands/clean.rb +1 -1
  40. data/lib/expressir/commands/file_violations.rb +70 -0
  41. data/lib/expressir/commands/format.rb +1 -1
  42. data/lib/expressir/commands/non_ascii_character.rb +49 -0
  43. data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
  44. data/lib/expressir/commands/package.rb +138 -185
  45. data/lib/expressir/commands/validate_ascii.rb +0 -510
  46. data/lib/expressir/commands/version.rb +1 -1
  47. data/lib/expressir/commands.rb +3 -0
  48. data/lib/expressir/coverage.rb +49 -117
  49. data/lib/expressir/express/builder.rb +74 -86
  50. data/lib/expressir/express/builder_context.rb +22 -0
  51. data/lib/expressir/express/builders/expression_builder.rb +16 -16
  52. data/lib/expressir/express/cache.rb +35 -8
  53. data/lib/expressir/express/error.rb +7 -0
  54. data/lib/expressir/express/formatter.rb +33 -18
  55. data/lib/expressir/express/formatters/declarations_formatter.rb +24 -2
  56. data/lib/expressir/express/formatters/remark_formatter.rb +75 -2
  57. data/lib/expressir/express/formatters/statements_formatter.rb +20 -3
  58. data/lib/expressir/express/grammar/parser.rb +705 -0
  59. data/lib/expressir/express/grammar.rb +11 -0
  60. data/lib/expressir/express/node_position_index.rb +215 -0
  61. data/lib/expressir/express/parser.rb +114 -884
  62. data/lib/expressir/express/pretty_formatter.rb +23 -5
  63. data/lib/expressir/express/remark_attacher.rb +475 -577
  64. data/lib/expressir/express/remark_scanner.rb +84 -19
  65. data/lib/expressir/express/schema_block_scanner.rb +136 -0
  66. data/lib/expressir/express/schema_source_formatter.rb +15 -0
  67. data/lib/expressir/express/scope_resolver.rb +194 -0
  68. data/lib/expressir/express/source_formatter.rb +15 -0
  69. data/lib/expressir/express/streaming_builder.rb +147 -176
  70. data/lib/expressir/express.rb +8 -6
  71. data/lib/expressir/model/concerns.rb +3 -0
  72. data/lib/expressir/model/data_types/generic_entity.rb +6 -6
  73. data/lib/expressir/model/declarations/entity.rb +5 -0
  74. data/lib/expressir/model/declarations/function.rb +5 -0
  75. data/lib/expressir/model/declarations/procedure.rb +5 -0
  76. data/lib/expressir/model/declarations/rule.rb +6 -0
  77. data/lib/expressir/model/declarations/schema.rb +21 -8
  78. data/lib/expressir/model/declarations/type.rb +3 -0
  79. data/lib/expressir/model/exp_file.rb +2 -0
  80. data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -0
  81. data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -0
  82. data/lib/expressir/model/expressions/binary_expression.rb +1 -0
  83. data/lib/expressir/model/expressions/entity_constructor.rb +1 -0
  84. data/lib/expressir/model/expressions/function_call.rb +1 -0
  85. data/lib/expressir/model/expressions/interval.rb +1 -0
  86. data/lib/expressir/model/expressions/query_expression.rb +2 -0
  87. data/lib/expressir/model/expressions/unary_expression.rb +1 -0
  88. data/lib/expressir/model/model_element.rb +128 -14
  89. data/lib/expressir/model/remark_format.rb +17 -0
  90. data/lib/expressir/model/remark_info.rb +31 -3
  91. data/lib/expressir/model/remark_placement.rb +29 -0
  92. data/lib/expressir/model/statements/alias.rb +2 -0
  93. data/lib/expressir/model/statements/assignment.rb +4 -0
  94. data/lib/expressir/model/statements/case.rb +21 -0
  95. data/lib/expressir/model/statements/case_action.rb +1 -0
  96. data/lib/expressir/model/statements/compound.rb +4 -0
  97. data/lib/expressir/model/statements/escape.rb +3 -0
  98. data/lib/expressir/model/statements/if.rb +5 -0
  99. data/lib/expressir/model/statements/null.rb +3 -0
  100. data/lib/expressir/model/statements/procedure_call.rb +3 -0
  101. data/lib/expressir/model/statements/repeat.rb +3 -0
  102. data/lib/expressir/model/statements/return.rb +3 -0
  103. data/lib/expressir/model/statements/skip.rb +3 -0
  104. data/lib/expressir/model.rb +12 -3
  105. data/lib/expressir/package/builder.rb +2 -2
  106. data/lib/expressir/version.rb +6 -1
  107. data/lib/expressir.rb +38 -3
  108. metadata +63 -5
  109. data/lib/expressir/express/transformer/remark_handling.rb +0 -194
@@ -92,201 +92,154 @@ module Expressir
92
92
  option :verbose, type: :boolean, default: false,
93
93
  desc: "Enable verbose output"
94
94
  def build(root_schema = nil, output = nil)
95
- schema_files = if options[:manifest]
96
- # Manifest-based mode
97
- unless File.exist?(options[:manifest])
98
- raise Expressir::ManifestNotFoundError.new(options[:manifest])
99
- end
100
-
101
- # Override output if not provided
102
- output ||= root_schema
103
- unless output
104
- raise Expressir::MissingRequiredArgumentError.new(
105
- "OUTPUT path is required",
106
- usage_hint: "expressir package build --manifest MANIFEST.yaml OUTPUT.ler",
107
- )
108
- end
109
-
110
- # Build repository
111
- say "Building LER package from manifest #{options[:manifest]}..." if options[:verbose]
112
-
113
- # Load manifest for verification and data extraction
114
- manifest = Expressir::SchemaManifest.from_file(options[:manifest])
115
- manifest_data = YAML.load_file(options[:manifest])
116
- manifest_dir = File.dirname(File.expand_path(options[:manifest]))
117
-
118
- # Verify manifest unless --skip-verify is used
119
- if options[:skip_verify]
120
- say "⚠ Warning: Skipping manifest verification",
121
- :yellow
122
- say " This set of EXPRESS schemas may not be fully internally consistent.",
123
- :yellow
124
- say ""
125
- else
126
- say "Verifying manifest integrity..."
127
-
128
- validator = Expressir::Manifest::Validator.new(
129
- manifest, options.merge(verbose: true)
130
- )
131
-
132
- # Check file existence
133
- file_errors = validator.validate_file_existence
134
- unless file_errors.empty?
135
- raise Expressir::ManifestValidationError.new(
136
- "Manifest validation failed",
137
- errors: file_errors.map { |e| e[:message] },
138
- )
139
- end
140
-
141
- # Check referential integrity
142
- reference_errors = validator.validate_referential_integrity
143
- unless reference_errors.empty?
144
- raise Expressir::ReferentialIntegrityError.new(
145
- reference_errors,
146
- message: "Manifest has unresolved dependencies",
147
- )
148
- end
149
-
150
- say "✓ Manifest verified", :green
151
- end
152
-
153
- # Validate and collect paths
154
- errors = []
155
- warnings = []
156
- paths = []
157
-
158
- manifest_data["schemas"].each do |schema_id, schema_data|
159
- schema_path = schema_data["path"]
160
-
161
- if schema_path.nil? || schema_path.empty?
162
- warnings << "Schema '#{schema_id}' has no path specified"
163
- next
164
- end
165
-
166
- # Expand relative paths relative to manifest directory
167
- full_path = if Pathname.new(schema_path).absolute?
168
- schema_path
169
- else
170
- File.expand_path(schema_path,
171
- manifest_dir)
172
- end
173
-
174
- unless File.exist?(full_path)
175
- errors << "Schema file not found: #{full_path} (#{schema_id})"
176
- next
177
- end
178
-
179
- paths << full_path
180
- end
181
-
182
- unless errors.empty?
183
- raise Expressir::ManifestValidationError.new(
184
- "Manifest validation failed",
185
- errors: errors,
186
- )
187
- end
188
-
189
- if options[:verbose] && warnings.any?
190
- say "Warnings:"
191
- warnings.each { |w| say " - #{w}", :yellow }
192
- end
193
-
194
- say " Using #{paths.size} schema(s) from manifest" if options[:verbose]
195
- paths
196
- else
197
- # Auto-resolution mode
198
- unless root_schema
199
- raise Expressir::MissingRequiredArgumentError.new(
200
- "ROOT_SCHEMA is required when not using --manifest",
201
- usage_hint: "expressir package build ROOT_SCHEMA OUTPUT.ler",
202
- )
203
- end
204
-
205
- unless output
206
- raise Expressir::MissingRequiredArgumentError.new(
207
- "OUTPUT path is required",
208
- usage_hint: "expressir package build ROOT_SCHEMA OUTPUT.ler",
209
- )
210
- end
211
-
212
- say "Building LER package from #{root_schema}..." if options[:verbose]
213
-
214
- # Resolve dependencies
215
- say "Resolving dependencies..." if options[:verbose]
216
- base_dirs = if options[:base_dirs]
217
- options[:base_dirs].split(",").map(&:strip)
218
- else
219
- # Default to the directory containing the root schema
220
- [File.dirname(File.expand_path(root_schema))]
221
- end
222
-
223
- if options[:verbose] && base_dirs.size == 1
224
- say " Using base directory: #{base_dirs.first}"
225
- say " Tip: Use --base-dirs to specify additional search paths for dependencies"
226
- end
227
-
228
- resolver = Expressir::Model::DependencyResolver.new(
229
- base_dirs: base_dirs,
230
- verbose: options[:verbose],
231
- )
232
- resolved = resolver.resolve_dependencies(root_schema)
233
- say " Found #{resolved.size} schema(s)" if options[:verbose]
234
- resolved
235
- end
236
-
237
- # Build repository
238
- say "Building repository..." if options[:verbose]
95
+ schema_files, output = resolve_schema_files(root_schema, output)
239
96
  repo = Expressir::Model::Repository.from_files(schema_files)
97
+ validate_repository(repo) if should_validate?
98
+ create_package(repo, output)
99
+ rescue Expressir::Error
100
+ raise
101
+ rescue StandardError => e
102
+ raise Expressir::PackageBuildError.new(
103
+ "Error building package: #{e.message}",
104
+ command_name: "package build",
105
+ )
106
+ end
107
+
108
+ private
109
+
110
+ def resolve_schema_files(root_schema, output)
111
+ if options[:manifest]
112
+ [resolve_from_manifest(output), output || root_schema]
113
+ else
114
+ [resolve_from_auto_resolution(root_schema), output]
115
+ end
116
+ end
117
+
118
+ def resolve_from_manifest(output)
119
+ raise Expressir::ManifestNotFoundError.new(options[:manifest]) unless File.exist?(options[:manifest])
120
+
121
+ unless output || root_schema_arg
122
+ raise Expressir::MissingRequiredArgumentError.new(
123
+ "OUTPUT path is required",
124
+ usage_hint: "expressir package build --manifest MANIFEST.yaml OUTPUT.ler",
125
+ )
126
+ end
127
+
128
+ say "Building LER package from manifest #{options[:manifest]}..." if options[:verbose]
129
+
130
+ manifest = Expressir::SchemaManifest.from_file(options[:manifest])
131
+ manifest_data = YAML.load_file(options[:manifest])
132
+ manifest_dir = File.dirname(File.expand_path(options[:manifest]))
133
+
134
+ verify_manifest(manifest) unless options[:skip_verify]
135
+ collect_manifest_paths(manifest_data, manifest_dir)
136
+ end
137
+
138
+ def verify_manifest(manifest)
139
+ say "Verifying manifest integrity..."
140
+ validator = Expressir::Manifest::Validator.new(manifest, options.merge(verbose: true))
141
+
142
+ file_errors = validator.validate_file_existence
143
+ unless file_errors.empty?
144
+ raise Expressir::ManifestValidationError.new(
145
+ "Manifest validation failed", errors: file_errors.map { |e| e[:message] }
146
+ )
147
+ end
148
+
149
+ reference_errors = validator.validate_referential_integrity
150
+ unless reference_errors.empty?
151
+ raise Expressir::ReferentialIntegrityError.new(
152
+ reference_errors, message: "Manifest has unresolved dependencies"
153
+ )
154
+ end
155
+
156
+ say "✓ Manifest verified", :green
157
+ end
158
+
159
+ def collect_manifest_paths(manifest_data, manifest_dir)
160
+ errors = []
161
+ warnings = []
162
+ paths = []
240
163
 
241
- # Validate if requested
242
- # When using --skip-verify, skip validation unless explicitly requested with --validate
243
- should_validate = if options[:manifest] && options[:skip_verify]
244
- # Check if --validate was explicitly passed
245
- # Thor doesn't have a way to check if option was set by user vs default
246
- # So we check if --no-validate was explicitly disabled
247
- false
248
- else
249
- options[:validate]
250
- end
251
-
252
- if should_validate
253
- say "Validating repository..." if options[:verbose]
254
- validation = repo.validate
255
- unless validation[:valid?]
256
- raise Expressir::SchemaValidationError.new(
257
- "Repository validation failed",
258
- errors: (validation[:errors] || []).map do |e|
259
- if e.is_a?(Hash)
260
- msg = e[:message] || "Unknown error"
261
- type = e[:type] ? "[#{e[:type]}] " : ""
262
- schema = e[:schema] ? " (schema: #{e[:schema]})" : ""
263
- ref = e[:referenced_schema] ? " (referenced: #{e[:referenced_schema]})" : ""
264
- iface = e[:interface_type] ? " (interface: #{e[:interface_type]})" : ""
265
- "#{type}#{msg}#{schema}#{ref}#{iface}"
266
- else
267
- e.to_s
268
- end
269
- end,
270
- )
164
+ manifest_data["schemas"].each do |schema_id, schema_data|
165
+ schema_path = schema_data["path"]
166
+ if schema_path.nil? || schema_path.empty?
167
+ warnings << "Schema '#{schema_id}' has no path specified"
168
+ next
271
169
  end
272
- say " ✓ Validation passed" if options[:verbose]
170
+
171
+ full_path = Pathname.new(schema_path).absolute? ? schema_path : File.expand_path(schema_path, manifest_dir)
172
+ if File.exist?(full_path)
173
+ paths << full_path
174
+ else
175
+ errors << "Schema file not found: #{full_path} (#{schema_id})"
176
+ end
177
+ end
178
+
179
+ raise Expressir::ManifestValidationError.new("Manifest validation failed", errors: errors) unless errors.empty?
180
+
181
+ say "Warnings:" if options[:verbose] && warnings.any?
182
+ warnings.each { |w| say " - #{w}", :yellow } if options[:verbose] && warnings.any?
183
+ say " Using #{paths.size} schema(s) from manifest" if options[:verbose]
184
+ paths
185
+ end
186
+
187
+ def resolve_from_auto_resolution(root_schema)
188
+ unless root_schema
189
+ raise Expressir::MissingRequiredArgumentError.new(
190
+ "ROOT_SCHEMA is required when not using --manifest",
191
+ usage_hint: "expressir package build ROOT_SCHEMA OUTPUT.ler",
192
+ )
193
+ end
194
+
195
+ say "Building LER package from #{root_schema}..." if options[:verbose]
196
+ say "Resolving dependencies..." if options[:verbose]
197
+
198
+ base_dirs = options[:base_dirs] ? options[:base_dirs].split(",").map(&:strip) : [File.dirname(File.expand_path(root_schema))]
199
+ if options[:verbose] && base_dirs.size == 1
200
+ say " Using base directory: #{base_dirs.first}"
201
+ say " Tip: Use --base-dirs to specify additional search paths for dependencies"
202
+ end
203
+
204
+ resolver = Expressir::Model::DependencyResolver.new(base_dirs: base_dirs, verbose: options[:verbose])
205
+ resolved = resolver.resolve_dependencies(root_schema)
206
+ say " Found #{resolved.size} schema(s)" if options[:verbose]
207
+ resolved
208
+ end
209
+
210
+ def should_validate?
211
+ return false if options[:manifest] && options[:skip_verify]
212
+
213
+ options[:validate]
214
+ end
215
+
216
+ def validate_repository(repo)
217
+ say "Validating repository..." if options[:verbose]
218
+ validation = repo.validate
219
+ return if validation[:valid?]
220
+
221
+ formatted_errors = (validation[:errors] || []).map do |e|
222
+ next e.to_s unless e.is_a?(Hash)
223
+
224
+ msg = e[:message] || "Unknown error"
225
+ type = e[:type] ? "[#{e[:type]}] " : ""
226
+ schema = e[:schema] ? " (schema: #{e[:schema]})" : ""
227
+ ref = e[:referenced_schema] ? " (referenced: #{e[:referenced_schema]})" : ""
228
+ iface = e[:interface_type] ? " (interface: #{e[:interface_type]})" : ""
229
+ "#{type}#{msg}#{schema}#{ref}#{iface}"
273
230
  end
231
+ raise Expressir::SchemaValidationError.new("Repository validation failed", errors: formatted_errors)
232
+ end
274
233
 
275
- # Build package
234
+ def create_package(repo, output)
276
235
  say "Creating package..." if options[:verbose]
277
236
  repo.export_to_package(output, build_package_options)
278
-
279
237
  say "✓ Package created: #{output}", :green
280
238
  say " Schemas: #{repo.schemas.size}", :green if options[:verbose]
281
- rescue Expressir::Error
282
- raise # Re-raise Expressir errors
283
- rescue StandardError => e
284
- raise Expressir::PackageBuildError.new(
285
- "Error building package: #{e.message}",
286
- command_name: "package build",
287
- )
288
239
  end
289
240
 
241
+ public
242
+
290
243
  desc "info PACKAGE", "Display package metadata and statistics"
291
244
  long_desc <<~DESC
292
245
  Display comprehensive information about an LER package including metadata,
@@ -430,7 +383,7 @@ module Expressir
430
383
  zip.each do |entry|
431
384
  dest_path = File.join(output_dir, entry.name)
432
385
  FileUtils.mkdir_p(File.dirname(dest_path))
433
- entry.extract(dest_path) unless File.exist?(dest_path)
386
+ entry.extract(entry.name, destination_directory: output_dir) unless File.exist?(dest_path)
434
387
  end
435
388
  end
436
389