structure 3.7.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 272646d259a42b8a13d4c0fbc29072022da5260f5a7948d2f8dbaddb9f625adf
4
- data.tar.gz: a3045255ecb35adc801f827c48a5c620dc90943aee453f474bae6ab85bc8b58d
3
+ metadata.gz: 2306964e18c5a78a2241c8c42071fd0a1921fa8c7c54cf210b0825b20607cfca
4
+ data.tar.gz: c43cf42996e715fe5c1ca1b57bf91ee411405f188382b2d10c0b593a66e90a6f
5
5
  SHA512:
6
- metadata.gz: 4d5914cb2446f277c61369d156d8286cfc50b866423b7bc86523964216d76a677901848a3135a5f1061f0383569fb3ad03624c3806de9d7841281bd751e5aa16
7
- data.tar.gz: 3684e0a22399da848c493eb6b837d0c8427f0dd6f24857a7f65812914366b448108da8af2b377fc937a08715ea13de633b3f538f6b843a288c65936123dd32b7
6
+ metadata.gz: 314242bf300d7ec09e1755647d4a8fef224eb2c64fc0b6d945be386b7af008c22146e80993f1e753f1df9cc5b37841242b7ab4a14d71cde42732952e288fc196
7
+ data.tar.gz: 72dcc21770b4f57b67fd64b8ad69695f02d0a55b2622717f63795b8da32aa8b45cd61db146b2e2c4dc02683588ca74dba831437652afaed9bb25fc159195d0b5
@@ -13,6 +13,7 @@ module Structure
13
13
  @mappings = {}
14
14
  @defaults = {}
15
15
  @types = {}
16
+ @optional = Set.new
16
17
  end
17
18
 
18
19
  # DSL method for defining attributes with optional type coercion
@@ -45,6 +46,25 @@ module Structure
45
46
  end
46
47
  end
47
48
 
49
+ # DSL method for defining optional attributes (key can be missing from input hash)
50
+ #
51
+ # @param name [Symbol] The attribute name
52
+ # @param type [Class, Symbol, Array, nil] Type for coercion (e.g., String, :boolean, [String])
53
+ # @param from [String, nil] Source key in the data hash (defaults to name.to_s)
54
+ # @param default [Object, nil] Default value if attribute is missing
55
+ # @yield [value] Block for custom transformation
56
+ # @raise [ArgumentError] If both type and block are provided
57
+ #
58
+ # @example Optional attribute
59
+ # attribute? :age, Integer
60
+ #
61
+ # @example Optional with default
62
+ # attribute? :status, String, default: "pending"
63
+ def attribute?(name, type = nil, from: nil, default: nil, &block)
64
+ attribute(name, type, from: from, default: default, &block)
65
+ @optional.add(name)
66
+ end
67
+
48
68
  # Defines a callback to run after parsing
49
69
  #
50
70
  # @yield [instance] Block that receives the parsed instance
@@ -59,9 +79,13 @@ module Structure
59
79
  end
60
80
 
61
81
  # @api private
62
- def attributes
63
- @mappings.keys
64
- end
82
+ def attributes = @mappings.keys
83
+
84
+ # @api private
85
+ def optional = @optional.to_a
86
+
87
+ # @api private
88
+ def required = attributes - optional
65
89
 
66
90
  # @api private
67
91
  def coercions(context = nil)
data/lib/structure/rbs.rb CHANGED
@@ -17,11 +17,13 @@ module Structure
17
17
 
18
18
  attributes = meta[:mappings] ? meta[:mappings].keys : klass.members
19
19
  types = meta.fetch(:types, {}) # steep:ignore
20
+ required = meta.fetch(:required, attributes) # steep:ignore
20
21
 
21
22
  emit_rbs_content(
22
23
  class_name:,
23
24
  attributes:,
24
25
  types:,
26
+ required:,
25
27
  has_structure_modules: meta.any?,
26
28
  )
27
29
  end
@@ -47,7 +49,7 @@ module Structure
47
49
 
48
50
  private
49
51
 
50
- def emit_rbs_content(class_name:, attributes:, types:, has_structure_modules:)
52
+ def emit_rbs_content(class_name:, attributes:, types:, required:, has_structure_modules:)
51
53
  # @type var lines: Array[String]
52
54
  lines = []
53
55
  lines << "class #{class_name} < Data"
@@ -61,7 +63,11 @@ module Structure
61
63
  [attr, rbs_type != "untyped" ? "#{rbs_type}?" : rbs_type]
62
64
  end.to_h
63
65
 
64
- keyword_params = attributes.map { |attr| "#{attr}: #{rbs_types[attr]}" }.join(", ")
66
+ # Mark optional attributes with ? prefix in keyword params
67
+ keyword_params = attributes.map do |attr|
68
+ prefix = required.include?(attr) ? "" : "?"
69
+ "#{prefix}#{attr}: #{rbs_types[attr]}"
70
+ end.join(", ")
65
71
  positional_params = attributes.map { |attr| rbs_types[attr] }.join(", ")
66
72
 
67
73
  lines << " def self.new: (#{keyword_params}) -> #{class_name}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Structure
4
- VERSION = "3.7.0"
4
+ VERSION = "4.0.0"
5
5
  end
data/lib/structure.rb CHANGED
@@ -26,6 +26,21 @@ module Structure
26
26
  # @type var klass: untyped
27
27
  klass = Data.define(*builder.attributes)
28
28
 
29
+ # Override initialize to make optional attributes truly optional
30
+ optional_attrs = builder.optional
31
+ unless optional_attrs.empty?
32
+ klass.class_eval do
33
+ alias_method(:__data_initialize__, :initialize)
34
+
35
+ define_method(:initialize) do |**kwargs| # steep:ignore
36
+ optional_attrs.each do |attr|
37
+ kwargs[attr] = nil unless kwargs.key?(attr)
38
+ end
39
+ __data_initialize__(**kwargs) # steep:ignore
40
+ end
41
+ end
42
+ end
43
+
29
44
  builder.predicate_methods.each do |pred, attr|
30
45
  klass.define_method(pred) { !!public_send(attr) }
31
46
  end
@@ -37,6 +52,7 @@ module Structure
37
52
  mappings: builder.mappings,
38
53
  coercions: builder.coercions(klass),
39
54
  after_parse: builder.after_parse_callback,
55
+ required: builder.required,
40
56
  }.freeze
41
57
  klass.instance_variable_set(:@__structure_meta__, meta)
42
58
  klass.singleton_class.attr_reader(:__structure_meta__)
@@ -72,6 +88,15 @@ module Structure
72
88
  mappings = __structure_meta__[:mappings]
73
89
  defaults = __structure_meta__[:defaults]
74
90
  after_parse = __structure_meta__[:after_parse]
91
+ required = __structure_meta__[:required]
92
+
93
+ # Check for missing required attributes
94
+ required.each do |attr|
95
+ from = mappings[attr]
96
+ next if data.key?(from) || data.key?(from.to_sym) || defaults.key?(attr)
97
+
98
+ raise ArgumentError, "missing keyword: :#{attr}"
99
+ end
75
100
 
76
101
  mappings.each do |attr, from|
77
102
  value = data.fetch(from) do
@@ -4,10 +4,14 @@ module Structure
4
4
  @types: Hash[Symbol, untyped]
5
5
  @defaults: Hash[Symbol, untyped]
6
6
  @after_parse_callback: Proc?
7
+ @optional: Set[Symbol]
7
8
 
8
9
  def attribute: (Symbol name, untyped type, ?from: String?, ?default: untyped) ?{ (untyped) -> untyped } -> void
9
10
  | (Symbol name, ?from: String, ?default: untyped) ?{ (untyped) -> untyped } -> void
10
11
 
12
+ def attribute?: (Symbol name, untyped type, ?from: String?, ?default: untyped) ?{ (untyped) -> untyped } -> void
13
+ | (Symbol name, ?from: String, ?default: untyped) ?{ (untyped) -> untyped } -> void
14
+
11
15
  def after_parse: () { (Data) -> void } -> void
12
16
 
13
17
  def attributes: () -> Array[Symbol]
@@ -16,6 +20,8 @@ module Structure
16
20
  def defaults: () -> Hash[Symbol, untyped]
17
21
  def coercions: (?untyped? context) -> Hash[Symbol, Proc]
18
22
  def predicate_methods: () -> Hash[Symbol, Symbol]
23
+ def optional: () -> Array[Symbol]
24
+ def required: () -> Array[Symbol]
19
25
  def after_parse_callback: () -> (Proc | nil)
20
26
  end
21
27
  end
@@ -3,7 +3,7 @@ module Structure
3
3
  def self.emit: (untyped klass) -> String?
4
4
  def self.write: (untyped klass, ?dir: String) -> String?
5
5
 
6
- private def self.emit_rbs_content: (class_name: String, attributes: Array[Symbol], types: Hash[Symbol, untyped], has_structure_modules: bool) -> String
6
+ private def self.emit_rbs_content: (class_name: String, attributes: Array[Symbol], types: Hash[Symbol, untyped], required: Array[Symbol], has_structure_modules: bool) -> String
7
7
  private def self.parse_data_type: (untyped type, String class_name) -> String
8
8
  private def self.map_type_to_rbs: (untyped type, String class_name) -> String
9
9
  end
data/sig/structure.rbs CHANGED
@@ -5,7 +5,8 @@ module Structure
5
5
  defaults: Hash[Symbol, untyped],
6
6
  mappings: Hash[Symbol, String],
7
7
  coercions: Hash[Symbol, untyped],
8
- after_parse: untyped
8
+ after_parse: untyped,
9
+ required: Array[Symbol]
9
10
  }
10
11
  end
11
12
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: structure
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hakan Ensari