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 +4 -4
- data/lib/structure/builder.rb +27 -3
- data/lib/structure/rbs.rb +8 -2
- data/lib/structure/version.rb +1 -1
- data/lib/structure.rb +25 -0
- data/sig/structure/builder.rbs +6 -0
- data/sig/structure/rbs.rbs +1 -1
- data/sig/structure.rbs +2 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2306964e18c5a78a2241c8c42071fd0a1921fa8c7c54cf210b0825b20607cfca
|
4
|
+
data.tar.gz: c43cf42996e715fe5c1ca1b57bf91ee411405f188382b2d10c0b593a66e90a6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 314242bf300d7ec09e1755647d4a8fef224eb2c64fc0b6d945be386b7af008c22146e80993f1e753f1df9cc5b37841242b7ab4a14d71cde42732952e288fc196
|
7
|
+
data.tar.gz: 72dcc21770b4f57b67fd64b8ad69695f02d0a55b2622717f63795b8da32aa8b45cd61db146b2e2c4dc02683588ca74dba831437652afaed9bb25fc159195d0b5
|
data/lib/structure/builder.rb
CHANGED
@@ -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
|
-
|
64
|
-
|
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
|
-
|
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}"
|
data/lib/structure/version.rb
CHANGED
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
|
data/sig/structure/builder.rbs
CHANGED
@@ -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
|
data/sig/structure/rbs.rbs
CHANGED
@@ -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