remap 2.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/remap/base.rb +229 -75
  3. data/lib/remap/compiler.rb +127 -37
  4. data/lib/remap/constructor/argument.rb +20 -6
  5. data/lib/remap/constructor/keyword.rb +20 -4
  6. data/lib/remap/constructor/none.rb +3 -4
  7. data/lib/remap/constructor.rb +12 -5
  8. data/lib/remap/contract.rb +27 -0
  9. data/lib/remap/extensions/enumerable.rb +48 -0
  10. data/lib/remap/extensions/hash.rb +13 -0
  11. data/lib/remap/extensions/object.rb +37 -0
  12. data/lib/remap/failure.rb +25 -15
  13. data/lib/remap/iteration/array.rb +20 -11
  14. data/lib/remap/iteration/hash.rb +21 -13
  15. data/lib/remap/iteration/other.rb +7 -7
  16. data/lib/remap/iteration.rb +8 -2
  17. data/lib/remap/mapper/and.rb +29 -7
  18. data/lib/remap/mapper/binary.rb +3 -6
  19. data/lib/remap/mapper/or.rb +29 -6
  20. data/lib/remap/mapper/support/operations.rb +40 -0
  21. data/lib/remap/mapper/xor.rb +29 -7
  22. data/lib/remap/mapper.rb +1 -48
  23. data/lib/remap/notice/traced.rb +19 -0
  24. data/lib/remap/notice/untraced.rb +11 -0
  25. data/lib/remap/notice.rb +34 -0
  26. data/lib/remap/operation.rb +26 -13
  27. data/lib/remap/path/input.rb +37 -0
  28. data/lib/remap/path/output.rb +26 -0
  29. data/lib/remap/path.rb +22 -0
  30. data/lib/remap/path_error.rb +13 -0
  31. data/lib/remap/proxy.rb +18 -0
  32. data/lib/remap/rule/each.rb +25 -24
  33. data/lib/remap/rule/embed.rb +33 -28
  34. data/lib/remap/rule/map/optional.rb +42 -0
  35. data/lib/remap/rule/map/required.rb +35 -0
  36. data/lib/remap/rule/map.rb +176 -55
  37. data/lib/remap/rule/set.rb +23 -33
  38. data/lib/remap/rule/support/collection/empty.rb +7 -7
  39. data/lib/remap/rule/support/collection/filled.rb +21 -8
  40. data/lib/remap/rule/support/collection.rb +11 -3
  41. data/lib/remap/rule/support/enum.rb +44 -21
  42. data/lib/remap/rule/void.rb +17 -18
  43. data/lib/remap/rule/wrap.rb +25 -17
  44. data/lib/remap/rule.rb +8 -1
  45. data/lib/remap/selector/all.rb +29 -7
  46. data/lib/remap/selector/index.rb +24 -16
  47. data/lib/remap/selector/key.rb +31 -16
  48. data/lib/remap/selector.rb +17 -0
  49. data/lib/remap/state/extension.rb +182 -208
  50. data/lib/remap/state/schema.rb +1 -1
  51. data/lib/remap/state.rb +30 -4
  52. data/lib/remap/static/fixed.rb +14 -3
  53. data/lib/remap/static/option.rb +21 -6
  54. data/lib/remap/static.rb +13 -0
  55. data/lib/remap/struct.rb +1 -0
  56. data/lib/remap/types.rb +13 -28
  57. data/lib/remap.rb +15 -19
  58. metadata +91 -89
  59. data/lib/remap/result.rb +0 -11
  60. data/lib/remap/rule/support/path.rb +0 -45
  61. data/lib/remap/state/types.rb +0 -11
  62. data/lib/remap/success.rb +0 -29
  63. data/lib/remap/version.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feae800c3a05859a5cf0cefd1c080f3afa92109856b504aba89f81975f21dfc4
4
- data.tar.gz: 54c5b5b7dd28b67b67aed40019867d6293141edca668ea8d4de9b5b670dedaa4
3
+ metadata.gz: b3b3344d7c0ab3825ba1bb0a80c5ddbd7a00d57dbf762b0e26cd3cc6216e0644
4
+ data.tar.gz: 30db4e775e16a7923c46d4f1ca41b0b207ab0bb21cf791179a8fd76cbad9d455
5
5
  SHA512:
6
- metadata.gz: ecabf17439228869a9e14a2686cc38d92cd495a529efa2fc6114ffb10abf40837fe3fccf858123134f10c6a54f314e2464d8d309f854183b69c2875733cd48f4
7
- data.tar.gz: cc27691d7b1710d9642368559a25b7da4009c5a4346b5b44aa6166b81a8ec3ea053276ebd20b5d976548bf65290e9d2b4395395c2932edaaea8cea08bbdd285f
6
+ metadata.gz: e590c6d048e6cbb822c6bc49428f05e0dd6ead0f0f8752d563b5660adf422e61f1f8e2dc899490fedbdeb34bb36e3dc047906b7fe7eacf9a2711021c6f4a1d5f
7
+ data.tar.gz: 58ebb654582cbcfbfc776fb90a8ff2ce88a840642f1c1669c755478cb582f254f7f41a844762f7a228ed8a812e320aab073836dba190a15a3f0a42406a8740a0
data/lib/remap/base.rb CHANGED
@@ -1,146 +1,300 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/monads/all"
3
+ require "active_support/configurable"
4
+ require "active_support/core_ext/object/with_options"
4
5
 
5
6
  module Remap
7
+ # @example Select all elements
8
+ # class Mapper < Remap::Base
9
+ # define do
10
+ # map [all, :name]
11
+ # end
12
+ # end
13
+ #
14
+ # Mapper.call([{ name: "John" }, { name: "Jane" }]) # => ["John", "Jane"]
15
+ #
16
+ # @example Given an option
17
+ # class Mapper < Remap::Base
18
+ # option :name
19
+ #
20
+ # define do
21
+ # set [:person, :name], to: option(:name)
22
+ # end
23
+ # end
24
+ #
25
+ # Mapper.call({}, name: "John") # => { person: { name: "John" } }
26
+ #
27
+ # @example Given a value
28
+ # class Mapper < Remap::Base
29
+ # define do
30
+ # set [:api_key], to: value("ABC-123")
31
+ # end
32
+ # end
33
+ #
34
+ # Mapper.call({}) # => { api_key: "ABC-123" }
35
+ #
36
+ # @example Maps ["A", "B", "C"] to ["A", "C"]
37
+ # class Mapper < Remap::Base
38
+ # define do
39
+ # each do
40
+ # map.if_not do
41
+ # value.include?("B")
42
+ # end
43
+ # end
44
+ # end
45
+ # end
46
+ #
47
+ # Mapper.call(["A", "B", "C"]) # => ["A", "C"]
48
+ #
49
+ # @example Maps ["A", "B", "C"] to ["B"]
50
+ # class Mapper < Remap::Base
51
+ # define do
52
+ # each do
53
+ # map.if do
54
+ # value.include?("B")
55
+ # end
56
+ # end
57
+ # end
58
+ # end
59
+ #
60
+ # Mapper.call(["A", "B", "C"]) # => ["B"]
61
+ #
62
+ # @example Maps { a: { b: "A" } } to "A"
63
+ # class Mapper < Remap::Base
64
+ # define do
65
+ # map(:a, :b).enum do
66
+ # value "A", "B"
67
+ # end
68
+ # end
69
+ # end
70
+ #
71
+ # Mapper.call({ a: { b: "A" } }) # => "A"
72
+ # Mapper.call({ a: { b: "B" } }) # => "B"
73
+ #
74
+ # @example Map { people: [{ name: "John" }] } to { names: ["John"] }
75
+ # class Mapper < Remap::Base
76
+ # define do
77
+ # map :people, to: :names do
78
+ # each do
79
+ # map :name
80
+ # end
81
+ # end
82
+ # end
83
+ # end
84
+ #
85
+ # Mapper.call({ people: [{ name: "John" }] }) # => { names: ["John"] }
86
+ #
87
+ # @example Map "Hello" to "Hello!"
88
+ # class Mapper < Remap::Base
89
+ # define do
90
+ # map.adjust do
91
+ # "#{value}!"
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # Mapper.call("Hello") # => "Hello!"
97
+ #
98
+ # @example Select the second element from an array
99
+ # class Mapper < Remap::Base
100
+ # define do
101
+ # map [at(1)]
102
+ # end
103
+ # end
104
+ #
105
+ # Mapper.call([1, 2, 3]) # => 2
6
106
  class Base < Mapper
7
- include Dry::Core::Memoizable
107
+ include ActiveSupport::Configurable
8
108
  include Dry::Core::Constants
9
- extend Dry::Monads[:result]
10
-
11
- extend Dry::Configurable
12
- extend Forwardable
13
-
14
109
  using State::Extension
15
- extend State
110
+ extend Operation
16
111
 
17
- CONTRACT = Dry::Schema.JSON do
18
- # NOP
112
+ with_options instance_accessor: true do |scope|
113
+ scope.config_accessor(:contract) { Dry::Schema.JSON {} }
114
+ scope.config_accessor(:constructor) { IDENTITY }
115
+ scope.config_accessor(:options) { EMPTY_ARRAY }
116
+ scope.config_accessor(:option) { EMPTY_HASH }
117
+ scope.config_accessor(:rules) { EMPTY_ARRAY }
118
+ scope.config_accessor(:context) { IDENTITY }
19
119
  end
20
120
 
21
- setting :constructor, default: IDENTITY
22
- setting :options, default: EMPTY_ARRAY
23
- setting :rules, default: EMPTY_ARRAY
24
- setting :contract, default: CONTRACT
25
- setting :context, default: IDENTITY
26
-
27
- delegate [:config] => self
28
-
29
121
  schema schema.strict(false)
30
122
 
31
- # Holds the current context
32
- # @private
123
+ # Defines a schema for the mapper
124
+ # If the schema fail, the mapper will fail
125
+ #
126
+ # @example Guard against missing values
127
+ # class MapperWithAge < Remap::Base
128
+ # contract do
129
+ # required(:age).filled(:integer)
130
+ # end
131
+ #
132
+ # define do
133
+ # map :age, to: [:person, :age]
134
+ # end
135
+ # end
136
+ #
137
+ # MapperWithAge.call({age: 50}) # => { person: { age: 50 } }
138
+ # MapperWithAge.call({age: '10'}) do |failure|
139
+ # # ...
140
+ # end
141
+ #
142
+ # @see https://dry-rb.org/gems/dry-schema/1.5/
143
+ #
144
+ # @return [void]
33
145
  def self.contract(&context)
34
- config.contract = Dry::Schema.JSON(&context)
146
+ self.contract = Dry::Schema.JSON(&context)
35
147
  end
36
148
 
37
- # @see Dry::Validation::Contract.rule
149
+ # Defines a rule for the mapper
150
+ # If the rule fail, the mapper will fail
151
+ #
152
+ # @example Guard against values
153
+ # class MapperWithRule < Remap::Base
154
+ # contract do
155
+ # required(:age)
156
+ # end
157
+ #
158
+ # rule(:age) do
159
+ # unless value >= 18
160
+ # key.failure("must be at least 18 years old")
161
+ # end
162
+ # end
163
+ #
164
+ # define do
165
+ # map :age, to: [:person, :age]
166
+ # end
167
+ # end
168
+ #
169
+ # MapperWithRule.call({age: 50}) # => { person: { age: 50 } }
170
+ # MapperWithRule.call({age: 10}) do |failure|
171
+ # # ...
172
+ # end
173
+ #
174
+ # @see https://dry-rb.org/gems/dry-validation/1.6/rules/
175
+ #
176
+ # @return [void]
38
177
  def self.rule(...)
39
- config.rules << ->(*) { rule(...) }
178
+ self.rules = rules + [-> * { rule(...) }]
40
179
  end
41
180
 
42
- # Defines a a constructor argument for the mapper
181
+ # Defines a required option for the mapper
182
+ #
183
+ # @example A mapper that takes an argument name
184
+ # class MapperWithOption < Remap::Base
185
+ # option :name
186
+ #
187
+ # define do
188
+ # set :name, to: option(:name)
189
+ # end
190
+ # end
43
191
  #
44
- # @param name [Symbol]
45
- # @param type [#call]
192
+ # MapperWithOption.call({}, name: "John") # => { name: "John" }
193
+ #
194
+ # @param field [Symbol]
195
+ # @option type (Types::Any) [#call]
196
+ #
197
+ # @return [void]
46
198
  def self.option(field, type: Types::Any)
47
199
  attribute(field, type)
48
200
 
49
201
  unless (key = schema.keys.find { _1.name == field })
50
- raise ArgumentError, "Could not locate [#{field}] in [#{self}]"
202
+ raise ArgumentError, "[BUG] Could not locate [#{field}] in [#{self}]"
51
203
  end
52
204
 
53
- config.options << ->(*) { option(field, type: key) }
205
+ self.options = options + [-> * { option(field, type: key) }]
54
206
  end
55
207
 
56
- # Pretty print the mapper
208
+ # Defines a mapper rules and possible constructor
57
209
  #
58
- # @return [String]
59
- def self.inspect
60
- "<#{self.class} #{rule}, #{self}>"
61
- end
62
-
63
- # Defines a mapper with a constructor used to wrap the output
210
+ # @param target (Nothing) [#call]
64
211
  #
65
- # @param constructor [#call]
212
+ # @option method (:new) [Symbol]
213
+ # @option strategy (:argument) [:argument, :keywords, :none]
66
214
  #
67
- # @example A mapper from path :a to path :b
68
- # class Mapper < Remap
215
+ # @example A mapper, which mapps a value at [:a] to [:b]
216
+ # class Mapper < Remap::Base
69
217
  # define do
70
218
  # map :a, to: :b
71
219
  # end
72
220
  # end
73
221
  #
74
- # Mapper.call(a: 1) # => { b: 1 }
222
+ # Mapper.call({a: 1}) # => { b: 1 }
223
+ #
224
+ # @example A mapper with an output constructor
225
+ # class Person < Dry::Struct
226
+ # attribute :first_name, Dry::Types['strict.string']
227
+ # end
228
+ #
229
+ # class Mapper < Remap::Base
230
+ # define(Person) do
231
+ # map :name, to: :first_name
232
+ # end
233
+ # end
234
+ #
235
+ # Mapper.call({name: "John"}).first_name # => "John"
236
+ #
237
+ # @return [void]
75
238
  def self.define(target = Nothing, method: :new, strategy: :argument, &context)
76
239
  unless context
77
240
  raise ArgumentError, "Missing block"
78
241
  end
79
242
 
80
- config.context = Compiler.call(&context)
81
- config.constructor = Constructor.call(method: method, strategy: strategy, target: target)
243
+ self.context = Compiler.call(&context)
244
+ self.constructor = Constructor.call(method: method, strategy: strategy, target: target)
82
245
  rescue Dry::Struct::Error => e
83
246
  raise ArgumentError, e.message
84
247
  end
85
248
 
86
- # Creates a new mapper
249
+ # Similar to {::call}, but takes a state
87
250
  #
88
- # @param input [Any]
89
- # @param params [Hash]
90
-
91
- # @return [Context]
92
-
93
- extend Operation
94
-
251
+ # @param state [State]
252
+ #
253
+ # @yield [Failure] if mapper fails
254
+ #
255
+ # @return [Result] if mapper succeeds
256
+ #
257
+ # @private
95
258
  def self.call!(state, &error)
96
259
  new(state.options).call(state._.set(mapper: self), &error)
97
- rescue Dry::Struct::Error => e
98
- raise ArgumentError, "Option missing to mapper [#{self}]: #{e}"
99
260
  end
100
261
 
101
- # Creates a mapper tree using {#context} and uses {#state} as argument
262
+ # Mappers state according to the mapper rules
102
263
  #
103
- # @return [State]
264
+ # @param state [State]
104
265
  #
105
- # @see .call!
266
+ # @yield [Failure] if mapper fails
267
+ #
268
+ # @return [State]
106
269
  #
107
270
  # @private
108
271
  def call(state, &error)
109
272
  unless error
110
- raise ArgumentError, "Missing block"
273
+ raise ArgumentError, "Base#call(state, &error) requires block"
111
274
  end
112
275
 
113
276
  state.tap do |input|
114
- contract.call(input, state.options).tap do |result|
277
+ validation.call(input, state.options).tap do |result|
115
278
  unless result.success?
116
279
  return error[state.failure(result.errors.to_h)]
117
280
  end
118
281
  end
119
282
  end
120
283
 
121
- state.then(&config.context).then(&config.constructor)
284
+ notice = catch :fatal do
285
+ return context.call(state) do |failure|
286
+ return error[failure]
287
+ end.then(&constructor)
288
+ end
289
+
290
+ error[state.failure(notice)]
122
291
  end
123
292
 
124
293
  private
125
294
 
126
- def contract(scope: self)
127
- Class.new(Dry::Validation::Contract) do |klass|
128
- config = scope.class.config
129
-
130
- config.rules.each do |rule|
131
- klass.class_eval(&rule)
132
- end
133
-
134
- config.options.each do |option|
135
- klass.class_eval(&option)
136
- end
137
-
138
- schema(config.contract)
139
- end.new(**attributes)
140
- end
141
-
142
- def config
143
- self.class.config
295
+ # @return [Contract]
296
+ def validation
297
+ Contract.call(attributes: attributes, contract: contract, options: options, rules: rules)
144
298
  end
145
299
  end
146
300
  end
@@ -1,17 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Remap
4
- class Compiler
5
- include Dry::Core::Constants
6
- extend Dry::Initializer
7
- extend Forwardable
8
-
9
- param :rules, default: -> { EMPTY_ARRAY.dup }
4
+ # Constructs a {Rule} from the block passed to {Remap::Base.define}
5
+ class Compiler < Proxy
6
+ # @return [Array<Rule>]
7
+ param :rules, type: Types.Array(Rule)
10
8
 
11
9
  # @return [Rule]
12
- delegate call: self
10
+ delegate :call, to: Compiler
13
11
 
14
- # Constructs a rule tree given {block}
12
+ # Constructs a rule tree given block
13
+ #
14
+ # @example Compiles two rules
15
+ # rule = Remap::Compiler.call do
16
+ # get :name
17
+ # get :age
18
+ # end
19
+ #
20
+ # state = Remap::State.call({
21
+ # name: "John",
22
+ # age: 50
23
+ # })
24
+ #
25
+ # error = -> failure { raise failure.exception }
26
+ #
27
+ # rule.call(state, &error).fetch(:value) # => { name: "John", age: 50 }
15
28
  #
16
29
  # @return [Rule]
17
30
  def self.call(&block)
@@ -19,57 +32,109 @@ module Remap
19
32
  return Rule::Void.new
20
33
  end
21
34
 
22
- new.tap { _1.instance_eval(&block) }.rule
35
+ new([]).tap do |compiler|
36
+ compiler.instance_exec(&block)
37
+ end.rule
23
38
  end
24
39
 
25
- # Maps {path} to {to} with {block} inbetween
40
+ # Maps input path [input] to output path [to]
26
41
  #
27
42
  # @param path ([]) [Array<Segment>, Segment]
28
43
  # @param to ([]) [Array<Symbol>, Symbol]
29
44
  #
30
- # @return [Rule::Map]
31
- def map(*path, to: EMPTY_ARRAY, &block)
32
- add Rule::Map.new(
45
+ # @return [Rule::Map::Required]
46
+ def map(*path, to: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
47
+ add Rule::Map::Required.call(
33
48
  path: {
34
- map: path.flatten,
35
- to: [to].flatten
49
+ output: [to].flatten,
50
+ input: path.flatten
36
51
  },
37
- rule: call(&block)
38
- )
52
+ backtrace: backtrace,
53
+ rule: call(&block))
54
+ end
55
+
56
+ # Optional version of {#map}
57
+ #
58
+ # @see #map
59
+ #
60
+ # @return [Rule::Map::Optional]
61
+ def map?(*path, to: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
62
+ add Rule::Map::Optional.call(
63
+ path: {
64
+ output: [to].flatten,
65
+ input: path.flatten
66
+ },
67
+ backtrace: backtrace,
68
+ rule: call(&block))
69
+ end
70
+
71
+ # Select a path and uses the same path as output
72
+ #
73
+ # @param path ([]) [Array<Segment>, Segment]
74
+ #
75
+ # @return [Rule::Map::Required]
76
+ def get(*path, backtrace: Kernel.caller, &block)
77
+ map(path, to: path, backtrace: backtrace, &block)
78
+ end
79
+
80
+ # Optional version of {#get}
81
+ #
82
+ # @see #get
83
+ #
84
+ # @return [Rule::Map::Optional]
85
+ def get?(*path, backtrace: Kernel.caller, &block)
86
+ map?(path, to: path, backtrace: backtrace, &block)
39
87
  end
40
88
 
41
- # Maps using {mapper}
89
+ # Maps using mapper
42
90
  #
43
91
  # @param mapper [Remap]
44
92
  #
45
93
  # @return [Rule::Embed]
46
- def embed(mapper)
94
+ def embed(mapper, &block)
95
+ if block
96
+ raise ArgumentError, "#embed does not take a block"
97
+ end
98
+
47
99
  add Rule::Embed.new(mapper: mapper)
48
100
  rescue Dry::Struct::Error
49
101
  raise ArgumentError, "Embeded mapper must be [Remap::Mapper], got [#{mapper}]"
50
102
  end
51
103
 
52
- # @param *path ([]) [Symbol, Array<Symbol>]
104
+ # @param path ([]) [Symbol, Array<Symbol>]
53
105
  # @option to [Remap::Static]
54
106
  #
55
107
  # @return [Rule::Set]
56
108
  # @raise [ArgumentError]
57
109
  # if no path given
58
110
  # if path is not a Symbol or Array<Symbol>
59
- def set(*path, to:)
60
- add Rule::Set.new(path: { to: path.flatten, map: EMPTY_ARRAY }, value: to)
111
+ def set(*path, to:, &block)
112
+ if block
113
+ raise ArgumentError, "#set does not take a block"
114
+ end
115
+
116
+ add Rule::Set.new(path: path.flatten, value: to)
61
117
  rescue Dry::Struct::Error => e
62
118
  raise ArgumentError, e.message
63
119
  end
64
120
 
65
- # Maps to {path} from {map} with {block} inbetween
121
+ # Maps to path from map with block in between
66
122
  #
67
123
  # @param path [Array<Symbol>, Symbol]
68
124
  # @param map [Array<Segment>, Segment]
69
125
  #
70
126
  # @return [Rule::Map]
71
- def to(*path, map: EMPTY_ARRAY, &block)
72
- map(*map, to: path, &block)
127
+ def to(*path, map: EMPTY_ARRAY, backtrace: Kernel.caller, &block)
128
+ map(*map, to: path, backtrace: backtrace, &block)
129
+ end
130
+
131
+ # Optional version of {#to}
132
+ #
133
+ # @see #to
134
+ #
135
+ # @return [Rule::Map::Optional]
136
+ def to?(*path, map: EMPTY_ARRAY, &block)
137
+ map?(*map, to: path, &block)
73
138
  end
74
139
 
75
140
  # Iterates over the input value, passes each value
@@ -79,13 +144,13 @@ module Remap
79
144
  # @raise [ArgumentError] if no block given
80
145
  def each(&block)
81
146
  unless block
82
- raise ArgumentError, "no block given"
147
+ raise ArgumentError, "#each requires a block"
83
148
  end
84
149
 
85
150
  add Rule::Each.new(rule: call(&block))
86
151
  end
87
152
 
88
- # Wraps output in {type}
153
+ # Wraps output in type
89
154
  #
90
155
  # @param type [:array]
91
156
  #
@@ -95,7 +160,7 @@ module Remap
95
160
  # @raise [ArgumentError] if type is not :array
96
161
  def wrap(type, &block)
97
162
  unless block
98
- raise ArgumentError, "no block given"
163
+ raise ArgumentError, "#wrap requires a block"
99
164
  end
100
165
 
101
166
  add Rule::Wrap.new(type: type, rule: call(&block))
@@ -106,7 +171,11 @@ module Remap
106
171
  # Selects all elements
107
172
  #
108
173
  # @return [Rule::Path::Segment::Quantifier::All]
109
- def all
174
+ def all(&block)
175
+ if block
176
+ raise ArgumentError, "all selector does not take a block"
177
+ end
178
+
110
179
  Selector::All.new(EMPTY_HASH)
111
180
  end
112
181
 
@@ -115,7 +184,11 @@ module Remap
115
184
  # @param value [Any]
116
185
  #
117
186
  # @return [Rule::Static::Fixed]
118
- def value(value)
187
+ def value(value, &block)
188
+ if block
189
+ raise ArgumentError, "option selector does not take a block"
190
+ end
191
+
119
192
  Static::Fixed.new(value: value)
120
193
  end
121
194
 
@@ -124,26 +197,39 @@ module Remap
124
197
  # @param id [Symbol]
125
198
  #
126
199
  # @return [Rule::Static::Option]
127
- def option(id)
128
- Static::Option.new(name: id)
200
+ def option(id, backtrace: Kernel.caller, &block)
201
+ if block
202
+ raise ArgumentError, "option selector does not take a block"
203
+ end
204
+
205
+ Static::Option.new(name: id, backtrace: backtrace)
129
206
  end
130
207
 
131
- # Selects {index} element in input
208
+ # Selects index element in input
132
209
  #
133
210
  # @param index [Integer]
134
211
  #
135
212
  # @return [Path::Segment::Key]
136
213
  # @raise [ArgumentError] if index is not an Integer
137
- def at(index)
214
+ def at(index, &block)
215
+ if block
216
+ raise ArgumentError, "first selector does not take a block"
217
+ end
218
+
138
219
  Selector::Index.new(index: index)
139
220
  rescue Dry::Struct::Error
140
- raise ArgumentError, "Selector at(index) requires an integer argument, got [#{index}] (#{index.class})"
221
+ raise ArgumentError,
222
+ "Selector at(index) requires an integer argument, got [#{index}] (#{index.class})"
141
223
  end
142
224
 
143
225
  # Selects first element in input
144
226
  #
145
227
  # @return [Path::Segment::Key]]
146
- def first
228
+ def first(&block)
229
+ if block
230
+ raise ArgumentError, "first selector does not take a block"
231
+ end
232
+
147
233
  at(0)
148
234
  end
149
235
  alias any first
@@ -151,7 +237,11 @@ module Remap
151
237
  # Selects last element in input
152
238
  #
153
239
  # @return [Path::Segment::Key]
154
- def last
240
+ def last(&block)
241
+ if block
242
+ raise ArgumentError, "last selector does not take a block"
243
+ end
244
+
155
245
  at(-1)
156
246
  end
157
247