lutaml-model 0.8.17 → 0.8.18

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/downstream-performance.yml +27 -1
  3. data/.github/workflows/js-pr-check.yml +102 -0
  4. data/.github/workflows/js-sync.yml +51 -0
  5. data/.github/workflows/opal.yml +26 -6
  6. data/.github/workflows/performance.yml +32 -7
  7. data/.github/workflows/rake.yml +78 -32
  8. data/.gitmodules +6 -0
  9. data/.rubocop_todo.yml +8 -0
  10. data/Gemfile +16 -1
  11. data/README.adoc +267 -0
  12. data/Rakefile +166 -4
  13. data/docs/_guides/schema-import.adoc +42 -0
  14. data/docs/_guides/xml-mapping.adoc +21 -0
  15. data/docs/_pages/validation.adoc +65 -0
  16. data/lib/compat/opal/generate_boot.rb +123 -0
  17. data/lib/compat/opal/js_bundle_entry.rb +42 -0
  18. data/lib/compat/opal/lutaml_model_boot.rb +497 -0
  19. data/lib/compat/opal/moxml_boot.rb +68 -0
  20. data/lib/compat/opal/yaml_compat.rb +32 -0
  21. data/lib/lutaml/json.rb +1 -1
  22. data/lib/lutaml/key_value/transform.rb +3 -8
  23. data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -1
  24. data/lib/lutaml/key_value/transformation.rb +17 -5
  25. data/lib/lutaml/model/adapter_resolver.rb +2 -2
  26. data/lib/lutaml/model/attribute.rb +88 -11
  27. data/lib/lutaml/model/cached_type_resolver.rb +1 -1
  28. data/lib/lutaml/model/error/union_schema_unsupported_error.rb +11 -0
  29. data/lib/lutaml/model/global_context.rb +11 -8
  30. data/lib/lutaml/model/instrumentation.rb +2 -2
  31. data/lib/lutaml/model/mapping/mapping_rule.rb +0 -6
  32. data/lib/lutaml/model/register.rb +2 -2
  33. data/lib/lutaml/model/runtime_compatibility.rb +50 -0
  34. data/lib/lutaml/model/schema/class_loader.rb +58 -0
  35. data/lib/lutaml/model/schema/compiled_output.rb +83 -0
  36. data/lib/lutaml/model/schema/definitions/attribute.rb +32 -0
  37. data/lib/lutaml/model/schema/definitions/choice.rb +20 -0
  38. data/lib/lutaml/model/schema/definitions/facet.rb +40 -0
  39. data/lib/lutaml/model/schema/definitions/group_import.rb +22 -0
  40. data/lib/lutaml/model/schema/definitions/member_walk.rb +33 -0
  41. data/lib/lutaml/model/schema/definitions/model.rb +40 -0
  42. data/lib/lutaml/model/schema/definitions/namespace.rb +23 -0
  43. data/lib/lutaml/model/schema/definitions/restricted_type.rb +30 -0
  44. data/lib/lutaml/model/schema/definitions/sequence.rb +19 -0
  45. data/lib/lutaml/model/schema/definitions/simple_content.rb +22 -0
  46. data/lib/lutaml/model/schema/definitions/transform_facet.rb +21 -0
  47. data/lib/lutaml/model/schema/definitions/type_ref.rb +20 -0
  48. data/lib/lutaml/model/schema/definitions/union_type.rb +28 -0
  49. data/lib/lutaml/model/schema/definitions/xml_root.rb +20 -0
  50. data/lib/lutaml/model/schema/definitions.rb +24 -0
  51. data/lib/lutaml/model/schema/file_writer.rb +70 -0
  52. data/lib/lutaml/model/schema/generator/definitions_collection.rb +16 -2
  53. data/lib/lutaml/model/schema/generator/property.rb +21 -4
  54. data/lib/lutaml/model/schema/module_nesting.rb +31 -0
  55. data/lib/lutaml/model/schema/namespace_naming.rb +46 -0
  56. data/lib/lutaml/model/schema/registry_generator.rb +115 -0
  57. data/lib/lutaml/model/schema/renderers/base.rb +40 -0
  58. data/lib/lutaml/model/schema/renderers/mappings.rb +72 -0
  59. data/lib/lutaml/model/schema/renderers/member_decls.rb +115 -0
  60. data/lib/lutaml/model/schema/renderers/model.rb +121 -0
  61. data/lib/lutaml/model/schema/renderers/namespace.rb +30 -0
  62. data/lib/lutaml/model/schema/renderers/registration.rb +64 -0
  63. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +93 -0
  64. data/lib/lutaml/model/schema/renderers/restricted_type.rb +88 -0
  65. data/lib/lutaml/model/schema/renderers/union.rb +83 -0
  66. data/lib/lutaml/model/schema/renderers.rb +19 -0
  67. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +47 -0
  68. data/lib/lutaml/model/schema/rnc_compiler.rb +62 -0
  69. data/lib/lutaml/model/schema/rng_compiler/define_classifier.rb +91 -0
  70. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +392 -0
  71. data/lib/lutaml/model/schema/rng_compiler/member_collector.rb +30 -0
  72. data/lib/lutaml/model/schema/rng_compiler/rng_helpers.rb +152 -0
  73. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +125 -0
  74. data/lib/lutaml/model/schema/rng_compiler.rb +220 -0
  75. data/lib/lutaml/model/schema/templates.rb +162 -0
  76. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +41 -0
  77. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +31 -107
  78. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +191 -0
  79. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +216 -0
  80. data/lib/lutaml/model/schema/xml_compiler/spec_builder/simple_types.rb +153 -0
  81. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +167 -0
  82. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +87 -0
  83. data/lib/lutaml/model/schema/xml_compiler.rb +38 -517
  84. data/lib/lutaml/model/schema.rb +19 -0
  85. data/lib/lutaml/model/serialize/attribute_definition.rb +14 -2
  86. data/lib/lutaml/model/serialize/builder.rb +15 -2
  87. data/lib/lutaml/model/serialize/value_mapping.rb +9 -57
  88. data/lib/lutaml/model/serialize.rb +6 -9
  89. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -3
  90. data/lib/lutaml/model/services.rb +0 -2
  91. data/lib/lutaml/model/store.rb +2 -1
  92. data/lib/lutaml/model/toml.rb +1 -1
  93. data/lib/lutaml/model/transform.rb +17 -39
  94. data/lib/lutaml/model/transformation_registry.rb +1 -1
  95. data/lib/lutaml/model/type.rb +1 -0
  96. data/lib/lutaml/model/union.rb +320 -0
  97. data/lib/lutaml/model/utils.rb +6 -0
  98. data/lib/lutaml/model/validation.rb +1 -4
  99. data/lib/lutaml/model/version.rb +1 -1
  100. data/lib/lutaml/model.rb +18 -0
  101. data/lib/lutaml/toml.rb +1 -1
  102. data/lib/lutaml/xml/adapter.rb +1 -1
  103. data/lib/lutaml/xml/adapter_loader.rb +1 -1
  104. data/lib/lutaml/xml/builder/base.rb +1 -1
  105. data/lib/lutaml/xml/builder.rb +1 -1
  106. data/lib/lutaml/xml/error/schema_validation_error.rb +25 -0
  107. data/lib/lutaml/xml/model_transform.rb +5 -1
  108. data/lib/lutaml/xml/schema/relaxng_schema.rb +5 -0
  109. data/lib/lutaml/xml/schema/xsd_schema.rb +4 -0
  110. data/lib/lutaml/xml/serialization/format_conversion.rb +48 -0
  111. data/lib/lutaml/xml/serialization/instance_methods.rb +13 -0
  112. data/lib/lutaml/xml/transformation/element_builder.rb +21 -8
  113. data/lib/lutaml/xml/transformation/ordered_applier.rb +6 -0
  114. data/lib/lutaml/xml/xsd_validator.rb +66 -0
  115. data/lib/lutaml/xml.rb +53 -12
  116. data/lutaml-model.gemspec +4 -2
  117. data/spec/fixtures/xml/schema/rnc/address_book.rnc +15 -0
  118. data/spec/fixtures/xml/schema/rnc/book_features.rnc +18 -0
  119. data/spec/fixtures/xml/schema/rnc/includes/book.rnc +6 -0
  120. data/spec/fixtures/xml/schema/rnc/includes/circular_a.rnc +8 -0
  121. data/spec/fixtures/xml/schema/rnc/includes/circular_b.rnc +8 -0
  122. data/spec/fixtures/xml/schema/rnc/includes/library.rnc +8 -0
  123. data/spec/fixtures/xml/schema/rng/address_book.rng +16 -0
  124. data/spec/fixtures/xml/schema/rng/book.rng +27 -0
  125. data/spec/fixtures/xml/schema/rng/fixed_value.rng +11 -0
  126. data/spec/fixtures/xml/schema/rng/fragment_in_choice.rng +30 -0
  127. data/spec/fixtures/xml/schema/rng/full_name.rng +39 -0
  128. data/spec/fixtures/xml/schema/rng/integer_range.rng +32 -0
  129. data/spec/fixtures/xml/schema/rng/list_type.rng +12 -0
  130. data/spec/fixtures/xml/schema/rng/namespaced.rng +9 -0
  131. data/spec/fixtures/xml/schema/rng/paragraph.rng +13 -0
  132. data/spec/fixtures/xml/schema/rng/person.rng +23 -0
  133. data/spec/fixtures/xml/schema/rng/union.rng +14 -0
  134. data/spec/fixtures/xml/validate_xml_with_address.xsd +13 -0
  135. data/spec/fixtures/xml/validate_xml_with_contact.xsd +11 -0
  136. data/spec/fixtures/xml/validate_xml_with_person.xsd +11 -0
  137. data/spec/fixtures/xml/validate_xml_with_person_strict.xsd +17 -0
  138. data/spec/fixtures/xml/validate_xml_with_product.xsd +37 -0
  139. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +134 -0
  140. data/spec/lutaml/model/attribute_spec.rb +169 -12
  141. data/spec/lutaml/model/boot_manifest_spec.rb +30 -0
  142. data/spec/lutaml/model/collection_spec.rb +18 -0
  143. data/spec/lutaml/model/mixed_content_spec.rb +171 -0
  144. data/spec/lutaml/model/opal_smoke_spec.rb +67 -2
  145. data/spec/lutaml/model/ordered_content_spec.rb +30 -0
  146. data/spec/lutaml/model/raw_element_spec.rb +125 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +7 -14
  148. data/spec/lutaml/model/runtime_compatibility_spec.rb +25 -0
  149. data/spec/lutaml/model/schema/renderers/model_spec.rb +149 -0
  150. data/spec/lutaml/model/schema/renderers/namespace_spec.rb +36 -0
  151. data/spec/lutaml/model/schema/renderers/restricted_type_spec.rb +75 -0
  152. data/spec/lutaml/model/schema/renderers/union_spec.rb +58 -0
  153. data/spec/lutaml/model/transform_apply_value_map_spec.rb +64 -0
  154. data/spec/lutaml/model/union_attribute_spec.rb +658 -0
  155. data/spec/lutaml/model/union_spec.rb +257 -0
  156. data/spec/lutaml/model/utils_spec.rb +14 -0
  157. data/spec/lutaml/xml/opal_xml_spec.rb +22 -20
  158. data/spec/lutaml/xml/schema/compiler_spec.rb +21 -4
  159. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +404 -0
  160. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +893 -0
  161. data/spec/lutaml/xml/validate_xml_with_constructs_spec.rb +222 -0
  162. data/spec/lutaml/xml/validate_xml_with_spec.rb +186 -0
  163. data/spec/spec_helper.rb +28 -11
  164. data/spec/support/opal.rb +5 -2
  165. metadata +97 -31
  166. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +0 -106
  167. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +0 -47
  168. data/lib/lutaml/model/schema/xml_compiler/choice.rb +0 -67
  169. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +0 -27
  170. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +0 -34
  171. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +0 -173
  172. data/lib/lutaml/model/schema/xml_compiler/element.rb +0 -121
  173. data/lib/lutaml/model/schema/xml_compiler/group.rb +0 -113
  174. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +0 -104
  175. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -52
  176. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +0 -40
  177. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +0 -265
  178. data/lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb +0 -110
  179. data/lib/lutaml/model/services/default_value_resolver.rb +0 -60
  180. data/spec/lutaml/model/services/default_value_resolver_spec.rb +0 -162
  181. data/spec/lutaml/xml/schema/compiler/attribute_group_spec.rb +0 -71
  182. data/spec/lutaml/xml/schema/compiler/attribute_spec.rb +0 -67
  183. data/spec/lutaml/xml/schema/compiler/choice_spec.rb +0 -74
  184. data/spec/lutaml/xml/schema/compiler/complex_content_restriction_spec.rb +0 -60
  185. data/spec/lutaml/xml/schema/compiler/complex_content_spec.rb +0 -41
  186. data/spec/lutaml/xml/schema/compiler/complex_type_spec.rb +0 -202
  187. data/spec/lutaml/xml/schema/compiler/element_spec.rb +0 -68
  188. data/spec/lutaml/xml/schema/compiler/group_spec.rb +0 -90
  189. data/spec/lutaml/xml/schema/compiler/restriction_spec.rb +0 -77
  190. data/spec/lutaml/xml/schema/compiler/sequence_spec.rb +0 -65
  191. data/spec/lutaml/xml/schema/compiler/simple_content_spec.rb +0 -61
  192. data/spec/lutaml/xml/schema/compiler/simple_type_spec.rb +0 -184
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Renderers
7
+ # Computes the `require_relative` / `require` lines that a
8
+ # generated Definitions::Model needs at the top of its file.
9
+ #
10
+ # XSD and RNG have different strategies because their type
11
+ # references mean different things:
12
+ #
13
+ # - XSD attributes carry TypeRef(:w3c | :symbol | :class_ref):
14
+ # :w3c -> require "lutaml/xml/w3c"
15
+ # :symbol -> require_relative "<value>"
16
+ # (unless the value is a skippable built-in)
17
+ # decimal is special: require "bigdecimal"
18
+ # :class_ref -> no require (class is autoloaded by registry)
19
+ # Plus any required_files surfaced by simple_content + the
20
+ # namespace class.
21
+ #
22
+ # - RNG attributes only ever reference other generated classes
23
+ # via :class_ref, so the calculator only collects class names
24
+ # (imports + class_ref types + namespace) and the caller
25
+ # formats them as `require_relative "snake_case_name"`.
26
+ module RequiredFilesCalculator
27
+ module_function
28
+
29
+ # XSD strategy. `skippable_type?` is a one-arg predicate the
30
+ # caller supplies so the calculator doesn't have to know the
31
+ # XSD built-in type table.
32
+ def for_xml(model, skippable_type:)
33
+ deps = []
34
+ Definitions::MemberWalk.each_attribute(model.members) do |attr|
35
+ deps.concat(xml_attribute_requires(attr, skippable_type))
36
+ end
37
+ deps.concat(simple_content_requires(model.simple_content, skippable_type))
38
+ deps << namespace_require(model.namespace_class_name) if model.namespace_class_name
39
+ deps.uniq
40
+ end
41
+
42
+ # RNG strategy. Returns the de-duplicated list of generated-class
43
+ # names the model depends on; caller wraps as `require_relative`.
44
+ def class_names_for_rng(model)
45
+ deps = model.imports.dup
46
+ Definitions::MemberWalk.each_attribute(model.members) do |attr|
47
+ deps << attr.type.value if attr.type.kind == :class_ref
48
+ end
49
+ deps << model.namespace_class_name if model.namespace_class_name
50
+ deps.uniq
51
+ end
52
+
53
+ def namespace_require(class_name)
54
+ %(require_relative "#{Utils.snake_case(class_name)}")
55
+ end
56
+
57
+ def xml_attribute_requires(attr, skippable_type)
58
+ ref = attr.type
59
+ return [] unless ref
60
+
61
+ case ref.kind
62
+ when :w3c then ['require "lutaml/xml/w3c"']
63
+ when :symbol then xml_symbol_requires(ref.value, skippable_type)
64
+ when :class_ref then []
65
+ else []
66
+ end
67
+ end
68
+
69
+ def xml_symbol_requires(value, skippable_type)
70
+ return [%(require "bigdecimal")] if value == "decimal"
71
+ return [] if skippable_type.call(value)
72
+
73
+ [%(require_relative "#{value}")]
74
+ end
75
+
76
+ # base_class arrives as a raw XSD name (e.g. "xs:dateTime"). The
77
+ # built-in skippable check keys on the XSD spelling (`:dateTime`),
78
+ # not the snake_case file form, so test against `local` directly
79
+ # before emitting the require_relative.
80
+ def simple_content_requires(simple_content, skippable_type)
81
+ return [] if simple_content.nil? || simple_content.base_class.nil?
82
+
83
+ local = Utils.last_of_split(simple_content.base_class)
84
+ return [%(require "bigdecimal")] if local == "decimal"
85
+ return [] if skippable_type.call(local)
86
+
87
+ [%(require_relative "#{Utils.snake_case(local)}")]
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ module Renderers
9
+ # Renders a Definitions::RestrictedType into a Ruby class extending
10
+ # a Lutaml::Model::Type::* with a cast body that mutates options
11
+ # with facet values and delegates to super.
12
+ class RestrictedType < Base
13
+ def render
14
+ Templates::RESTRICTED_SIMPLE_TYPE.result(binding)
15
+ end
16
+
17
+ private
18
+
19
+ def rendered_class_name = @spec.class_name
20
+ def parent_class = @spec.parent_class
21
+
22
+ def xml_namespace_line
23
+ ns = @spec.namespace_class_name
24
+ ns && "namespace #{ns}"
25
+ end
26
+
27
+ def restricted_simple_type_required_files = required_files_block
28
+
29
+ def restricted_simple_type_cast_body
30
+ [
31
+ render_min_max,
32
+ render_pattern,
33
+ render_enumerations,
34
+ render_transform,
35
+ ].compact.join
36
+ end
37
+
38
+ def render_min_max
39
+ f = @spec.facets
40
+ max = f.max_inclusive || f.max_exclusive
41
+ min = f.min_inclusive || f.min_exclusive
42
+ return nil unless max || min
43
+
44
+ out = +""
45
+ out << "#{@extended_indent}options[:max] = #{max}\n" if max
46
+ out << "#{@extended_indent}options[:min] = #{min}\n" if min
47
+ out
48
+ end
49
+
50
+ def render_pattern
51
+ p = @spec.facets.pattern
52
+ p && "#{@extended_indent}options[:pattern] = %r{#{p}}\n"
53
+ end
54
+
55
+ def render_enumerations
56
+ e = @spec.facets.enumerations
57
+ return nil if e.nil? || e.empty?
58
+
59
+ casted = e.map { |v| "super(#{v.inspect})" }.join(", ")
60
+ "#{@extended_indent}options[:values] = [#{casted}]\n"
61
+ end
62
+
63
+ def render_transform
64
+ t = @spec.transform_facet
65
+ t && "#{@extended_indent}value = #{t.expression}\n"
66
+ end
67
+
68
+ def registration_methods
69
+ Registration.methods_block(
70
+ class_name: @spec.class_name,
71
+ module_namespace: @module_namespace,
72
+ indent: @indent,
73
+ lazy: true,
74
+ keep_when_namespaced: @spec.keep_register_when_namespaced,
75
+ )
76
+ end
77
+
78
+ def registration_execution
79
+ Registration.execution_line(
80
+ class_name: @spec.class_name,
81
+ module_namespace: @module_namespace,
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ module Renderers
9
+ # Renders a Definitions::UnionType into a
10
+ # Lutaml::Model::Type::Value subclass with a cast body whose
11
+ # strategy is chosen from the spec.
12
+ class Union < Base
13
+ def render
14
+ Templates::UNION_TYPE.result(binding)
15
+ end
16
+
17
+ private
18
+
19
+ def rendered_class_name = @spec.class_name
20
+
21
+ def union_required_files = required_files_block
22
+
23
+ def union_cast_body
24
+ case @spec.cast_strategy
25
+ when :resolve_type then resolve_type_body
26
+ when :class_refs then class_refs_body
27
+ end
28
+ end
29
+
30
+ def resolve_type_body
31
+ chain = @spec.members.map { |m| resolve_type_call(m) }
32
+ "#{@extended_indent}#{chain.join(" ||\n ")}\n"
33
+ end
34
+
35
+ def resolve_type_call(type_ref)
36
+ "Lutaml::Model::GlobalContext.resolve_type(:#{type_ref.value}, @register).cast(value, options)"
37
+ end
38
+
39
+ def class_refs_body
40
+ classes = @spec.members.map { |m| literal_class(m) }.join(", ")
41
+ sp2 = @extended_indent
42
+ <<~BODY
43
+ #{sp2}[#{classes}].each do |t|
44
+ #{sp2} begin
45
+ #{sp2} casted = t.cast(value, options)
46
+ #{sp2} return casted unless casted.nil?
47
+ #{sp2} rescue StandardError
48
+ #{sp2} next
49
+ #{sp2} end
50
+ #{sp2}end
51
+ #{sp2}value
52
+ BODY
53
+ end
54
+
55
+ def literal_class(type_ref)
56
+ case type_ref.kind
57
+ when :class_ref then type_ref.value
58
+ when :w3c then "::#{type_ref.value}"
59
+ when :symbol then "Lutaml::Model::GlobalContext.resolve_type(:#{type_ref.value})"
60
+ end
61
+ end
62
+
63
+ def registration_methods
64
+ Registration.methods_block(
65
+ class_name: @spec.class_name,
66
+ module_namespace: @module_namespace,
67
+ indent: @indent,
68
+ lazy: @spec.lazy_register,
69
+ keep_when_namespaced: @spec.keep_register_when_namespaced,
70
+ )
71
+ end
72
+
73
+ def registration_execution
74
+ Registration.execution_line(
75
+ class_name: @spec.class_name,
76
+ module_namespace: @module_namespace,
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Renderers
7
+ autoload :Base, "#{__dir__}/renderers/base"
8
+ autoload :Registration, "#{__dir__}/renderers/registration"
9
+ autoload :RequiredFilesCalculator, "#{__dir__}/renderers/required_files_calculator"
10
+ autoload :MemberDecls, "#{__dir__}/renderers/member_decls"
11
+ autoload :Mappings, "#{__dir__}/renderers/mappings"
12
+ autoload :Model, "#{__dir__}/renderers/model"
13
+ autoload :RestrictedType, "#{__dir__}/renderers/restricted_type"
14
+ autoload :Union, "#{__dir__}/renderers/union"
15
+ autoload :Namespace, "#{__dir__}/renderers/namespace"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module RncCompiler
7
+ # Resolves RNC input text and the base directory used for relative
8
+ # include paths.
9
+ module SourceResolver
10
+ ResolvedSource =
11
+ Struct.new(:text, :base_dir, :path, keyword_init: true)
12
+
13
+ module_function
14
+
15
+ def resolve(input, options)
16
+ location = options[:location].to_s
17
+ return string_source(input) if location.empty?
18
+
19
+ expanded = File.expand_path(location)
20
+ if File.file?(expanded)
21
+ return file_source(expanded) if input.nil? || input.to_s.empty?
22
+
23
+ return string_source(input, base_dir: File.dirname(expanded), path: expanded)
24
+ end
25
+ return string_source(input, base_dir: expanded) if File.directory?(expanded)
26
+
27
+ string_source(input)
28
+ end
29
+
30
+ def string_source(input, base_dir: nil, path: nil)
31
+ ResolvedSource.new(text: input.to_s, base_dir: base_dir, path: path)
32
+ end
33
+
34
+ def file_source(path)
35
+ ResolvedSource.new(
36
+ text: File.read(path),
37
+ base_dir: File.dirname(path),
38
+ path: path,
39
+ )
40
+ end
41
+
42
+ private_class_method :string_source, :file_source
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ # Compiles RELAX NG Compact Syntax (RNC) into Lutaml::Model Ruby source.
7
+ #
8
+ # RNC support is a thin adapter: the rng gem parses the compact syntax
9
+ # into Rng::Grammar — resolving includes, bracket annotations, and
10
+ # non-leading `start` definitions natively — then RngCompiler performs all
11
+ # model generation and output handling.
12
+ module RncCompiler
13
+ extend self
14
+
15
+ autoload :SourceResolver, "#{__dir__}/rnc_compiler/source_resolver"
16
+
17
+ DEFAULT_OUTPUT_DIR_PREFIX = "rnc_models"
18
+
19
+ def to_models(rnc, options = {})
20
+ require_rnc_parser!
21
+
22
+ grammar = parse_grammar(rnc, options)
23
+
24
+ RngCompiler.to_models(
25
+ grammar,
26
+ options.merge(default_output_dir: default_output_dir),
27
+ )
28
+ end
29
+
30
+ def parse_grammar(rnc, options)
31
+ return rnc if rnc.is_a?(::Rng::Grammar)
32
+
33
+ source = SourceResolver.resolve(rnc, options)
34
+ ::Rng.parse_rnc(source.text, location: source.path || source.base_dir)
35
+ end
36
+
37
+ def require_rnc_parser!
38
+ return if rnc_parser_available?
39
+
40
+ raise "RNC schema compilation requires an rng gem whose " \
41
+ "`Rng.parse_rnc` accepts a `location:` option (for native " \
42
+ "include resolution). Point your Gemfile at an rng version " \
43
+ "that provides it."
44
+ end
45
+
46
+ # rng gained the `location:` option (native include resolution) after
47
+ # the 0.3.7 release; the released parse_rnc takes only `(rnc)`.
48
+ def rnc_parser_available?
49
+ defined?(::Rng::Grammar) && ::Rng.respond_to?(:parse_rnc) &&
50
+ ::Rng.method(:parse_rnc).parameters.any? { |_type, name| name == :location }
51
+ end
52
+
53
+ def default_output_dir
54
+ "#{DEFAULT_OUTPUT_DIR_PREFIX}_#{Time.now.to_i}"
55
+ end
56
+
57
+ private :parse_grammar, :require_rnc_parser!, :rnc_parser_available?,
58
+ :default_output_dir
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module RngCompiler
7
+ # Detects the shape of a <define> body and builds the corresponding
8
+ # Definitions::* spec for it.
9
+ #
10
+ # <data type="X"><param ...>...</param></data> -> RestrictedType
11
+ # <choice><value>a</value><value>b</value></choice> -> RestrictedType (enum)
12
+ # <choice><data type="A"/><data type="B"/></choice> -> UnionType
13
+ #
14
+ # Returns nil when the define is structural (contains elements or
15
+ # attributes) — caller falls back to building a Definitions::Model.
16
+ class DefineClassifier
17
+ def self.build(define, class_name)
18
+ new(define, class_name).build
19
+ end
20
+
21
+ def initialize(define, class_name)
22
+ @define = define
23
+ @class_name = class_name
24
+ end
25
+
26
+ def build
27
+ return nil if structural_define?
28
+
29
+ union_type || data_restricted_type || enum_restricted_type
30
+ end
31
+
32
+ private
33
+
34
+ def structural_define?
35
+ @define.element.any? || @define.attribute.any?
36
+ end
37
+
38
+ def union_type
39
+ choice = value_choice
40
+ return nil unless RngHelpers.pure_union_choice?(choice)
41
+
42
+ type_refs = Array(choice.data).map do |d|
43
+ symbol = RngCompiler::DATA_TYPE_MAP.fetch(
44
+ d.type, RngCompiler::DEFAULT_DATA_TYPE
45
+ )
46
+ Definitions::TypeRef.new(
47
+ kind: :class_ref,
48
+ value: RngHelpers.parent_class_for(symbol),
49
+ )
50
+ end
51
+
52
+ Definitions::UnionType.new(
53
+ class_name: @class_name,
54
+ members: type_refs,
55
+ cast_strategy: :class_refs,
56
+ )
57
+ end
58
+
59
+ def data_restricted_type
60
+ data = RngHelpers.single(@define.data)
61
+ return nil unless data
62
+
63
+ base = RngCompiler::DATA_TYPE_MAP.fetch(
64
+ data.type, RngCompiler::DEFAULT_DATA_TYPE
65
+ )
66
+ Definitions::RestrictedType.new(
67
+ class_name: @class_name,
68
+ parent_class: RngHelpers.parent_class_for(base),
69
+ facets: RngHelpers.facet_from_data(data),
70
+ )
71
+ end
72
+
73
+ def enum_restricted_type
74
+ choice = value_choice
75
+ return nil unless RngHelpers.pure_value_choice?(choice)
76
+
77
+ Definitions::RestrictedType.new(
78
+ class_name: @class_name,
79
+ parent_class: RngHelpers.parent_class_for(:string),
80
+ facets: RngHelpers.facet_from_values(choice.value),
81
+ )
82
+ end
83
+
84
+ def value_choice
85
+ RngHelpers.single(@define.choice)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end