structure 4.4.0 → 4.5.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 +40 -5
- data/lib/structure/types.rb +48 -17
- data/lib/structure/version.rb +1 -1
- data/lib/structure.rb +59 -40
- data/sig/structure/builder.rbs +6 -3
- data/sig/structure/types.rbs +3 -0
- data/sig/structure.rbs +5 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 548fe77f526e4c9f238da59611f13c3f13614f3111f3cf466054bacb84883b18
|
|
4
|
+
data.tar.gz: f6bda78f70879a1d373c8b5aa1aeb0ef3475004b0475ac5100adf9b6bfa5e657
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84f01c87a3e85b73d364d3994b1c0af646aedd50db182e9966b48bb21cc77432448342986931600f843b62c58677202247888791f58c901e809d1830e944915e
|
|
7
|
+
data.tar.gz: 8d65b5847a2c6839f49cd66ad9a0d08afa7eda6adeba4930dcb79fcff97b8f580e639b0b9a1c13c1a64c4f31c16a72a9517201998793d2c16143e44af69a7d95
|
data/lib/structure/builder.rb
CHANGED
|
@@ -41,14 +41,15 @@ module Structure
|
|
|
41
41
|
# @example Non-nullable attribute
|
|
42
42
|
# attribute :id, String, null: false
|
|
43
43
|
def attribute(name, type = nil, from: nil, default: nil, null: true, &block)
|
|
44
|
-
|
|
44
|
+
key = from || name
|
|
45
|
+
mappings[name] = key.is_a?(Symbol) ? key.name : -key.to_s
|
|
45
46
|
defaults[name] = default unless default.nil?
|
|
46
47
|
@non_nullable.add(name) unless null
|
|
47
48
|
|
|
48
49
|
if type && block
|
|
49
50
|
raise ArgumentError, "cannot specify both type and block for :#{name}"
|
|
50
51
|
else
|
|
51
|
-
types[name] = type || block
|
|
52
|
+
types[name] = shareable(type) || shareable_block(block)
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
|
|
@@ -77,15 +78,22 @@ module Structure
|
|
|
77
78
|
|
|
78
79
|
# Defines a callback to run after parsing
|
|
79
80
|
#
|
|
81
|
+
# @param callable [#call, nil] Object that receives the parsed instance, such as a module with a call method
|
|
80
82
|
# @yield [instance] Block that receives the parsed instance
|
|
81
83
|
# @return [void]
|
|
84
|
+
# @raise [ArgumentError] If both a callable and a block are provided
|
|
82
85
|
#
|
|
83
86
|
# @example Validation
|
|
84
87
|
# after_parse do |order|
|
|
85
88
|
# raise "Invalid order" if order.total < 0
|
|
86
89
|
# end
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
#
|
|
91
|
+
# @example With a module, which any Ractor can call
|
|
92
|
+
# after_parse(OrderValidator)
|
|
93
|
+
def after_parse(callable = nil, &block)
|
|
94
|
+
raise ArgumentError, "cannot specify both a callable and a block for after_parse" if callable && block
|
|
95
|
+
|
|
96
|
+
@after_parse_callback = callable || shareable_block(block)
|
|
89
97
|
end
|
|
90
98
|
|
|
91
99
|
# @api private
|
|
@@ -104,7 +112,7 @@ module Structure
|
|
|
104
112
|
def coercions(context = nil)
|
|
105
113
|
@types.to_h do |attr, type|
|
|
106
114
|
coercion = Types.coerce(type, context)
|
|
107
|
-
[attr, coercion]
|
|
115
|
+
[attr, coercion] #: [Symbol, Proc]
|
|
108
116
|
rescue ArgumentError => e
|
|
109
117
|
raise ArgumentError, "#{e.message} for :#{attr}"
|
|
110
118
|
end
|
|
@@ -118,5 +126,32 @@ module Structure
|
|
|
118
126
|
end
|
|
119
127
|
end.to_h
|
|
120
128
|
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Before Ruby 4.1, Ractors that call the same shareable block share its $~
|
|
133
|
+
# (https://bugs.ruby-lang.org/issues/22216), so one that matches a Regexp would read another Ractor's match. A block
|
|
134
|
+
# is shared from 4.1 only, and only when Ruby can: one that uses a local variable from around it stays as it is.
|
|
135
|
+
if defined?(Ractor) && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("4.1")
|
|
136
|
+
def shareable_block(block)
|
|
137
|
+
block && Ractor.shareable_proc(&block) # steep:ignore
|
|
138
|
+
rescue Ractor::IsolationError, ArgumentError
|
|
139
|
+
block
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
def shareable_block(block)
|
|
143
|
+
block
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Returns the type as another Ractor can read it: a frozen copy of an Array or of a class name. The caller's own
|
|
148
|
+
# object is left as it is.
|
|
149
|
+
def shareable(type)
|
|
150
|
+
case type
|
|
151
|
+
when Array then type.map { |element| shareable(element) }.freeze
|
|
152
|
+
when String then -type
|
|
153
|
+
else type
|
|
154
|
+
end
|
|
155
|
+
end
|
|
121
156
|
end
|
|
122
157
|
end
|
data/lib/structure/types.rb
CHANGED
|
@@ -64,28 +64,39 @@ module Structure
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# Makes an object shareable when Ruby can. A lambda that closes over a caller's unshareable object can't be,
|
|
68
|
+
# and comes back as it is. Where Ruby has no Ractor, nothing is shared.
|
|
69
|
+
if defined?(Ractor)
|
|
70
|
+
# @api private
|
|
71
|
+
def shareable(object)
|
|
72
|
+
Ractor.make_shareable(object)
|
|
73
|
+
rescue Ractor::IsolationError
|
|
74
|
+
object
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
# @api private
|
|
78
|
+
def shareable(object)
|
|
79
|
+
object
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
67
83
|
private
|
|
68
84
|
|
|
69
85
|
def boolean
|
|
70
|
-
@boolean ||= ->(val) { BOOLEAN_TRUTHY.include?(val) }
|
|
86
|
+
@boolean ||= shareable(->(val) { BOOLEAN_TRUTHY.include?(val) })
|
|
71
87
|
end
|
|
72
88
|
|
|
73
89
|
def self_referential(context)
|
|
74
|
-
->(val) { context.parse(val) }
|
|
90
|
+
shareable(->(val) { context.parse(val) })
|
|
75
91
|
end
|
|
76
92
|
|
|
77
93
|
def lazy_class(class_name, context)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
->(value) do
|
|
81
|
-
resolved_class ||= resolve_class(class_name, context)
|
|
82
|
-
# @type var resolved_class: untyped
|
|
83
|
-
resolved_class.parse(value)
|
|
84
|
-
end
|
|
94
|
+
key = shareable(Object.new)
|
|
95
|
+
shareable(->(value) { parse_as(resolved_class(key, class_name, context), value) })
|
|
85
96
|
end
|
|
86
97
|
|
|
87
98
|
def array(element_type, context = nil)
|
|
88
|
-
case element_type
|
|
99
|
+
coercion = case element_type
|
|
89
100
|
when :self
|
|
90
101
|
lambda do |value|
|
|
91
102
|
unless value.respond_to?(:map)
|
|
@@ -95,16 +106,14 @@ module Structure
|
|
|
95
106
|
value.map { |element| context.parse(element) }
|
|
96
107
|
end
|
|
97
108
|
when String
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
key = shareable(Object.new)
|
|
100
110
|
lambda do |value|
|
|
101
111
|
unless value.respond_to?(:map)
|
|
102
112
|
raise TypeError, "can't convert #{value.class} into Array"
|
|
103
113
|
end
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
value.map { |element| resolved_class.parse(element) }
|
|
115
|
+
element_class = resolved_class(key, element_type, context)
|
|
116
|
+
value.map { |element| parse_as(element_class, element) }
|
|
108
117
|
end
|
|
109
118
|
else
|
|
110
119
|
element_coercer = coerce(element_type, context)
|
|
@@ -116,16 +125,38 @@ module Structure
|
|
|
116
125
|
value.map { |element| element_coercer.call(element) }
|
|
117
126
|
end
|
|
118
127
|
end
|
|
128
|
+
|
|
129
|
+
shareable(coercion)
|
|
119
130
|
end
|
|
120
131
|
|
|
121
132
|
def parseable(type)
|
|
122
133
|
@parseable_cache ||= {} # : Hash[untyped, Proc]
|
|
123
|
-
@parseable_cache[type] ||= ->(val) { type
|
|
134
|
+
@parseable_cache[type] ||= shareable(->(val) { parse_as(type, val) })
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# A method must make this call, not a lambda. A parser written in C, such as Date.parse, keeps its match in
|
|
138
|
+
# the caller's $~, and a lambda shared between Ractors shares its $~ too.
|
|
139
|
+
def parse_as(type, val)
|
|
140
|
+
type.parse(val)
|
|
124
141
|
end
|
|
125
142
|
|
|
126
143
|
def kernel(type)
|
|
127
144
|
@kernel_cache ||= {} # : Hash[untyped, Proc]
|
|
128
|
-
@kernel_cache[type] ||= ->(val) { Kernel.send(type.name, val) }
|
|
145
|
+
@kernel_cache[type] ||= shareable(->(val) { Kernel.send(type.name, val) })
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Resolves a class given by name once in each Ractor. A shareable lambda can't keep the class itself, so it
|
|
149
|
+
# keeps a key into a weak map in the Ractor's local storage. Where Ruby has no Ractor, the key keeps the class.
|
|
150
|
+
if defined?(Ractor)
|
|
151
|
+
def resolved_class(key, class_name, context)
|
|
152
|
+
classes = Ractor.current[:__structure_classes__] ||= ObjectSpace::WeakMap.new # steep:ignore
|
|
153
|
+
classes[key] ||= resolve_class(class_name, context)
|
|
154
|
+
end
|
|
155
|
+
else
|
|
156
|
+
def resolved_class(key, class_name, context)
|
|
157
|
+
key.instance_variable_get(:@resolved) ||
|
|
158
|
+
key.instance_variable_set(:@resolved, resolve_class(class_name, context))
|
|
159
|
+
end
|
|
129
160
|
end
|
|
130
161
|
|
|
131
162
|
def resolve_class(class_name, context)
|
data/lib/structure/version.rb
CHANGED
data/lib/structure.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "structure/builder"
|
|
|
4
4
|
|
|
5
5
|
# A library for parsing data into immutable Ruby Data objects with type coercion
|
|
6
6
|
module Structure
|
|
7
|
-
TO_H_CHECKER = ->(x) { x.respond_to?(:to_h) && x }
|
|
7
|
+
TO_H_CHECKER = Types.shareable(->(x) { x.respond_to?(:to_h) && x })
|
|
8
8
|
private_constant :TO_H_CHECKER
|
|
9
9
|
|
|
10
10
|
class << self
|
|
@@ -29,7 +29,7 @@ module Structure
|
|
|
29
29
|
# @type var klass: untyped
|
|
30
30
|
klass = Data.define(*builder.attributes)
|
|
31
31
|
|
|
32
|
-
custom_methods_metadata = { instance: [], singleton: [] }.freeze # steep:ignore
|
|
32
|
+
custom_methods_metadata = { instance: [].freeze, singleton: [].freeze }.freeze # steep:ignore
|
|
33
33
|
|
|
34
34
|
# Enable custom method definitions by evaluating block on the class
|
|
35
35
|
if block
|
|
@@ -39,7 +39,7 @@ module Structure
|
|
|
39
39
|
# Provide temporary dummy DSL methods to prevent NoMethodError during class_eval
|
|
40
40
|
klass.define_singleton_method(:attribute) { |*args, **kwargs, &blk| }
|
|
41
41
|
klass.define_singleton_method(:attribute?) { |*args, **kwargs, &blk| }
|
|
42
|
-
klass.define_singleton_method(:after_parse) {
|
|
42
|
+
klass.define_singleton_method(:after_parse) { |*args, &blk| }
|
|
43
43
|
|
|
44
44
|
# Evaluate block in class context for method definitions
|
|
45
45
|
klass.class_eval(&block)
|
|
@@ -60,7 +60,7 @@ module Structure
|
|
|
60
60
|
|
|
61
61
|
{
|
|
62
62
|
name: name,
|
|
63
|
-
parameters: klass.instance_method(name).parameters,
|
|
63
|
+
parameters: Types.shareable(klass.instance_method(name).parameters),
|
|
64
64
|
}.freeze
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -70,7 +70,7 @@ module Structure
|
|
|
70
70
|
|
|
71
71
|
{
|
|
72
72
|
name: name,
|
|
73
|
-
parameters: singleton_class.instance_method(name).parameters,
|
|
73
|
+
parameters: Types.shareable(singleton_class.instance_method(name).parameters),
|
|
74
74
|
}.freeze
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -81,42 +81,46 @@ module Structure
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Override initialize to make optional attributes truly optional
|
|
84
|
-
optional_attrs = builder.optional
|
|
84
|
+
optional_attrs = builder.optional.freeze
|
|
85
85
|
unless optional_attrs.empty?
|
|
86
|
-
klass.
|
|
87
|
-
alias_method(:__data_initialize__, :initialize)
|
|
86
|
+
klass.alias_method(:__data_initialize__, :initialize)
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end
|
|
94
|
-
__data_initialize__(**kwargs)
|
|
88
|
+
# steep:ignore:start
|
|
89
|
+
define_shareable_method(klass, :initialize) do |**kwargs|
|
|
90
|
+
optional_attrs.each do |attr|
|
|
91
|
+
kwargs[attr] = nil unless kwargs.key?(attr)
|
|
95
92
|
end
|
|
96
|
-
|
|
93
|
+
__data_initialize__(**kwargs)
|
|
97
94
|
end
|
|
95
|
+
# steep:ignore:end
|
|
98
96
|
end
|
|
99
97
|
|
|
100
98
|
builder.predicate_methods.each do |pred, attr|
|
|
101
|
-
klass
|
|
99
|
+
define_common_method(klass, pred) { !!public_send(attr) }
|
|
102
100
|
end
|
|
103
101
|
|
|
104
|
-
# Store metadata on class to avoid closure capture (memory optimization)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
# Store metadata on class to avoid closure capture (memory optimization). What Structure builds of it is
|
|
103
|
+
# frozen, so the whole is shareable when the caller's own types, defaults and blocks are, and then any
|
|
104
|
+
# Ractor can parse.
|
|
105
|
+
klass.instance_variable_set(:@__structure_meta__, {
|
|
106
|
+
types: builder.types.freeze,
|
|
107
|
+
defaults: builder.defaults.freeze,
|
|
108
|
+
mappings: builder.mappings.freeze,
|
|
109
|
+
coercions: builder.coercions(klass).freeze,
|
|
110
110
|
after_parse: builder.after_parse_callback,
|
|
111
|
-
required: builder.required,
|
|
112
|
-
non_nullable: builder.non_nullable,
|
|
111
|
+
required: builder.required.freeze,
|
|
112
|
+
non_nullable: builder.non_nullable.freeze,
|
|
113
113
|
custom_methods: custom_methods_metadata,
|
|
114
|
-
}.freeze
|
|
115
|
-
klass.
|
|
116
|
-
|
|
114
|
+
}.freeze)
|
|
115
|
+
define_common_method(klass.singleton_class, :__structure_meta__) do
|
|
116
|
+
@__structure_meta__
|
|
117
|
+
rescue Ractor::IsolationError
|
|
118
|
+
raise Ractor::IsolationError,
|
|
119
|
+
"#{self} parses only in the main Ractor: a type, block, default or after_parse callback is not shareable"
|
|
120
|
+
end
|
|
117
121
|
|
|
118
122
|
# recursive to_h
|
|
119
|
-
klass
|
|
123
|
+
define_shareable_method(klass, :to_h) do
|
|
120
124
|
klass.members.to_h do |m|
|
|
121
125
|
v = public_send(m)
|
|
122
126
|
value = case v
|
|
@@ -130,10 +134,9 @@ module Structure
|
|
|
130
134
|
|
|
131
135
|
# parse accepts JSON-ish hashes + optional overrides hash
|
|
132
136
|
# overrides is a positional arg (not **kwargs) to avoid hash allocation when unused
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
klass.singleton_class.define_method(:parse) do |data = {}, overrides = nil|
|
|
137
|
+
define_common_method(klass.singleton_class, :parse) do |data = {}, overrides = nil|
|
|
138
|
+
# @type self: singleton(Data) & _StructuredDataClass
|
|
139
|
+
# @type var final: Hash[Symbol, untyped]
|
|
137
140
|
return data if data.is_a?(self)
|
|
138
141
|
|
|
139
142
|
unless data.respond_to?(:merge!)
|
|
@@ -143,12 +146,13 @@ module Structure
|
|
|
143
146
|
overrides&.each { |k, v| data[k.to_s] = v }
|
|
144
147
|
|
|
145
148
|
final = {}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
meta = __structure_meta__
|
|
150
|
+
mappings = meta[:mappings]
|
|
151
|
+
defaults = meta[:defaults]
|
|
152
|
+
coercions = meta[:coercions]
|
|
153
|
+
after_parse = meta[:after_parse]
|
|
154
|
+
required = meta[:required]
|
|
155
|
+
non_nullable = meta[:non_nullable]
|
|
152
156
|
|
|
153
157
|
# Check for missing required attributes
|
|
154
158
|
required.each do |attr|
|
|
@@ -186,10 +190,25 @@ module Structure
|
|
|
186
190
|
obj
|
|
187
191
|
end
|
|
188
192
|
|
|
189
|
-
klass.
|
|
190
|
-
klass.
|
|
193
|
+
define_common_method(klass.singleton_class, :load) { |data| parse(data) if data } # steep:ignore
|
|
194
|
+
define_common_method(klass.singleton_class, :dump) { |value| value&.to_h }
|
|
191
195
|
|
|
192
196
|
klass
|
|
193
197
|
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
# Defines a method that any Ractor can call, not only the one that defined it. The block may read only locals
|
|
202
|
+
# that hold shareable values and are assigned once.
|
|
203
|
+
def define_shareable_method(target, name, &block)
|
|
204
|
+
target.define_method(name, &Types.shareable(block))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# The same, for a method whose block is identical for every structure. The first block given for a name is
|
|
208
|
+
# made shareable once and reused; later ones are ignored.
|
|
209
|
+
def define_common_method(target, name, &block)
|
|
210
|
+
@common_methods ||= {} # : Hash[Symbol, Proc]
|
|
211
|
+
target.define_method(name, &(@common_methods[name] ||= Types.shareable(block)))
|
|
212
|
+
end
|
|
194
213
|
end
|
|
195
214
|
end
|
data/sig/structure/builder.rbs
CHANGED
|
@@ -3,7 +3,7 @@ module Structure
|
|
|
3
3
|
@mappings: Hash[Symbol, String]
|
|
4
4
|
@types: Hash[Symbol, untyped]
|
|
5
5
|
@defaults: Hash[Symbol, untyped]
|
|
6
|
-
@after_parse_callback:
|
|
6
|
+
@after_parse_callback: untyped
|
|
7
7
|
@optional: Set[Symbol]
|
|
8
8
|
@non_nullable: Set[Symbol]
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ module Structure
|
|
|
13
13
|
def attribute?: (Symbol name, untyped type, ?from: String | Symbol | nil, ?default: untyped, ?null: bool) ?{ (untyped) -> untyped } -> void
|
|
14
14
|
| (Symbol name, ?from: String | Symbol | nil, ?default: untyped, ?null: bool) ?{ (untyped) -> untyped } -> void
|
|
15
15
|
|
|
16
|
-
def after_parse: () { (Data) -> void } -> void
|
|
16
|
+
def after_parse: (?untyped callable) ?{ (Data) -> void } -> void
|
|
17
17
|
|
|
18
18
|
def attributes: () -> Array[Symbol]
|
|
19
19
|
def mappings: () -> Hash[Symbol, String]
|
|
@@ -24,7 +24,10 @@ module Structure
|
|
|
24
24
|
def optional: () -> Array[Symbol]
|
|
25
25
|
def required: () -> Array[Symbol]
|
|
26
26
|
def non_nullable: () -> Array[Symbol]
|
|
27
|
-
def after_parse_callback: () ->
|
|
27
|
+
def after_parse_callback: () -> untyped
|
|
28
|
+
|
|
29
|
+
private def shareable: (untyped type) -> untyped
|
|
30
|
+
private def shareable_block: (Proc? block) -> Proc?
|
|
28
31
|
|
|
29
32
|
def method_missing: (Symbol, *untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
|
|
30
33
|
def respond_to_missing?: (Symbol, bool) -> bool
|
data/sig/structure/types.rbs
CHANGED
|
@@ -12,14 +12,17 @@ module Structure
|
|
|
12
12
|
self.@string_class_cache: Hash[Array[untyped], Proc]
|
|
13
13
|
|
|
14
14
|
def self.coerce: (untyped type, ?untyped? context) -> untyped
|
|
15
|
+
def self.shareable: [T] (T object) -> T
|
|
15
16
|
|
|
16
17
|
private def self.boolean: () -> Proc
|
|
17
18
|
private def self.self_referential: (_ParseableClass context) -> Proc
|
|
18
19
|
private def self.lazy_class: (String class_name, untyped? context) -> Proc
|
|
19
20
|
private def self.array: (untyped element_type, ?untyped? context) -> Proc
|
|
20
21
|
private def self.parseable: (untyped type) -> Proc
|
|
22
|
+
private def self.parse_as: (untyped type, untyped val) -> untyped
|
|
21
23
|
private def self.kernel: (Class type) -> Proc
|
|
22
24
|
private def self.parse: (untyped val) -> untyped
|
|
25
|
+
private def self.resolved_class: (Object key, String class_name, untyped? context) -> _ParseableClass
|
|
23
26
|
private def self.resolve_class: (String class_name, untyped? context) -> _ParseableClass
|
|
24
27
|
end
|
|
25
28
|
end
|
data/sig/structure.rbs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Structure
|
|
2
2
|
TO_H_CHECKER: Proc
|
|
3
3
|
|
|
4
|
+
self.@common_methods: Hash[Symbol, Proc]
|
|
5
|
+
|
|
4
6
|
interface _StructuredDataClass
|
|
5
7
|
def __structure_meta__: () -> {
|
|
6
8
|
types: Hash[Symbol, untyped],
|
|
@@ -15,4 +17,7 @@ module Structure
|
|
|
15
17
|
# Block evaluated twice: first for DSL methods (attribute calls), then for custom methods
|
|
16
18
|
# Context set to Builder so DSL methods are recognized
|
|
17
19
|
def self.new: () ?{ () [self: Structure::Builder] -> void } -> untyped
|
|
20
|
+
|
|
21
|
+
private def self.define_shareable_method: (untyped target, Symbol name) { (?) -> untyped } -> void
|
|
22
|
+
private def self.define_common_method: (untyped target, Symbol name) { (?) -> untyped } -> void
|
|
18
23
|
end
|
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: 4.
|
|
4
|
+
version: 4.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hakan Ensari
|
|
@@ -37,14 +37,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '3.
|
|
40
|
+
version: '3.3'
|
|
41
41
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
42
|
requirements:
|
|
43
43
|
- - ">="
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
45
|
version: '0'
|
|
46
46
|
requirements: []
|
|
47
|
-
rubygems_version: 4.0.
|
|
47
|
+
rubygems_version: 4.0.20
|
|
48
48
|
specification_version: 4
|
|
49
49
|
summary: Structure your data
|
|
50
50
|
test_files: []
|