expressir 2.4.29-arm-linux

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 (426) hide show
  1. checksums.yaml +7 -0
  2. data/.cargo/config.toml +3 -0
  3. data/.github/scripts/test_installed_gem.rb +34 -0
  4. data/.github/workflows/codeql.yml +35 -0
  5. data/.github/workflows/docs.yml +99 -0
  6. data/.github/workflows/links.yml +100 -0
  7. data/.github/workflows/native-gems.yml +201 -0
  8. data/.github/workflows/rake.yml +19 -0
  9. data/.github/workflows/release.yml +37 -0
  10. data/.github/workflows/rust-ext.yml +41 -0
  11. data/.github/workflows/stress.yml +36 -0
  12. data/.github/workflows/validate_schemas.yml +48 -0
  13. data/.github/workflows/verify_remarks.yml +33 -0
  14. data/.gitignore +55 -0
  15. data/.hound.yml +3 -0
  16. data/.rspec +2 -0
  17. data/.rubocop.yml +18 -0
  18. data/.rubocop_todo.yml +297 -0
  19. data/CHANGELOG.md +159 -0
  20. data/Gemfile +18 -0
  21. data/README.adoc +2783 -0
  22. data/Rakefile +41 -0
  23. data/TODO.bugs/01-stale-transformer-autoload.md +39 -0
  24. data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
  25. data/TODO.bugs/03-builder-mutable-state.md +43 -0
  26. data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
  27. data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
  28. data/TODO.bugs/06-collection-registry-single-source.md +53 -0
  29. data/TODO.bugs/07-require-relative-cleanup.md +42 -0
  30. data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
  31. data/TODO.bugs/09-parser-split.md +53 -0
  32. data/TODO.bugs/10-to-s-override.md +42 -0
  33. data/TODO.bugs/11-parser-class-variables.md +39 -0
  34. data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
  35. data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
  36. data/TODO.bugs/14-model-formatting-leak.md +30 -0
  37. data/TODO.bugs/15-expression-children-macro.md +27 -0
  38. data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
  39. data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
  40. data/TODO.bugs/18-const-get-private-constants.md +30 -0
  41. data/TODO.bugs/19-format-methods-public.md +22 -0
  42. data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
  43. data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
  44. data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
  45. data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
  46. data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
  47. data/TODO.bugs/25-debug-puts-in-production.md +21 -0
  48. data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
  49. data/TODO.bugs/27-package-build-god-method.md +19 -0
  50. data/TODO.bugs/28-package-god-class.md +30 -0
  51. data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
  52. data/TODO.bugs/30-unicode-map-extraction.md +19 -0
  53. data/TODO.bugs/README.md +43 -0
  54. data/TODO.max-perf/01-restore-ci-green.md +36 -0
  55. data/TODO.max-perf/02-streaming-parse-path.md +31 -0
  56. data/TODO.max-perf/03-cli-parallel-opt-in.md +27 -0
  57. data/TODO.max-perf/04-benchmark-harness.md +28 -0
  58. data/TODO.max-perf/05-parallel-fidelity-specs.md +22 -0
  59. data/TODO.max-perf/06-builder-cpu-audit.md +41 -0
  60. data/TODO.max-perf/07-upstream-parsanol-roadmap.md +27 -0
  61. data/TODO.max-perf/08-builder-build-perf.md +45 -0
  62. data/TODO.max-perf/09-grammar-cold-start.md +25 -0
  63. data/TODO.max-perf/10-parser-facade-hygiene.md +23 -0
  64. data/TODO.max-perf/11-ci-green-closeout.md +25 -0
  65. data/TODO.max-perf/12-require-boot-profile.md +25 -0
  66. data/TODO.max-perf/13-key-conversion-specs.md +26 -0
  67. data/TODO.max-perf/14-builder-call-handler-audit.md +28 -0
  68. data/TODO.parity-ee/00-overview.md +76 -0
  69. data/TODO.parity-ee/01-annex-g-rule-extraction.md +41 -0
  70. data/TODO.parity-ee/02-eeng-algorithm-inventory.md +8 -0
  71. data/TODO.parity-ee/03-eeng-oracle-harness.md +33 -0
  72. data/TODO.parity-ee/04-shtolo-concatenate.md +44 -0
  73. data/TODO.parity-ee/05-shtolo-longform-flatten.md +53 -0
  74. data/TODO.parity-ee/06-interface-scheduling-parity.md +47 -0
  75. data/TODO.parity-ee/07-semantic-checks-port.md +35 -0
  76. data/TODO.parity-ee/08-pretty-roundtrip-gate.md +27 -0
  77. data/TODO.parity-ee/09-smrl-index-and-listing.md +22 -0
  78. data/TODO.parity-ee/10-interface-dot-graph.md +21 -0
  79. data/TODO.parity-ee/11-import-eeng-tests.md +79 -0
  80. data/TODO.parity-ee/12-full-check-catalog.md +67 -0
  81. data/TODO.parity-ee/13-population-p21.md +54 -0
  82. data/TODO.parity-ee/14-shtolo-completion.md +44 -0
  83. data/TODO.parity-ee/15-part28-xml.md +51 -0
  84. data/TODO.parity-ee/16-compare-patch.md +48 -0
  85. data/TODO.parity-ee/17-official-tests.md +42 -0
  86. data/TODO.parity-ee/18-mim-mapping.md +47 -0
  87. data/TODO.parity-ee/19-architecture.md +97 -0
  88. data/TODO.parity-ee/parity-matrix.md +108 -0
  89. data/TODO.refpath-v2/00-overview.md +64 -0
  90. data/TODO.suma-improvements/00-overview.md +23 -0
  91. data/TODO.suma-improvements/01-prebuilt-platform-gems.md +33 -0
  92. data/TODO.suma-improvements/02-suma-e2e.md +39 -0
  93. data/TODO.suma-improvements/03-item-graph.md +22 -0
  94. data/TODO.suma-improvements/04-compiled-set-default.md +16 -0
  95. data/TODO.suma-improvements/05-validation-in-docs.md +17 -0
  96. data/TODO.suma-improvements/06-memory-remeasure.md +36 -0
  97. data/TODO.suma-improvements/07-part28-xml.md +15 -0
  98. data/TODO.suma-improvements/08-windows-gnu-toolchain.md +15 -0
  99. data/TODO.suma-improvements/09-checker-tranche3.md +13 -0
  100. data/TODO.suma-improvements/10-expressdoc.md +8 -0
  101. data/benchmark/srl_benchmark.rb +458 -0
  102. data/benchmark/srl_native_benchmark.rb +146 -0
  103. data/benchmark/srl_ruby_benchmark.rb +132 -0
  104. data/bin/console +11 -0
  105. data/bin/rspec +30 -0
  106. data/bin/setup +8 -0
  107. data/docs/Gemfile +12 -0
  108. data/docs/_config.yml +141 -0
  109. data/docs/_guides/changes/changes-format.adoc +778 -0
  110. data/docs/_guides/changes/importing-eengine.adoc +898 -0
  111. data/docs/_guides/changes/index.adoc +396 -0
  112. data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
  113. data/docs/_guides/changes/validating-changes.adoc +681 -0
  114. data/docs/_guides/cli/benchmark-performance.adoc +834 -0
  115. data/docs/_guides/cli/coverage-analysis.adoc +921 -0
  116. data/docs/_guides/cli/format-schemas.adoc +547 -0
  117. data/docs/_guides/cli/index.adoc +8 -0
  118. data/docs/_guides/cli/managing-changes.adoc +927 -0
  119. data/docs/_guides/cli/validate-ascii.adoc +645 -0
  120. data/docs/_guides/cli/validate-schemas.adoc +534 -0
  121. data/docs/_guides/formatter/formatter-architecture.adoc +401 -0
  122. data/docs/_guides/index.adoc +165 -0
  123. data/docs/_guides/ler/creating-packages.adoc +664 -0
  124. data/docs/_guides/ler/index.adoc +305 -0
  125. data/docs/_guides/ler/loading-packages.adoc +707 -0
  126. data/docs/_guides/ler/package-formats.adoc +748 -0
  127. data/docs/_guides/ler/querying-packages.adoc +826 -0
  128. data/docs/_guides/ler/step-packages.adoc +385 -0
  129. data/docs/_guides/ler/validating-packages.adoc +750 -0
  130. data/docs/_guides/liquid/basic-templates.adoc +813 -0
  131. data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
  132. data/docs/_guides/liquid/drops-reference.adoc +829 -0
  133. data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
  134. data/docs/_guides/liquid/index.adoc +468 -0
  135. data/docs/_guides/manifests/creating-manifests.adoc +483 -0
  136. data/docs/_guides/manifests/index.adoc +307 -0
  137. data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
  138. data/docs/_guides/manifests/validating-manifests.adoc +713 -0
  139. data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
  140. data/docs/_guides/ruby-api/index.adoc +257 -0
  141. data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
  142. data/docs/_guides/ruby-api/search-engine.adoc +609 -0
  143. data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
  144. data/docs/_pages/data-model.adoc +665 -0
  145. data/docs/_pages/express-language.adoc +506 -0
  146. data/docs/_pages/getting-started.adoc +414 -0
  147. data/docs/_pages/index.adoc +116 -0
  148. data/docs/_pages/introduction.adoc +256 -0
  149. data/docs/_pages/ler-packages.adoc +837 -0
  150. data/docs/_pages/parsers.adoc +709 -0
  151. data/docs/_pages/schema-manifests.adoc +431 -0
  152. data/docs/_references/index.adoc +228 -0
  153. data/docs/_tutorials/creating-ler-package.adoc +735 -0
  154. data/docs/_tutorials/documentation-coverage.adoc +795 -0
  155. data/docs/_tutorials/formatting-schemas.adoc +89 -0
  156. data/docs/_tutorials/index.adoc +231 -0
  157. data/docs/_tutorials/liquid-templates.adoc +806 -0
  158. data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
  159. data/docs/_tutorials/querying-schemas.adoc +751 -0
  160. data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
  161. data/docs/index.adoc +242 -0
  162. data/docs/lychee.toml +90 -0
  163. data/examples/demo_ler_usage.sh +86 -0
  164. data/examples/ler/README.md +111 -0
  165. data/examples/ler/simple_example.ler +0 -0
  166. data/examples/ler/simple_schema.exp +33 -0
  167. data/examples/ler_build.rb +75 -0
  168. data/examples/ler_cli.rb +79 -0
  169. data/examples/ler_demo_complete.rb +276 -0
  170. data/examples/ler_query.rb +91 -0
  171. data/examples/ler_query_examples.rb +305 -0
  172. data/examples/ler_stats.rb +81 -0
  173. data/examples/phase3_demo.rb +159 -0
  174. data/examples/query_demo_simple.rb +131 -0
  175. data/exe/expressir +11 -0
  176. data/exe/expressir-format +14 -0
  177. data/exe/expressir-format-test +82 -0
  178. data/expressir.gemspec +61 -0
  179. data/lib/expressir/3.2/expressir_core.so +0 -0
  180. data/lib/expressir/3.3/expressir_core.so +0 -0
  181. data/lib/expressir/3.4/expressir_core.so +0 -0
  182. data/lib/expressir/4.0/expressir_core.so +0 -0
  183. data/lib/expressir/benchmark.rb +310 -0
  184. data/lib/expressir/changes/item_change.rb +20 -0
  185. data/lib/expressir/changes/mapping_change.rb +16 -0
  186. data/lib/expressir/changes/schema_change.rb +85 -0
  187. data/lib/expressir/changes/version_change.rb +26 -0
  188. data/lib/expressir/changes.rb +9 -0
  189. data/lib/expressir/cli.rb +148 -0
  190. data/lib/expressir/commands/base.rb +25 -0
  191. data/lib/expressir/commands/benchmark.rb +59 -0
  192. data/lib/expressir/commands/benchmark_cache.rb +80 -0
  193. data/lib/expressir/commands/changes.rb +34 -0
  194. data/lib/expressir/commands/changes_import_eengine.rb +146 -0
  195. data/lib/expressir/commands/changes_validate.rb +63 -0
  196. data/lib/expressir/commands/check.rb +56 -0
  197. data/lib/expressir/commands/clean.rb +20 -0
  198. data/lib/expressir/commands/coverage.rb +458 -0
  199. data/lib/expressir/commands/expand.rb +26 -0
  200. data/lib/expressir/commands/file_violations.rb +70 -0
  201. data/lib/expressir/commands/fix.rb +33 -0
  202. data/lib/expressir/commands/flatten.rb +29 -0
  203. data/lib/expressir/commands/format.rb +43 -0
  204. data/lib/expressir/commands/manifest.rb +420 -0
  205. data/lib/expressir/commands/mapping_validate.rb +68 -0
  206. data/lib/expressir/commands/non_ascii_character.rb +49 -0
  207. data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
  208. data/lib/expressir/commands/package.rb +1222 -0
  209. data/lib/expressir/commands/parity_inputs.rb +160 -0
  210. data/lib/expressir/commands/validate.rb +106 -0
  211. data/lib/expressir/commands/validate_ascii.rb +94 -0
  212. data/lib/expressir/commands/validate_load.rb +91 -0
  213. data/lib/expressir/commands/version.rb +9 -0
  214. data/lib/expressir/commands/xsd.rb +23 -0
  215. data/lib/expressir/commands.rb +30 -0
  216. data/lib/expressir/config.rb +60 -0
  217. data/lib/expressir/coverage.rb +510 -0
  218. data/lib/expressir/eengine/arm_compare_report.rb +23 -0
  219. data/lib/expressir/eengine/changes_section.rb +16 -0
  220. data/lib/expressir/eengine/compare_report.rb +69 -0
  221. data/lib/expressir/eengine/mim_compare_report.rb +23 -0
  222. data/lib/expressir/eengine/modified_object.rb +21 -0
  223. data/lib/expressir/eengine.rb +9 -0
  224. data/lib/expressir/errors.rb +113 -0
  225. data/lib/expressir/express/adoc_hyperlink_formatter.rb +30 -0
  226. data/lib/expressir/express/adoc_source_formatter.rb +12 -0
  227. data/lib/expressir/express/ast_key_converter.rb +114 -0
  228. data/lib/expressir/express/builder.rb +277 -0
  229. data/lib/expressir/express/builder_context.rb +22 -0
  230. data/lib/expressir/express/builder_registry.rb +397 -0
  231. data/lib/expressir/express/builders/attribute_decl_builder.rb +32 -0
  232. data/lib/expressir/express/builders/built_in_builder.rb +75 -0
  233. data/lib/expressir/express/builders/constant_builder.rb +108 -0
  234. data/lib/expressir/express/builders/declaration_builder.rb +20 -0
  235. data/lib/expressir/express/builders/derive_clause_builder.rb +14 -0
  236. data/lib/expressir/express/builders/derived_attr_builder.rb +28 -0
  237. data/lib/expressir/express/builders/domain_rule_builder.rb +21 -0
  238. data/lib/expressir/express/builders/entity_decl_builder.rb +116 -0
  239. data/lib/expressir/express/builders/explicit_attr_builder.rb +49 -0
  240. data/lib/expressir/express/builders/expression_builder.rb +416 -0
  241. data/lib/expressir/express/builders/function_decl_builder.rb +86 -0
  242. data/lib/expressir/express/builders/helpers.rb +148 -0
  243. data/lib/expressir/express/builders/interface_builder.rb +151 -0
  244. data/lib/expressir/express/builders/inverse_attr_builder.rb +43 -0
  245. data/lib/expressir/express/builders/inverse_attr_type_builder.rb +30 -0
  246. data/lib/expressir/express/builders/inverse_clause_builder.rb +14 -0
  247. data/lib/expressir/express/builders/literal_builder.rb +93 -0
  248. data/lib/expressir/express/builders/procedure_decl_builder.rb +82 -0
  249. data/lib/expressir/express/builders/qualifier_builder.rb +102 -0
  250. data/lib/expressir/express/builders/reference_builder.rb +18 -0
  251. data/lib/expressir/express/builders/rule_decl_builder.rb +97 -0
  252. data/lib/expressir/express/builders/schema_body_decl_builder.rb +18 -0
  253. data/lib/expressir/express/builders/schema_decl_builder.rb +56 -0
  254. data/lib/expressir/express/builders/schema_version_builder.rb +34 -0
  255. data/lib/expressir/express/builders/simple_id_builder.rb +17 -0
  256. data/lib/expressir/express/builders/statement_builder.rb +243 -0
  257. data/lib/expressir/express/builders/subtype_constraint_builder.rb +166 -0
  258. data/lib/expressir/express/builders/syntax_builder.rb +30 -0
  259. data/lib/expressir/express/builders/type_builder.rb +238 -0
  260. data/lib/expressir/express/builders/type_decl_builder.rb +26 -0
  261. data/lib/expressir/express/builders/unique_clause_builder.rb +20 -0
  262. data/lib/expressir/express/builders/unique_rule_builder.rb +41 -0
  263. data/lib/expressir/express/builders/where_clause_builder.rb +20 -0
  264. data/lib/expressir/express/builders.rb +55 -0
  265. data/lib/expressir/express/cache.rb +76 -0
  266. data/lib/expressir/express/checker.rb +812 -0
  267. data/lib/expressir/express/concatenator.rb +79 -0
  268. data/lib/expressir/express/core.rb +95 -0
  269. data/lib/expressir/express/error.rb +124 -0
  270. data/lib/expressir/express/formatter.rb +149 -0
  271. data/lib/expressir/express/formatters/data_types_formatter.rb +362 -0
  272. data/lib/expressir/express/formatters/declarations_formatter.rb +753 -0
  273. data/lib/expressir/express/formatters/expressions_formatter.rb +180 -0
  274. data/lib/expressir/express/formatters/literals_formatter.rb +55 -0
  275. data/lib/expressir/express/formatters/references_formatter.rb +53 -0
  276. data/lib/expressir/express/formatters/remark_formatter.rb +270 -0
  277. data/lib/expressir/express/formatters/remark_item_formatter.rb +28 -0
  278. data/lib/expressir/express/formatters/statements_formatter.rb +272 -0
  279. data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +54 -0
  280. data/lib/expressir/express/formatters.rb +22 -0
  281. data/lib/expressir/express/grammar/parser.rb +712 -0
  282. data/lib/expressir/express/grammar.rb +11 -0
  283. data/lib/expressir/express/hyperlink_formatter.rb +35 -0
  284. data/lib/expressir/express/interface_dot.rb +105 -0
  285. data/lib/expressir/express/lazy_repository.rb +211 -0
  286. data/lib/expressir/express/line_map.rb +48 -0
  287. data/lib/expressir/express/listing.rb +174 -0
  288. data/lib/expressir/express/model_traversal.rb +42 -0
  289. data/lib/expressir/express/model_visitor.rb +25 -0
  290. data/lib/expressir/express/node_position_index.rb +342 -0
  291. data/lib/expressir/express/parallel_files.rb +229 -0
  292. data/lib/expressir/express/parser.rb +523 -0
  293. data/lib/expressir/express/pretty_formatter.rb +586 -0
  294. data/lib/expressir/express/pretty_gate.rb +61 -0
  295. data/lib/expressir/express/refs_overlay.rb +47 -0
  296. data/lib/expressir/express/remark_attacher.rb +1098 -0
  297. data/lib/expressir/express/remark_overlay.rb +86 -0
  298. data/lib/expressir/express/remark_scanner.rb +245 -0
  299. data/lib/expressir/express/resolve_references_model_visitor.rb +29 -0
  300. data/lib/expressir/express/schema_block_scanner.rb +137 -0
  301. data/lib/expressir/express/schema_head_formatter.rb +22 -0
  302. data/lib/expressir/express/schema_plain_source_formatter.rb +11 -0
  303. data/lib/expressir/express/schema_source_formatter.rb +15 -0
  304. data/lib/expressir/express/scope_resolver.rb +223 -0
  305. data/lib/expressir/express/self_schema_reference.rb +126 -0
  306. data/lib/expressir/express/shtolo.rb +679 -0
  307. data/lib/expressir/express/smrl_xml.rb +125 -0
  308. data/lib/expressir/express/source_formatter.rb +15 -0
  309. data/lib/expressir/express/streaming_builder.rb +435 -0
  310. data/lib/expressir/express/xsd.rb +259 -0
  311. data/lib/expressir/express.rb +53 -0
  312. data/lib/expressir/liquid.rb +1 -0
  313. data/lib/expressir/manifest/resolver.rb +210 -0
  314. data/lib/expressir/manifest/validator.rb +192 -0
  315. data/lib/expressir/manifest.rb +6 -0
  316. data/lib/expressir/mapping/refpath.rb +857 -0
  317. data/lib/expressir/mapping.rb +253 -0
  318. data/lib/expressir/model/cache.rb +18 -0
  319. data/lib/expressir/model/concerns.rb +42 -0
  320. data/lib/expressir/model/data_types/aggregate.rb +26 -0
  321. data/lib/expressir/model/data_types/array.rb +25 -0
  322. data/lib/expressir/model/data_types/bag.rb +21 -0
  323. data/lib/expressir/model/data_types/binary.rb +19 -0
  324. data/lib/expressir/model/data_types/boolean.rb +15 -0
  325. data/lib/expressir/model/data_types/enumeration.rb +21 -0
  326. data/lib/expressir/model/data_types/enumeration_item.rb +24 -0
  327. data/lib/expressir/model/data_types/generic.rb +24 -0
  328. data/lib/expressir/model/data_types/generic_entity.rb +24 -0
  329. data/lib/expressir/model/data_types/integer.rb +15 -0
  330. data/lib/expressir/model/data_types/list.rb +23 -0
  331. data/lib/expressir/model/data_types/logical.rb +15 -0
  332. data/lib/expressir/model/data_types/number.rb +15 -0
  333. data/lib/expressir/model/data_types/real.rb +17 -0
  334. data/lib/expressir/model/data_types/select.rb +23 -0
  335. data/lib/expressir/model/data_types/set.rb +21 -0
  336. data/lib/expressir/model/data_types/string.rb +19 -0
  337. data/lib/expressir/model/data_types.rb +25 -0
  338. data/lib/expressir/model/declarations/attribute.rb +38 -0
  339. data/lib/expressir/model/declarations/constant.rb +28 -0
  340. data/lib/expressir/model/declarations/derived_attribute.rb +25 -0
  341. data/lib/expressir/model/declarations/entity.rb +62 -0
  342. data/lib/expressir/model/declarations/function.rb +59 -0
  343. data/lib/expressir/model/declarations/informal_proposition_rule.rb +25 -0
  344. data/lib/expressir/model/declarations/interface.rb +37 -0
  345. data/lib/expressir/model/declarations/interface_item.rb +21 -0
  346. data/lib/expressir/model/declarations/interfaced_item.rb +33 -0
  347. data/lib/expressir/model/declarations/inverse_attribute.rb +25 -0
  348. data/lib/expressir/model/declarations/parameter.rb +28 -0
  349. data/lib/expressir/model/declarations/procedure.rb +57 -0
  350. data/lib/expressir/model/declarations/remark_item.rb +21 -0
  351. data/lib/expressir/model/declarations/rule.rb +66 -0
  352. data/lib/expressir/model/declarations/schema.rb +215 -0
  353. data/lib/expressir/model/declarations/schema_version.rb +19 -0
  354. data/lib/expressir/model/declarations/schema_version_item.rb +19 -0
  355. data/lib/expressir/model/declarations/subtype_constraint.rb +32 -0
  356. data/lib/expressir/model/declarations/type.rb +45 -0
  357. data/lib/expressir/model/declarations/unique_rule.rb +26 -0
  358. data/lib/expressir/model/declarations/variable.rb +28 -0
  359. data/lib/expressir/model/declarations/where_rule.rb +26 -0
  360. data/lib/expressir/model/declarations.rb +31 -0
  361. data/lib/expressir/model/dependency_resolver.rb +268 -0
  362. data/lib/expressir/model/exp_file.rb +42 -0
  363. data/lib/expressir/model/expressions/aggregate_initializer.rb +18 -0
  364. data/lib/expressir/model/expressions/aggregate_initializer_item.rb +20 -0
  365. data/lib/expressir/model/expressions/binary_expression.rb +59 -0
  366. data/lib/expressir/model/expressions/entity_constructor.rb +20 -0
  367. data/lib/expressir/model/expressions/function_call.rb +20 -0
  368. data/lib/expressir/model/expressions/interval.rb +29 -0
  369. data/lib/expressir/model/expressions/query_expression.rb +31 -0
  370. data/lib/expressir/model/expressions/unary_expression.rb +25 -0
  371. data/lib/expressir/model/expressions.rb +18 -0
  372. data/lib/expressir/model/identifier.rb +26 -0
  373. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  374. data/lib/expressir/model/indexes/item_graph.rb +185 -0
  375. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  376. data/lib/expressir/model/indexes/type_index.rb +149 -0
  377. data/lib/expressir/model/indexes.rb +12 -0
  378. data/lib/expressir/model/interface_validator.rb +384 -0
  379. data/lib/expressir/model/literals/binary.rb +17 -0
  380. data/lib/expressir/model/literals/integer.rb +17 -0
  381. data/lib/expressir/model/literals/logical.rb +21 -0
  382. data/lib/expressir/model/literals/real.rb +17 -0
  383. data/lib/expressir/model/literals/string.rb +20 -0
  384. data/lib/expressir/model/literals.rb +13 -0
  385. data/lib/expressir/model/model_element.rb +377 -0
  386. data/lib/expressir/model/references/attribute_reference.rb +19 -0
  387. data/lib/expressir/model/references/group_reference.rb +19 -0
  388. data/lib/expressir/model/references/index_reference.rb +23 -0
  389. data/lib/expressir/model/references/simple_reference.rb +21 -0
  390. data/lib/expressir/model/references.rb +12 -0
  391. data/lib/expressir/model/remark_format.rb +17 -0
  392. data/lib/expressir/model/remark_info.rb +92 -0
  393. data/lib/expressir/model/remark_placement.rb +42 -0
  394. data/lib/expressir/model/repository.rb +483 -0
  395. data/lib/expressir/model/repository_validator.rb +293 -0
  396. data/lib/expressir/model/search_engine.rb +551 -0
  397. data/lib/expressir/model/statements/alias.rb +31 -0
  398. data/lib/expressir/model/statements/assignment.rb +23 -0
  399. data/lib/expressir/model/statements/case.rb +42 -0
  400. data/lib/expressir/model/statements/case_action.rb +20 -0
  401. data/lib/expressir/model/statements/compound.rb +21 -0
  402. data/lib/expressir/model/statements/escape.rb +18 -0
  403. data/lib/expressir/model/statements/if.rb +26 -0
  404. data/lib/expressir/model/statements/null.rb +18 -0
  405. data/lib/expressir/model/statements/procedure_call.rb +22 -0
  406. data/lib/expressir/model/statements/repeat.rb +40 -0
  407. data/lib/expressir/model/statements/return.rb +20 -0
  408. data/lib/expressir/model/statements/skip.rb +18 -0
  409. data/lib/expressir/model/statements.rb +20 -0
  410. data/lib/expressir/model/supertype_expressions/binary_supertype_expression.rb +25 -0
  411. data/lib/expressir/model/supertype_expressions/oneof_supertype_expression.rb +17 -0
  412. data/lib/expressir/model/supertype_expressions.rb +12 -0
  413. data/lib/expressir/model.rb +39 -0
  414. data/lib/expressir/package/builder.rb +231 -0
  415. data/lib/expressir/package/metadata.rb +79 -0
  416. data/lib/expressir/package/reader.rb +149 -0
  417. data/lib/expressir/package.rb +8 -0
  418. data/lib/expressir/schema_manifest.rb +159 -0
  419. data/lib/expressir/schema_manifest_entry.rb +15 -0
  420. data/lib/expressir/version.rb +8 -0
  421. data/lib/expressir.rb +94 -0
  422. data/lib/tasks/verify_remarks.rake +16 -0
  423. data/rakelib/native.rake +24 -0
  424. data/rakelib/oracle.rake +52 -0
  425. data/rakelib/verify_remarks.rake +16 -0
  426. metadata +692 -0
@@ -0,0 +1,1222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "json"
5
+ require "yaml"
6
+ require "pathname"
7
+
8
+ module Expressir
9
+ module Commands
10
+ # Package management CLI commands
11
+ # Handles LER package creation, inspection, validation, and extraction
12
+ class Package < Thor
13
+ include Thor::Actions
14
+
15
+ # Allow options to be set for testing
16
+ attr_accessor :options
17
+
18
+ def initialize(options_or_args = [], *args, **kwargs)
19
+ # Check if first argument is options hash (for testing)
20
+ if options_or_args.is_a?(Hash) && args.empty? && kwargs.empty?
21
+ # Direct options passed for testing - don't initialize Thor
22
+ @options = options_or_args
23
+ else
24
+ # Normal Thor initialization
25
+ super
26
+ @options ||= {}
27
+ end
28
+ end
29
+
30
+ desc "build ROOT_SCHEMA OUTPUT",
31
+ "Build LER package from schema with dependency resolution"
32
+ long_desc <<~DESC
33
+ Build an LER package from a root schema file or manifest.
34
+
35
+ Two modes:
36
+ 1. Auto-resolution: Parses root schema and resolves dependencies
37
+ 2. Manifest-based: Uses pre-generated manifest file
38
+
39
+ Manifest Verification (manifest-based mode only):
40
+ By default, manifests are verified for referential integrity before building.
41
+ This ensures all USE FROM and REFERENCE FROM declarations can be resolved.
42
+
43
+ Use --skip-verify to bypass verification (may result in incomplete packages).
44
+ Warning: Skipping verification may produce packages with missing dependencies
45
+ that are not fully internally consistent.
46
+
47
+ Options match the Ruby API Repository#export_to_package method:
48
+ - express_mode: How to bundle EXPRESS files
49
+ - resolution_mode: Whether to pre-serialize schemas
50
+ - serialization_format: Format for serialized data
51
+ - validate: Run validation before packaging
52
+ - base_dirs: Base directories for schema resolution (auto-resolution only)
53
+ - manifest: Use pre-generated manifest file (skips dependency resolution)
54
+ - skip_verify: Skip manifest verification (manifest-based only)
55
+
56
+ Example (auto-resolution):
57
+ expressir package build schemas/activity/mim.exp activity.ler \\
58
+ --name "Activity Module" \\
59
+ --version "1.0" \\
60
+ --validate
61
+
62
+ Example (manifest-based with verification):
63
+ expressir package build --manifest activity_manifest.yaml activity.ler \\
64
+ --validate
65
+
66
+ Example (manifest-based, skip verification):
67
+ expressir package build --manifest activity_manifest.yaml activity.ler \\
68
+ --skip-verify
69
+ DESC
70
+ option :name, type: :string, desc: "Package name for metadata"
71
+ option :version, type: :string, desc: "Package version for metadata",
72
+ default: "1.0.0"
73
+ option :description, type: :string,
74
+ desc: "Package description for metadata"
75
+ option :express_mode, type: :string, default: "include_all",
76
+ enum: ["include_all", "allow_external"],
77
+ desc: "EXPRESS bundling mode"
78
+ option :resolution_mode, type: :string, default: "resolved",
79
+ enum: ["resolved", "bare"],
80
+ desc: "Schema resolution mode"
81
+ option :serialization_format, type: :string, default: "marshal",
82
+ enum: ["marshal", "json", "yaml"],
83
+ desc: "Serialization format for resolved mode"
84
+ option :validate, type: :boolean, default: true,
85
+ desc: "Validate repository before packaging"
86
+ option :base_dirs, type: :string,
87
+ desc: "Comma-separated list of base directories for schema resolution (auto-resolution only)"
88
+ option :manifest, type: :string,
89
+ desc: "Use pre-generated manifest file (skips dependency resolution)"
90
+ option :skip_verify, type: :boolean, default: false,
91
+ desc: "Skip manifest verification (may result in incomplete packages)"
92
+ option :verbose, type: :boolean, default: false,
93
+ desc: "Enable verbose output"
94
+ option :max_processes, type: :numeric, default: 1,
95
+ desc: "Parallel parse workers for schema loading (1 = sequential)"
96
+ def build(root_schema = nil, output = nil)
97
+ schema_files, output = resolve_schema_files(root_schema, output)
98
+ repo = Expressir::Model::Repository.from_files(schema_files,
99
+ max_processes: options[:max_processes])
100
+ validate_repository(repo) if should_validate?
101
+ create_package(repo, output)
102
+ rescue Expressir::Error
103
+ raise
104
+ rescue StandardError => e
105
+ raise Expressir::PackageBuildError.new(
106
+ "Error building package: #{e.message}",
107
+ command_name: "package build",
108
+ )
109
+ end
110
+
111
+ private
112
+
113
+ def resolve_schema_files(root_schema, output)
114
+ if options[:manifest]
115
+ [resolve_from_manifest(output), output || root_schema]
116
+ else
117
+ [resolve_from_auto_resolution(root_schema), output]
118
+ end
119
+ end
120
+
121
+ def resolve_from_manifest(output)
122
+ raise Expressir::ManifestNotFoundError.new(options[:manifest]) unless File.exist?(options[:manifest])
123
+
124
+ unless output || root_schema_arg
125
+ raise Expressir::MissingRequiredArgumentError.new(
126
+ "OUTPUT path is required",
127
+ usage_hint: "expressir package build --manifest MANIFEST.yaml OUTPUT.ler",
128
+ )
129
+ end
130
+
131
+ say "Building LER package from manifest #{options[:manifest]}..." if options[:verbose]
132
+
133
+ manifest = Expressir::SchemaManifest.from_file(options[:manifest])
134
+ manifest_data = YAML.load_file(options[:manifest])
135
+ manifest_dir = File.dirname(File.expand_path(options[:manifest]))
136
+
137
+ verify_manifest(manifest) unless options[:skip_verify]
138
+ collect_manifest_paths(manifest_data, manifest_dir)
139
+ end
140
+
141
+ def verify_manifest(manifest)
142
+ say "Verifying manifest integrity..."
143
+ validator = Expressir::Manifest::Validator.new(manifest, options.merge(verbose: true))
144
+
145
+ file_errors = validator.validate_file_existence
146
+ unless file_errors.empty?
147
+ raise Expressir::ManifestValidationError.new(
148
+ "Manifest validation failed", errors: file_errors.map { |e| e[:message] }
149
+ )
150
+ end
151
+
152
+ reference_errors = validator.validate_referential_integrity
153
+ unless reference_errors.empty?
154
+ raise Expressir::ReferentialIntegrityError.new(
155
+ reference_errors, message: "Manifest has unresolved dependencies"
156
+ )
157
+ end
158
+
159
+ say "✓ Manifest verified", :green
160
+ end
161
+
162
+ def collect_manifest_paths(manifest_data, manifest_dir)
163
+ errors = []
164
+ warnings = []
165
+ paths = []
166
+
167
+ manifest_data["schemas"].each do |schema_id, schema_data|
168
+ schema_path = schema_data["path"]
169
+ if schema_path.nil? || schema_path.empty?
170
+ warnings << "Schema '#{schema_id}' has no path specified"
171
+ next
172
+ end
173
+
174
+ full_path = Pathname.new(schema_path).absolute? ? schema_path : File.expand_path(schema_path, manifest_dir)
175
+ if File.exist?(full_path)
176
+ paths << full_path
177
+ else
178
+ errors << "Schema file not found: #{full_path} (#{schema_id})"
179
+ end
180
+ end
181
+
182
+ raise Expressir::ManifestValidationError.new("Manifest validation failed", errors: errors) unless errors.empty?
183
+
184
+ say "Warnings:" if options[:verbose] && warnings.any?
185
+ warnings.each { |w| say " - #{w}", :yellow } if options[:verbose] && warnings.any?
186
+ say " Using #{paths.size} schema(s) from manifest" if options[:verbose]
187
+ paths
188
+ end
189
+
190
+ def resolve_from_auto_resolution(root_schema)
191
+ unless root_schema
192
+ raise Expressir::MissingRequiredArgumentError.new(
193
+ "ROOT_SCHEMA is required when not using --manifest",
194
+ usage_hint: "expressir package build ROOT_SCHEMA OUTPUT.ler",
195
+ )
196
+ end
197
+
198
+ say "Building LER package from #{root_schema}..." if options[:verbose]
199
+ say "Resolving dependencies..." if options[:verbose]
200
+
201
+ base_dirs = options[:base_dirs] ? options[:base_dirs].split(",").map(&:strip) : [File.dirname(File.expand_path(root_schema))]
202
+ if options[:verbose] && base_dirs.size == 1
203
+ say " Using base directory: #{base_dirs.first}"
204
+ say " Tip: Use --base-dirs to specify additional search paths for dependencies"
205
+ end
206
+
207
+ resolver = Expressir::Model::DependencyResolver.new(base_dirs: base_dirs, verbose: options[:verbose])
208
+ resolved = resolver.resolve_dependencies(root_schema)
209
+ say " Found #{resolved.size} schema(s)" if options[:verbose]
210
+ resolved
211
+ end
212
+
213
+ def should_validate?
214
+ return false if options[:manifest] && options[:skip_verify]
215
+
216
+ options[:validate]
217
+ end
218
+
219
+ def validate_repository(repo)
220
+ say "Validating repository..." if options[:verbose]
221
+ validation = repo.validate
222
+ return if validation[:valid?]
223
+
224
+ formatted_errors = (validation[:errors] || []).map do |e|
225
+ next e.to_s unless e.is_a?(Hash)
226
+
227
+ msg = e[:message] || "Unknown error"
228
+ type = e[:type] ? "[#{e[:type]}] " : ""
229
+ schema = e[:schema] ? " (schema: #{e[:schema]})" : ""
230
+ ref = e[:referenced_schema] ? " (referenced: #{e[:referenced_schema]})" : ""
231
+ iface = e[:interface_type] ? " (interface: #{e[:interface_type]})" : ""
232
+ "#{type}#{msg}#{schema}#{ref}#{iface}"
233
+ end
234
+ raise Expressir::SchemaValidationError.new("Repository validation failed", errors: formatted_errors)
235
+ end
236
+
237
+ def create_package(repo, output)
238
+ say "Creating package..." if options[:verbose]
239
+ repo.export_to_package(output, build_package_options)
240
+ say "✓ Package created: #{output}", :green
241
+ say " Schemas: #{repo.schemas.size}", :green if options[:verbose]
242
+ end
243
+
244
+ public
245
+
246
+ desc "info PACKAGE", "Display package metadata and statistics"
247
+ long_desc <<~DESC
248
+ Display comprehensive information about an LER package including metadata,
249
+ statistics, and configuration.
250
+
251
+ Output formats:
252
+ - text: Human-readable formatted output (default)
253
+ - json: Machine-readable JSON
254
+ - yaml: YAML format
255
+
256
+ Example:
257
+ expressir package info activity.ler
258
+ expressir package info activity.ler --format json
259
+ DESC
260
+ option :format, type: :string, default: "text",
261
+ enum: ["text", "json", "yaml"],
262
+ desc: "Output format"
263
+ def info(package_path)
264
+ repo = load_package(package_path)
265
+ metadata = load_package_metadata(package_path)
266
+
267
+ case options[:format]
268
+ when "json"
269
+ output_json_info(metadata, repo)
270
+ when "yaml"
271
+ output_yaml_info(metadata, repo)
272
+ else
273
+ output_text_info(metadata, repo)
274
+ end
275
+ rescue Expressir::Error
276
+ raise # Re-raise Expressir errors
277
+ rescue StandardError => e
278
+ raise Expressir::PackageReadError.new(
279
+ "Error reading package info: #{e.message}",
280
+ command_name: "package info",
281
+ )
282
+ end
283
+
284
+ desc "validate PACKAGE", "Validate package structure and integrity"
285
+ long_desc <<~DESC
286
+ Validate an LER package structure, metadata, and repository consistency.
287
+
288
+ Checks performed:
289
+ - Package file integrity
290
+ - Metadata validation
291
+ - Schema completeness
292
+ - Reference resolution
293
+ - Optional interface validation
294
+ - Optional strict mode for enhanced validation
295
+
296
+ Validation options:
297
+ - --strict: Enable strict validation (unused schemas become warnings)
298
+ - --check-interfaces: Perform detailed interface validation
299
+ - --check-completeness: Check for schema completeness
300
+ - --check-duplicates: Check for duplicate interface aliases
301
+ - --check-self-references: Check for self-referencing interfaces
302
+ - --detailed: Show detailed validation reports with fix suggestions
303
+
304
+ Example:
305
+ expressir package validate activity.ler
306
+ expressir package validate activity.ler --strict
307
+ expressir package validate activity.ler --check-interfaces --detailed
308
+ expressir package validate activity.ler --strict --check-completeness --check-interfaces
309
+ DESC
310
+ option :strict, type: :boolean, default: false,
311
+ desc: "Enable strict validation (unused schemas)"
312
+ option :check_interfaces, type: :boolean, default: true,
313
+ desc: "Perform detailed interface validation"
314
+ option :check_completeness, type: :boolean, default: false,
315
+ desc: "Check for schema completeness"
316
+ option :check_duplicates, type: :boolean, default: false,
317
+ desc: "Check for duplicate interface aliases"
318
+ option :check_self_references, type: :boolean, default: false,
319
+ desc: "Check for self-referencing interfaces"
320
+ option :detailed, type: :boolean, default: false,
321
+ desc: "Show detailed validation reports with fix suggestions"
322
+ option :format, type: :string, default: "text",
323
+ enum: ["text", "json", "yaml"],
324
+ desc: "Output format"
325
+ def validate(package_path)
326
+ repo = load_package(package_path)
327
+ validation = repo.validate(
328
+ strict: options[:strict],
329
+ )
330
+
331
+ case options[:format]
332
+ when "json"
333
+ output_json_validation(validation)
334
+ when "yaml"
335
+ output_yaml_validation(validation)
336
+ else
337
+ output_text_validation(validation)
338
+ end
339
+
340
+ unless validation[:valid?]
341
+ raise Expressir::PackageValidationError.new(
342
+ "Package validation failed",
343
+ errors: validation[:errors] || [],
344
+ )
345
+ end
346
+ rescue Expressir::Error
347
+ raise # Re-raise Expressir errors
348
+ rescue StandardError => e
349
+ raise Expressir::PackageValidationError.new(
350
+ "Error validating package: #{e.message}",
351
+ command_name: "package validate",
352
+ )
353
+ end
354
+
355
+ desc "extract PACKAGE", "Extract package contents to directory"
356
+ long_desc <<~DESC
357
+ Extract all contents of an LER package to a specified directory.
358
+
359
+ Extracts:
360
+ - Metadata file
361
+ - EXPRESS schema files
362
+ - Serialized repository (if present)
363
+ - Index files
364
+ - Manifest
365
+
366
+ Example:
367
+ expressir package extract activity.ler --output extracted/
368
+ DESC
369
+ option :output, aliases: "-o", type: :string, required: true,
370
+ desc: "Output directory for extraction"
371
+ def extract(package_path)
372
+ require "zip"
373
+ require "fileutils"
374
+
375
+ unless options[:output]
376
+ raise Expressir::MissingRequiredArgumentError.new(
377
+ "output directory is required",
378
+ usage_hint: "expressir package extract PACKAGE --output OUTPUT_DIR",
379
+ )
380
+ end
381
+
382
+ output_dir = options[:output]
383
+ FileUtils.mkdir_p(output_dir)
384
+
385
+ Zip::File.open(package_path) do |zip|
386
+ zip.each do |entry|
387
+ dest_path = File.join(output_dir, entry.name)
388
+ FileUtils.mkdir_p(File.dirname(dest_path))
389
+ entry.extract(entry.name, destination_directory: output_dir) unless File.exist?(dest_path)
390
+ end
391
+ end
392
+
393
+ say "✓ Package extracted to: #{output_dir}", :green
394
+ say " Files extracted: #{Dir.glob(File.join(output_dir, '**', '*')).select do |f|
395
+ File.file?(f)
396
+ end.size}", :green
397
+ rescue Expressir::Error
398
+ raise # Re-raise Expressir errors
399
+ rescue StandardError => e
400
+ raise Expressir::PackageExtractError.new(
401
+ "Error extracting package: #{e.message}",
402
+ command_name: "package extract",
403
+ )
404
+ end
405
+
406
+ desc "list PACKAGE", "List all elements of a specific type"
407
+ long_desc <<~DESC
408
+ List all elements of a specific type in the package.
409
+
410
+ Element types:
411
+ - schema, entity, type, attribute, derived_attribute, inverse_attribute
412
+ - function, procedure, rule, constant, parameter, variable
413
+ - where_rule, unique_rule, enumeration_item, interface
414
+
415
+ Type categories (for --type type):
416
+ - select, enumeration, aggregate, defined
417
+
418
+ Example:
419
+ expressir package list activity.ler
420
+ expressir package list activity.ler --type entity
421
+ expressir package list activity.ler --type type --category select
422
+ expressir package list activity.ler --type entity --schema action_schema
423
+ DESC
424
+ option :type, type: :string, default: "entity",
425
+ desc: "Element type to list"
426
+ option :category, type: :string,
427
+ desc: "Type category (for --type type)"
428
+ option :schema, type: :string,
429
+ desc: "Filter by schema name"
430
+ option :format, type: :string, default: "text",
431
+ enum: ["text", "json", "yaml"],
432
+ desc: "Output format"
433
+ option :show_path, type: :boolean, default: false,
434
+ desc: "Show full EXPRESS paths"
435
+ option :show_details, type: :boolean, default: false,
436
+ desc: "Show element details"
437
+ option :count_only, type: :boolean, default: false,
438
+ desc: "Show count only"
439
+ def list(package_path)
440
+ repo = load_package(package_path)
441
+ search_engine = Expressir::Model::SearchEngine.new(repo)
442
+
443
+ results = search_engine.list(
444
+ type: options[:type],
445
+ schema: options[:schema],
446
+ category: options[:category],
447
+ )
448
+
449
+ if options[:count_only]
450
+ say results.size.to_s
451
+ return
452
+ end
453
+
454
+ case options[:format]
455
+ when "json"
456
+ say results.to_json
457
+ when "yaml"
458
+ say results.to_yaml
459
+ else
460
+ output_text_list(results, options[:type], options[:schema],
461
+ options[:category])
462
+ end
463
+ rescue Expressir::Error
464
+ raise # Re-raise Expressir errors
465
+ rescue StandardError => e
466
+ raise Expressir::PackageListError.new(
467
+ "Error listing elements: #{e.message}",
468
+ command_name: "package list",
469
+ )
470
+ end
471
+
472
+ desc "search PACKAGE PATTERN", "Search for elements matching a pattern"
473
+ long_desc <<~DESC
474
+ Search for elements matching a pattern.
475
+
476
+ Pattern formats:
477
+ - name Simple name (searches all schemas)
478
+ - schema.element Qualified name
479
+ - schema.entity.attr Deep path
480
+ - *.element Wildcard schema
481
+ - schema.* Wildcard element
482
+ - *.*.name Multi-level wildcards
483
+ - action* Prefix matching
484
+
485
+ Example:
486
+ expressir package search activity.ler "action"
487
+ expressir package search activity.ler "action" --type entity
488
+ expressir package search activity.ler "*.*.id" --type attribute
489
+ expressir package search activity.ler "action*" --type entity
490
+ expressir package search activity.ler "action_schema.action.*" --type attribute
491
+ DESC
492
+ option :type, type: :string,
493
+ desc: "Filter by element type"
494
+ option :category, type: :string,
495
+ desc: "Type category (for --type type)"
496
+ option :schema, type: :string,
497
+ desc: "Limit to specific schema"
498
+ option :case_sensitive, type: :boolean, default: false,
499
+ desc: "Enable case-sensitive matching"
500
+ option :regex, type: :boolean, default: false,
501
+ desc: "Treat pattern as regex"
502
+ option :exact, type: :boolean, default: false,
503
+ desc: "Exact match only"
504
+ option :format, type: :string, default: "text",
505
+ enum: ["text", "json", "yaml"],
506
+ desc: "Output format"
507
+ option :show_path, type: :boolean, default: false,
508
+ desc: "Show full EXPRESS paths"
509
+ option :show_details, type: :boolean, default: false,
510
+ desc: "Show element details"
511
+ option :show_location, type: :boolean, default: false,
512
+ desc: "Show source location"
513
+ option :limit, type: :numeric,
514
+ desc: "Limit results"
515
+ option :count_only, type: :boolean, default: false,
516
+ desc: "Show count only"
517
+ def search(package_path, pattern)
518
+ repo = load_package(package_path)
519
+ search_engine = Expressir::Model::SearchEngine.new(repo)
520
+
521
+ results = search_engine.search(
522
+ pattern: pattern,
523
+ type: options[:type],
524
+ schema: options[:schema],
525
+ category: options[:category],
526
+ case_sensitive: options[:case_sensitive],
527
+ regex: options[:regex],
528
+ exact: options[:exact],
529
+ )
530
+
531
+ # Apply limit if specified
532
+ results = results.take(options[:limit]) if options[:limit]
533
+
534
+ if options[:count_only]
535
+ say results.size.to_s
536
+ return
537
+ end
538
+
539
+ case options[:format]
540
+ when "json"
541
+ say results.to_json
542
+ when "yaml"
543
+ say results.to_yaml
544
+ else
545
+ output_text_search_results(results, pattern)
546
+ end
547
+ rescue Expressir::Error
548
+ raise # Re-raise Expressir errors
549
+ rescue StandardError => e
550
+ raise Expressir::PackageSearchError.new(
551
+ "Error searching: #{e.message}",
552
+ command_name: "package search",
553
+ )
554
+ end
555
+ desc "tree PACKAGE", "Display hierarchical tree view of package contents"
556
+ long_desc <<~DESC
557
+ Display a hierarchical tree view of all package contents showing schemas,
558
+ entities, types, functions, and their nested elements.
559
+
560
+ Tree structure uses:
561
+ - ├─ for intermediate items
562
+ - └─ for last items
563
+ - │ for continuation lines
564
+
565
+ Colors (can be disabled with --no-color):
566
+ - Schemas: bold blue
567
+ - Entities: green
568
+ - Types: yellow
569
+ - Attributes: cyan
570
+ - Functions: magenta
571
+ - Procedures: magenta
572
+ - Rules: red
573
+
574
+ Example:
575
+ expressir package tree activity.ler
576
+ expressir package tree activity.ler --depth 2
577
+ expressir package tree activity.ler --schema action_schema
578
+ expressir package tree activity.ler --type entity
579
+ expressir package tree activity.ler --counts
580
+ expressir package tree activity.ler --no-color
581
+ DESC
582
+ option :depth, type: :numeric,
583
+ desc: "Limit tree depth (1=schemas, 2=entities/types, 3=attributes)"
584
+ option :type, type: :string,
585
+ desc: "Filter to show only specific element type (entity, type, function, etc.)"
586
+ option :schema, type: :string,
587
+ desc: "Show tree for specific schema only"
588
+ option :no_color, type: :boolean, default: false,
589
+ desc: "Disable colors"
590
+ option :counts, type: :boolean, default: false,
591
+ desc: "Show element counts at each level"
592
+ def tree(package_path)
593
+ require "paint"
594
+
595
+ repo = load_package(package_path)
596
+
597
+ # Filter schemas if requested
598
+ schemas = if options[:schema]
599
+ (repo.schemas || []).select { |s| s.id == options[:schema] }
600
+ else
601
+ repo.schemas || []
602
+ end
603
+
604
+ if schemas.empty?
605
+ say "No schemas found", :yellow
606
+ return
607
+ end
608
+
609
+ # Display package name
610
+ package_name = File.basename(package_path)
611
+ say colorize(package_name, :bold)
612
+
613
+ # Display tree
614
+ schemas.each_with_index do |schema, idx|
615
+ is_last_schema = idx == schemas.size - 1
616
+ display_schema_tree(schema, is_last_schema, "", 1)
617
+ end
618
+ rescue Expressir::Error
619
+ raise # Re-raise Expressir errors
620
+ rescue StandardError => e
621
+ raise Expressir::PackageTreeError.new(
622
+ "Error displaying tree: #{e.message}",
623
+ command_name: "package tree",
624
+ )
625
+ end
626
+
627
+ private
628
+
629
+ # Build package options from CLI options
630
+ # @return [Hash] Package options
631
+ def build_package_options
632
+ {
633
+ name: options[:name] || "Unnamed Package",
634
+ version: options[:version],
635
+ description: options[:description] || "",
636
+ express_mode: options[:express_mode],
637
+ resolution_mode: options[:resolution_mode],
638
+ serialization_format: options[:serialization_format],
639
+ }
640
+ end
641
+
642
+ # Load package and return repository
643
+ # @param package_path [String] Path to .ler file
644
+ # @return [Model::Repository] Loaded repository
645
+ def load_package(package_path)
646
+ unless File.exist?(package_path)
647
+ raise Expressir::PackageNotFoundError.new(package_path)
648
+ end
649
+
650
+ Expressir::Model::Repository.from_package(package_path)
651
+ end
652
+
653
+ # Load package metadata without full repository
654
+ # @param package_path [String] Path to .ler file
655
+ # @return [Package::Metadata] Package metadata
656
+ def load_package_metadata(package_path)
657
+ require "zip"
658
+
659
+ Zip::File.open(package_path) do |zip|
660
+ entry = zip.find_entry("metadata.yaml")
661
+ raise "Metadata not found in package" unless entry
662
+
663
+ Expressir::Package::Metadata.from_yaml(entry.get_input_stream.read)
664
+ end
665
+ end
666
+
667
+ # Output package info in text format
668
+ # @param metadata [Package::Metadata] Package metadata
669
+ # @param repo [Model::Repository] Repository instance
670
+ # @return [void]
671
+ def output_text_info(metadata, repo)
672
+ stats = repo.statistics
673
+
674
+ say "Package Information", :bold
675
+ say "=" * 50
676
+ say "Name: #{metadata.name}" if metadata.name && !metadata.name.empty?
677
+ say "Version: #{metadata.version}" if metadata.version && !metadata.version.empty?
678
+ say "Description: #{metadata.description}" if metadata.description && !metadata.description.to_s.empty?
679
+ say "Created: #{metadata.created_at}" if metadata.created_at && !metadata.created_at.to_s.empty?
680
+ say "Created by: #{metadata.created_by}" if metadata.created_by && !metadata.created_by.to_s.empty? && metadata.created_by != "expressir"
681
+ say ""
682
+ say "Configuration", :bold
683
+ say "-" * 50
684
+ say "Express mode: #{metadata.express_mode}" if metadata.express_mode && !metadata.express_mode.to_s.empty?
685
+ say "Resolution mode: #{metadata.resolution_mode}" if metadata.resolution_mode && !metadata.resolution_mode.to_s.empty?
686
+ say "Serialization format: #{metadata.serialization_format}" if metadata.serialization_format && !metadata.serialization_format.to_s.empty?
687
+ say ""
688
+ say "Statistics", :bold
689
+ say "-" * 50
690
+ say "Total schemas: #{stats[:total_schemas]}"
691
+ say "Total entities: #{stats[:total_entities]}"
692
+ say "Total types: #{stats[:total_types]}"
693
+ say "Total functions: #{stats[:total_functions]}"
694
+ say "Total rules: #{stats[:total_rules]}"
695
+ say "Total procedures: #{stats[:total_procedures]}"
696
+ end
697
+
698
+ # Output package info in JSON format
699
+ # @param metadata [Package::Metadata] Package metadata
700
+ # @param repo [Model::Repository] Repository instance
701
+ # @return [void]
702
+ def output_json_info(metadata, repo)
703
+ stats = repo.statistics
704
+ # Convert symbol keys to strings recursively, handle nil stats
705
+ stats_hash = stats.is_a?(Hash) ? stringify_keys(stats) : {}
706
+
707
+ info = {
708
+ "metadata" => stringify_keys(metadata.to_h),
709
+ "statistics" => stats_hash,
710
+ }
711
+ say info.to_json
712
+ end
713
+
714
+ # Output package info in YAML format
715
+ # @param metadata [Package::Metadata] Package metadata
716
+ # @param repo [Model::Repository] Repository instance
717
+ # @return [void]
718
+ def output_yaml_info(metadata, repo)
719
+ stats = repo.statistics
720
+ # Convert symbol keys to strings recursively, handle nil stats
721
+ stats_hash = stats.is_a?(Hash) ? stringify_keys(stats) : {}
722
+
723
+ info = {
724
+ "metadata" => stringify_keys(metadata.to_h),
725
+ "statistics" => stats_hash,
726
+ }
727
+ say info.to_yaml
728
+ end
729
+
730
+ # Recursively stringify hash keys
731
+ # @param obj [Object] Object to stringify
732
+ # @return [Object] Object with stringified keys
733
+ def stringify_keys(obj)
734
+ case obj
735
+ when Hash
736
+ obj.transform_keys(&:to_s).transform_values { |v| stringify_keys(v) }
737
+ when Array
738
+ obj.map { |v| stringify_keys(v) }
739
+ else
740
+ obj
741
+ end
742
+ end
743
+
744
+ # Display schema in tree format
745
+ # @param schema [Declarations::Schema] Schema to display
746
+ # @param is_last [Boolean] Whether this is the last item
747
+ # @param prefix [String] Current indentation prefix
748
+ # @param current_depth [Integer] Current tree depth
749
+ # @return [void]
750
+ def display_schema_tree(schema, is_last, prefix, current_depth)
751
+ return if options[:depth] && current_depth > options[:depth]
752
+ return if options[:type] && options[:type] != "schema"
753
+
754
+ # Build schema line
755
+ connector = is_last ? "└─ " : "├─ "
756
+ counts_text = build_counts_text(schema) if options[:counts]
757
+ schema_text = "#{schema.id} (schema)#{counts_text}"
758
+
759
+ say "#{prefix}#{connector}#{colorize(schema_text, :blue, :bold)}"
760
+
761
+ # Don't show children if depth limit reached
762
+ return if options[:depth] && current_depth >= options[:depth]
763
+
764
+ # Build new prefix for children
765
+ child_prefix = prefix + (is_last ? " " : "│ ")
766
+
767
+ # Collect all displayable children
768
+ children = collect_schema_children(schema)
769
+
770
+ # Display children
771
+ children.each_with_index do |child, idx|
772
+ is_last_child = idx == children.size - 1
773
+ display_element_tree(child[:element], child[:type], is_last_child,
774
+ child_prefix, current_depth + 1)
775
+ end
776
+ end
777
+
778
+ # Collect schema children based on filters
779
+ # @param schema [Declarations::Schema] Schema to collect from
780
+ # @return [Array<Hash>] Array of child elements with type
781
+ def collect_schema_children(schema)
782
+ children = []
783
+
784
+ # Add entities
785
+ if should_include_type?("entity") && schema.entities
786
+ schema.entities.each do |e|
787
+ children << { element: e, type: "entity" }
788
+ end
789
+ end
790
+
791
+ # Add types
792
+ if should_include_type?("type") && schema.types
793
+ schema.types.each { |t| children << { element: t, type: "type" } }
794
+ end
795
+
796
+ # Add functions
797
+ if should_include_type?("function") && schema.functions
798
+ schema.functions.each do |f|
799
+ children << { element: f, type: "function" }
800
+ end
801
+ end
802
+
803
+ # Add procedures
804
+ if should_include_type?("procedure") && schema.procedures
805
+ schema.procedures.each do |p|
806
+ children << { element: p, type: "procedure" }
807
+ end
808
+ end
809
+
810
+ # Add rules
811
+ if should_include_type?("rule") && schema.rules
812
+ schema.rules.each { |r| children << { element: r, type: "rule" } }
813
+ end
814
+
815
+ children
816
+ end
817
+
818
+ # Display element in tree format
819
+ # @param element [ModelElement] Element to display
820
+ # @param type [String] Element type
821
+ # @param is_last [Boolean] Whether this is the last item
822
+ # @param prefix [String] Current indentation prefix
823
+ # @param current_depth [Integer] Current tree depth
824
+ # @return [void]
825
+ def display_element_tree(element, type, is_last, prefix, current_depth)
826
+ return if options[:depth] && current_depth > options[:depth]
827
+
828
+ connector = is_last ? "└─ " : "├─ "
829
+ element_text = format_element_text(element, type)
830
+ color = element_color(type)
831
+
832
+ say "#{prefix}#{connector}#{colorize(element_text, color)}"
833
+
834
+ # Don't show children if depth limit reached
835
+ return if options[:depth] && current_depth >= options[:depth]
836
+
837
+ # Build new prefix for children
838
+ child_prefix = prefix + (is_last ? " " : "│ ")
839
+
840
+ # Display children based on element type
841
+ case type
842
+ when "entity"
843
+ display_entity_children(element, child_prefix, current_depth + 1)
844
+ when "type"
845
+ display_type_children(element, child_prefix, current_depth + 1)
846
+ when "function", "procedure"
847
+ display_callable_children(element, child_prefix, current_depth + 1)
848
+ end
849
+ end
850
+
851
+ # Display entity children (attributes)
852
+ # @param entity [Declarations::Entity] Entity to display
853
+ # @param prefix [String] Current indentation prefix
854
+ # @param current_depth [Integer] Current tree depth
855
+ # @return [void]
856
+ def display_entity_children(entity, prefix, current_depth)
857
+ return if options[:depth] && current_depth > options[:depth]
858
+
859
+ children = []
860
+
861
+ # Add explicit attributes
862
+ if should_include_type?("attribute") && entity.attributes
863
+ entity.attributes.each do |a|
864
+ children << { element: a, type: "attribute" }
865
+ end
866
+ end
867
+
868
+ # Add derived attributes
869
+ if should_include_type?("derived_attribute") && entity.derived_attributes
870
+ entity.derived_attributes.each do |a|
871
+ children << { element: a, type: "derived_attribute" }
872
+ end
873
+ end
874
+
875
+ # Add inverse attributes
876
+ if should_include_type?("inverse_attribute") && entity.inverse_attributes
877
+ entity.inverse_attributes.each do |a|
878
+ children << { element: a, type: "inverse_attribute" }
879
+ end
880
+ end
881
+
882
+ # Show summary if too many and not showing all
883
+ if children.size > 5 && !should_show_all_attributes?
884
+ # Show first few
885
+ children.take(3).each_with_index do |child, _idx|
886
+ display_element_tree(child[:element], child[:type], false, prefix,
887
+ current_depth)
888
+ end
889
+
890
+ # Show summary
891
+ remaining = children.size - 3
892
+ summary = "... (#{remaining} more #{pluralize('attribute',
893
+ remaining)})"
894
+ say "#{prefix}└─ #{colorize(summary, :gray)}"
895
+ else
896
+ # Show all
897
+ children.each_with_index do |child, idx|
898
+ is_last = idx == children.size - 1
899
+ display_element_tree(child[:element], child[:type], is_last,
900
+ prefix, current_depth)
901
+ end
902
+ end
903
+ end
904
+
905
+ # Display type children
906
+ # @param type_elem [Declarations::Type] Type to display
907
+ # @param prefix [String] Current indentation prefix
908
+ # @param current_depth [Integer] Current tree depth
909
+ # @return [void]
910
+ def display_type_children(type_elem, prefix, current_depth)
911
+ return if options[:depth] && current_depth > options[:depth]
912
+
913
+ # For SELECT types, show items
914
+ if type_elem.underlying_type.is_a?(Expressir::Model::DataTypes::Select) && type_elem.underlying_type.items
915
+ items = type_elem.underlying_type.items
916
+ if items.size > 5 && !should_show_all_attributes?
917
+ # Show first few items
918
+ items.take(3).each_with_index do |item, _idx|
919
+ connector = "├─ "
920
+ say "#{prefix}#{connector}#{colorize(item.ref || item.id, :cyan)}"
921
+ end
922
+
923
+ # Items summary
924
+ remaining = items.size - 3
925
+ items_summary = "... [#{remaining} more items]"
926
+ say "#{prefix}└─ #{colorize(items_summary, :gray)}"
927
+
928
+ else
929
+ items.each_with_index do |item, idx|
930
+ is_last = idx == items.size - 1
931
+ connector = is_last ? "└─ " : "├─ "
932
+ say "#{prefix}#{connector}#{colorize(item.ref || item.id, :cyan)}"
933
+ end
934
+ end
935
+ end
936
+ end
937
+
938
+ # Display function/procedure children (parameters)
939
+ # @param callable [Declarations::Function, Declarations::Procedure] Callable to display
940
+ # @param prefix [String] Current indentation prefix
941
+ # @param current_depth [Integer] Current tree depth
942
+ # @return [void]
943
+ def display_callable_children(callable, prefix, current_depth)
944
+ return if options[:depth] && current_depth > options[:depth]
945
+ return unless should_include_type?("parameter")
946
+ return unless callable.parameters
947
+
948
+ callable.parameters.each_with_index do |param, idx|
949
+ is_last = idx == callable.parameters.size - 1
950
+ display_element_tree(param, "parameter", is_last, prefix,
951
+ current_depth)
952
+ end
953
+ end
954
+
955
+ # Format element text with type information
956
+ # @param element [ModelElement] Element to format
957
+ # @param type [String] Element type
958
+ # @return [String] Formatted text
959
+ def format_element_text(element, type)
960
+ case type
961
+ when "entity", "type", "function", "procedure", "rule"
962
+ "#{element.id} (#{type})"
963
+ when "attribute", "derived_attribute", "inverse_attribute", "parameter"
964
+ type_info = extract_type_info(element)
965
+ "#{element.id} (#{type.sub('_attribute', '')}): #{type_info}"
966
+ when "parameter"
967
+ type_info = extract_type_info(element)
968
+ "#{element.id} (parameter): #{type_info}"
969
+ else
970
+ "#{element.id} (#{type})"
971
+ end
972
+ end
973
+
974
+ # Extract type information from attribute/parameter
975
+ # @param element [Attribute, Parameter] Element with type
976
+ # @return [String] Type description
977
+ def extract_type_info(element)
978
+ return "ANY" unless element.type
979
+
980
+ type = element.type
981
+ case type
982
+ when Expressir::Model::DataTypes::Set
983
+ "SET"
984
+ when Expressir::Model::DataTypes::List
985
+ "LIST"
986
+ when Expressir::Model::DataTypes::Array
987
+ "ARRAY"
988
+ when Expressir::Model::References::SimpleReference
989
+ type.ref || type.id
990
+ when Expressir::Model::DataTypes::String
991
+ "STRING"
992
+ when Expressir::Model::DataTypes::Integer
993
+ "INTEGER"
994
+ when Expressir::Model::DataTypes::Real
995
+ "REAL"
996
+ when Expressir::Model::DataTypes::Boolean
997
+ "BOOLEAN"
998
+ when Expressir::Model::DataTypes::Logical
999
+ "LOGICAL"
1000
+ when Expressir::Model::DataTypes::Number
1001
+ "NUMBER"
1002
+ else
1003
+ type.class.name.split("::").last
1004
+ end
1005
+ end
1006
+
1007
+ # Build counts text for schema
1008
+ # @param schema [Declarations::Schema] Schema
1009
+ # @return [String] Counts text
1010
+ def build_counts_text(schema)
1011
+ counts = []
1012
+ entities_count = schema.entities&.size || 0
1013
+ types_count = schema.types&.size || 0
1014
+ functions_count = schema.functions&.size || 0
1015
+
1016
+ if entities_count.positive?
1017
+ counts << "#{entities_count} #{pluralize('entity',
1018
+ entities_count)}"
1019
+ end
1020
+ if types_count.positive?
1021
+ counts << "#{types_count} #{pluralize('type',
1022
+ types_count)}"
1023
+ end
1024
+ if functions_count.positive?
1025
+ counts << "#{functions_count} #{pluralize('function',
1026
+ functions_count)}"
1027
+ end
1028
+
1029
+ counts.any? ? " [#{counts.join(', ')}]" : ""
1030
+ end
1031
+
1032
+ # Colorize text based on options
1033
+ # @param text [String] Text to colorize
1034
+ # @param color [Symbol] Color name
1035
+ # @param style [Symbol, nil] Optional style (bold, etc.)
1036
+ # @return [String] Colorized or plain text
1037
+ def colorize(text, color, style = nil)
1038
+ return "" if text.nil?
1039
+ return text if options[:no_color]
1040
+
1041
+ if style
1042
+ Paint[text, color, style]
1043
+ else
1044
+ Paint[text, color]
1045
+ end
1046
+ end
1047
+
1048
+ # Get color for element type
1049
+ # @param type [String] Element type
1050
+ # @return [Symbol] Color name
1051
+ def element_color(type)
1052
+ case type
1053
+ when "entity"
1054
+ :green
1055
+ when "type"
1056
+ :yellow
1057
+ when "attribute", "derived_attribute", "inverse_attribute", "parameter"
1058
+ :cyan
1059
+ when "function", "procedure"
1060
+ :magenta
1061
+ when "rule"
1062
+ :red
1063
+ else
1064
+ :white
1065
+ end
1066
+ end
1067
+
1068
+ # Check if type should be included based on filter
1069
+ # @param type [String] Element type to check
1070
+ # @return [Boolean] Whether to include
1071
+ def should_include_type?(type)
1072
+ return true unless options[:type]
1073
+
1074
+ # Handle attribute variations
1075
+ return true if options[:type] == "attribute" && type.end_with?("_attribute")
1076
+
1077
+ options[:type] == type
1078
+ end
1079
+
1080
+ # Check if all attributes should be shown
1081
+ # @return [Boolean] Whether to show all attributes
1082
+ def should_show_all_attributes?
1083
+ # Show all if specifically filtering for attributes
1084
+ options[:type]&.include?("attribute")
1085
+ end
1086
+
1087
+ # Pluralize word based on count
1088
+ # @param word [String] Word to pluralize
1089
+ # @param count [Integer] Count
1090
+ # @return [String] Singular or plural form
1091
+ def pluralize(word, count)
1092
+ count == 1 ? word : "#{word}s"
1093
+ end
1094
+
1095
+ # Output validation results in text format
1096
+ # @param validation [Hash] Validation results
1097
+ # @return [void]
1098
+ def output_text_validation(validation)
1099
+ if validation[:valid?]
1100
+ say "✓ Package is valid", :green
1101
+ else
1102
+ say "✗ Validation failed", :red
1103
+ say ""
1104
+ errors = validation[:errors] || []
1105
+ say "Errors (#{errors.size}):", :bold
1106
+ errors.each_with_index do |e, i|
1107
+ error_msg = if e.is_a?(Hash)
1108
+ # Format hash errors properly
1109
+ msg = e[:message] || "Unknown error"
1110
+ type = e[:type] ? "[#{e[:type]}] " : ""
1111
+ "#{type}#{msg}"
1112
+ else
1113
+ # Fallback for string errors
1114
+ e.to_s
1115
+ end
1116
+ say " #{i + 1}. #{error_msg}", :red
1117
+ if e.is_a?(Hash) && e[:schema]
1118
+ say " Schema: #{e[:schema]}",
1119
+ :red
1120
+ end
1121
+ if e[:referenced_schema]
1122
+ say " Referenced: #{e[:referenced_schema]}",
1123
+ :red
1124
+ end
1125
+ if e[:interface_type]
1126
+ say " Interface: #{e[:interface_type]}", :red
1127
+ end
1128
+ say " Item: #{e[:item]}", :red if e[:item]
1129
+ say " Fix: #{e[:fix_suggestion]}", :yellow if e[:fix_suggestion]
1130
+ say "" unless i == errors.size - 1
1131
+ end
1132
+ end
1133
+
1134
+ warnings = validation[:warnings]
1135
+ if warnings&.any?
1136
+ say ""
1137
+ say "Warnings (#{warnings.size}):", :bold
1138
+ warnings.each_with_index do |w, i|
1139
+ say " #{i + 1}. #{w[:message]}", :yellow
1140
+ say " Schema: #{w[:schema]}", :yellow if w[:schema]
1141
+ if w[:fix_suggestion]
1142
+ say " Fix: #{w[:fix_suggestion]}", :yellow
1143
+ end
1144
+ say "" unless i == warnings.size - 1
1145
+ end
1146
+ end
1147
+
1148
+ # Show detailed interface report if available
1149
+ if validation[:interface_report]
1150
+ say ""
1151
+ say validation[:interface_report]
1152
+ end
1153
+ end
1154
+
1155
+ # Output validation results in JSON format
1156
+ # @param validation [Hash] Validation results
1157
+ # @return [void]
1158
+ def output_json_validation(validation)
1159
+ say validation.to_json
1160
+ end
1161
+
1162
+ # Output validation results in YAML format
1163
+ # @param validation [Hash] Validation results
1164
+ # @return [void]
1165
+ def output_yaml_validation(validation)
1166
+ say validation.to_yaml
1167
+ end
1168
+
1169
+ # Output list results in text format
1170
+ # @param results [Array<Hash>] List results
1171
+ # @param type [String] Element type
1172
+ # @param schema_filter [String, nil] Schema filter
1173
+ # @param category_filter [String, nil] Category filter
1174
+ # @return [void]
1175
+ def output_text_list(results, type, schema_filter, category_filter)
1176
+ filters = []
1177
+ filters << "type: #{type}"
1178
+ filters << "schema: #{schema_filter}" if schema_filter
1179
+ filters << "category: #{category_filter}" if category_filter
1180
+ filter_text = filters.join(", ")
1181
+
1182
+ say "Elements (#{filter_text})", :bold
1183
+ say "Total: #{results.size}"
1184
+ say "=" * 60
1185
+
1186
+ results.each do |elem|
1187
+ if options[:show_path] && elem[:path]
1188
+ say elem[:path]
1189
+ elsif elem[:schema]
1190
+ category_label = elem[:category] ? " [#{elem[:category]}]" : ""
1191
+ say "#{elem[:schema]}.#{elem[:id]}#{category_label}"
1192
+ else
1193
+ say elem[:id] || "(unnamed)"
1194
+ end
1195
+ end
1196
+ end
1197
+
1198
+ # Output search results in text format
1199
+ # @param results [Array<Hash>] Search results
1200
+ # @param pattern [String] Search pattern
1201
+ # @return [void]
1202
+ def output_text_search_results(results, pattern)
1203
+ say "Search Results for: #{pattern}", :bold
1204
+ say "Total: #{results.size}"
1205
+ say "=" * 60
1206
+
1207
+ results.each do |elem|
1208
+ type_label = "[#{elem[:type]}]"
1209
+
1210
+ if options[:show_path] && elem[:path]
1211
+ say "#{type_label.ljust(20)} #{elem[:path]}"
1212
+ elsif elem[:schema]
1213
+ category_label = elem[:category] ? " [#{elem[:category]}]" : ""
1214
+ say "#{type_label.ljust(20)} #{elem[:schema]}.#{elem[:id]}#{category_label}"
1215
+ else
1216
+ say "#{type_label.ljust(20)} #{elem[:id] || '(unnamed)'}"
1217
+ end
1218
+ end
1219
+ end
1220
+ end
1221
+ end
1222
+ end