cuprum 0.7.0 → 0.8.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.
@@ -11,6 +11,7 @@ module Cuprum
11
11
  # # the value of the previous command.
12
12
  #
13
13
  # class GenerateUrlCommand
14
+ # include Cuprum::Chaining
14
15
  # include Cuprum::Processing
15
16
  #
16
17
  # private
@@ -47,6 +48,7 @@ module Cuprum
47
48
  # # the command is failing. This can be used to perform error handling.
48
49
  #
49
50
  # class CreateTaggingCommand
51
+ # include Cuprum::Chaining
50
52
  # include Cuprum::Processing
51
53
  #
52
54
  # private
@@ -111,6 +113,7 @@ module Cuprum
111
113
  # # ignored.
112
114
  #
113
115
  # class UpdatePostCommand
116
+ # include Cuprum::Chaining
114
117
  # include Cuprum::Processing
115
118
  #
116
119
  # private
@@ -148,6 +151,25 @@ module Cuprum
148
151
  # end
149
152
  # end
150
153
  #
154
+ # @example Protected Chaining Methods
155
+ # # Using the protected chaining methods #chain!, #tap_result!, and
156
+ # # #yield_result!, you can create a command class that composes other
157
+ # # commands.
158
+ #
159
+ # # We subclass the build command, which will be executed first.
160
+ # class CreateCommentCommand < BuildCommentCommand
161
+ # include Cuprum::Chaining
162
+ # include Cuprum::Processing
163
+ #
164
+ # def initialize
165
+ # # After the build step is run, we validate the comment.
166
+ # chain!(ValidateCommentCommand.new)
167
+ #
168
+ # # If the validation passes, we then save the comment.
169
+ # chain!(SaveCommentCommand.new, on: :success)
170
+ # end
171
+ # end
172
+ #
151
173
  # @see Cuprum::Command
152
174
  module Chaining
153
175
  # Creates a copy of the first command, and then chains the given command or
@@ -193,15 +215,7 @@ module Cuprum
193
215
  #
194
216
  # @yieldparam value [Object] The value of the previous result.
195
217
  def chain command = nil, on: nil, &block
196
- command ||= Cuprum::Command.new(&block)
197
-
198
- clone.tap do |fn|
199
- fn.chained_procs <<
200
- {
201
- :proc => chain_command(command),
202
- :on => on
203
- } # end hash
204
- end # tap
218
+ clone.chain!(command, :on => on, &block)
205
219
  end # method chain
206
220
 
207
221
  # Shorthand for command.chain(:on => :failure). Creates a copy of the first
@@ -223,7 +237,7 @@ module Cuprum
223
237
  #
224
238
  # @yieldparam value [Object] The value of the previous result.
225
239
  def failure command = nil, &block
226
- chain(command, :on => :failure, &block)
240
+ clone.chain!(command, :on => :failure, &block)
227
241
  end # method failure
228
242
 
229
243
  # Shorthand for command.chain(:on => :success). Creates a copy of the first
@@ -245,7 +259,7 @@ module Cuprum
245
259
  #
246
260
  # @yieldparam value [Object] The value of the previous result.
247
261
  def success command = nil, &block
248
- chain(command, :on => :success, &block)
262
+ clone.chain!(command, :on => :success, &block)
249
263
  end # method success
250
264
 
251
265
  # As #yield_result, but always returns the previous result when the block is
@@ -259,15 +273,7 @@ module Cuprum
259
273
  #
260
274
  # @see #yield_result
261
275
  def tap_result on: nil, &block
262
- tapped = ->(result) { result.tap { block.call(result) } }
263
-
264
- clone.tap do |fn|
265
- fn.chained_procs <<
266
- {
267
- :proc => tapped,
268
- :on => on
269
- } # end hash
270
- end # tap
276
+ clone.tap_result!(:on => on, &block)
271
277
  end # method tap_result
272
278
 
273
279
  # Creates a copy of the command, and then chains the block to execute after
@@ -291,17 +297,63 @@ module Cuprum
291
297
  #
292
298
  # @see #tap_result
293
299
  def yield_result on: nil, &block
294
- clone.tap do |fn|
295
- fn.chained_procs <<
296
- {
297
- :proc => block,
298
- :on => on
299
- } # end hash
300
- end # tap
300
+ clone.yield_result!(:on => on, &block)
301
301
  end # method yield_result
302
302
 
303
303
  protected
304
304
 
305
+ # @!visibility public
306
+ #
307
+ # As #chain, but modifies the current command instead of creating a clone.
308
+ # This is a protected method, and is meant to be called by the command to be
309
+ # chained, such as during #initialize.
310
+ #
311
+ # @return [Cuprum::Chaining] The current command.
312
+ #
313
+ # @see #chain
314
+ #
315
+ # @overload chain!(command, on: nil)
316
+ # @param command [Cuprum::Command] The command to chain.
317
+ #
318
+ # @param on [Symbol] Sets a condition on when the chained block can run,
319
+ # based on the previous result. Valid values are :success, :failure, and
320
+ # :always. If the value is :success, the block will be called only if
321
+ # the previous result succeeded and is not halted. If the value is
322
+ # :failure, the block will be called only if the previous result failed
323
+ # and is not halted. If the value is :always, the block will be called
324
+ # regardless of the previous result status, even if the previous result
325
+ # is halted. If no value is given, the command will run whether the
326
+ # previous command was a success or a failure, but not if the command
327
+ # chain has been halted.
328
+ #
329
+ # @overload chain!(on: nil) { |value| }
330
+ # Creates an anonymous command from the given block. The command will be
331
+ # passed the value of the previous result.
332
+ #
333
+ # @param on [Symbol] Sets a condition on when the chained block can run,
334
+ # based on the previous result. Valid values are :success, :failure, and
335
+ # :always. If the value is :success, the block will be called only if
336
+ # the previous result succeeded and is not halted. If the value is
337
+ # :failure, the block will be called only if the previous result failed
338
+ # and is not halted. If the value is :always, the block will be called
339
+ # regardless of the previous result status, even if the previous result
340
+ # is halted. If no value is given, the command will run whether the
341
+ # previous command was a success or a failure, but not if the command
342
+ # chain has been halted.
343
+ #
344
+ # @yieldparam value [Object] The value of the previous result.
345
+ def chain! command = nil, on: nil, &block
346
+ command ||= Cuprum::Command.new(&block)
347
+
348
+ chained_procs <<
349
+ {
350
+ :proc => chain_command(command),
351
+ :on => on
352
+ } # end hash
353
+
354
+ self
355
+ end # method chain!
356
+
305
357
  def chained_procs
306
358
  @chained_procs ||= []
307
359
  end # method chained_procs
@@ -310,6 +362,54 @@ module Cuprum
310
362
  yield_chain(super)
311
363
  end # method call
312
364
 
365
+ # @!visibility public
366
+ #
367
+ # As #tap_result, but modifies the current command instead of creating a
368
+ # clone. This is a protected method, and is meant to be called by the
369
+ # command to be chained, such as during #initialize.
370
+ #
371
+ # @param (see #tap_result)
372
+ #
373
+ # @yieldparam result [Cuprum::Result] The #result of the previous command.
374
+ #
375
+ # @return (see #tap_result)
376
+ #
377
+ # @see #tap_result
378
+ def tap_result! on: nil, &block
379
+ tapped = ->(result) { result.tap { block.call(result) } }
380
+
381
+ chained_procs <<
382
+ {
383
+ :proc => tapped,
384
+ :on => on
385
+ } # end hash
386
+
387
+ self
388
+ end # method tap_result!
389
+
390
+ # @!visibility public
391
+ #
392
+ # As #yield_result, but modifies the current command instead of creating a
393
+ # clone. This is a protected method, and is meant to be called by the
394
+ # command to be chained, such as during #initialize.
395
+ #
396
+ # @param (see #yield_result)
397
+ #
398
+ # @yieldparam result [Cuprum::Result] The #result of the previous command.
399
+ #
400
+ # @return (see #yield_result)
401
+ #
402
+ # @see #yield_result
403
+ def yield_result! on: nil, &block
404
+ chained_procs <<
405
+ {
406
+ :proc => block,
407
+ :on => on
408
+ } # end hash
409
+
410
+ self
411
+ end # method yield_result!
412
+
313
413
  private
314
414
 
315
415
  def chain_command command
@@ -342,7 +442,7 @@ module Cuprum
342
442
  if value_is_result?(value)
343
443
  value.to_result
344
444
  else
345
- build_result(value, :errors => build_errors)
445
+ build_result(value)
346
446
  end # if-else
347
447
  end # reduce
348
448
  end # method yield_chain
@@ -1,6 +1,5 @@
1
1
  require 'cuprum/chaining'
2
2
  require 'cuprum/processing'
3
- require 'cuprum/result_helpers'
4
3
 
5
4
  module Cuprum
6
5
  # Functional object that encapsulates a business logic operation with a
@@ -112,11 +111,9 @@ module Cuprum
112
111
  #
113
112
  # @see Cuprum::Chaining
114
113
  # @see Cuprum::Processing
115
- # @see Cuprum::ResultHelpers
116
114
  class Command
117
115
  include Cuprum::Processing
118
116
  include Cuprum::Chaining
119
- include Cuprum::ResultHelpers
120
117
 
121
118
  # Returns a new instance of Cuprum::Command.
122
119
  #
@@ -0,0 +1,276 @@
1
+ require 'cuprum'
2
+
3
+ require 'sleeping_king_studios/tools/toolbelt'
4
+
5
+ module Cuprum
6
+ # Builder class for instantiating command objects.
7
+ #
8
+ # @example
9
+ # class SpaceFactory < Cuprum::CommandFactory
10
+ # command :build, BuildCommand
11
+ #
12
+ # command :fly { |launch_site:| FlyCommand.new(launch_site) }
13
+ #
14
+ # command_class :dream { DreamCommand }
15
+ # end
16
+ #
17
+ # factory = SpaceFactory.new
18
+ #
19
+ # factory::Build #=> BuildCommand
20
+ # factory.build #=> an instance of BuildCommand
21
+ #
22
+ # rocket = factory.build.call({ size: 'big' }) #=> an instance of Rocket
23
+ # rocket.size #=> 'big'
24
+ #
25
+ # command = factory.fly(launch_site: 'KSC') #=> an instance of FlyCommand
26
+ # command.call(rocket)
27
+ # #=> launches the rocket from KSC
28
+ #
29
+ # factory::Dream #=> DreamCommand
30
+ # factory.dream #=> an instance of DreamCommand
31
+ class CommandFactory < Module
32
+ # Defines the Domain-Specific Language and helper methods for dynamically
33
+ # defined commands.
34
+ class << self
35
+ # Defines a command for the factory.
36
+ #
37
+ # @overload command(name, command_class)
38
+ # Defines a command using the given factory class. For example, when a
39
+ # command is defined with the name "whirlpool" and the WhirlpoolCommand
40
+ # class:
41
+ #
42
+ # A factory instance will define the constant ::Whirlpool, and accessing
43
+ # factory::Whirlpool will return the WhirlpoolCommand class.
44
+ #
45
+ # A factory instance will define the method #whirlpool, and calling
46
+ # factory#whirlpool will return an instance of WhirlpoolCommand. Any
47
+ # arguments passed to the #whirlpool method will be forwarded to the
48
+ # constructor when building the command.
49
+ #
50
+ # @param name [String, Symbol] The name of the command.
51
+ # @param command_class [Class] The command class. Must be a subclass of
52
+ # Cuprum::Command.
53
+ #
54
+ # @example
55
+ # class MoveFactory < Cuprum::CommandFactory
56
+ # command :cut, CutCommand
57
+ # end
58
+ #
59
+ # factory = MoveFactory.new
60
+ # factory::Cut #=> CutCommand
61
+ # factory.cut #=> an instance of CutCommand
62
+ #
63
+ # @overload command(name) { |*args| }
64
+ # Defines a command using the given block, which must return an instance
65
+ # of a Cuprum::Command subclass. For example, when a command is defined
66
+ # with the name "dive" and a block that returns an instance of the
67
+ # DiveCommand class:
68
+ #
69
+ # A factory instance will define the method #dive, and calling
70
+ # factory#dive will call the block and return the resulting command
71
+ # instance. Any arguments passed to the #dive method will be forwarded
72
+ # to the block when building the command.
73
+ #
74
+ # The block will be evaluated in the context of the factory instance, so
75
+ # it has access to any methods or instance variables defined for the
76
+ # factory instance.
77
+ #
78
+ # @param name [String, Symbol] The name of the command.
79
+ #
80
+ # @yield The block will be executed in the context of the factory
81
+ # instance.
82
+ # @yieldparam *args [Array] Any arguments given to the method
83
+ # factory.name() will be passed on the block.
84
+ # @yieldreturn [Cuprum::Command] The block return an instance of a
85
+ # Cuprum::Command subclass, or else raise an error.
86
+ #
87
+ # @example
88
+ # class MoveFactory < Cuprum::CommandFactory
89
+ # command :fly { |destination| FlyCommand.new(destination) }
90
+ # end
91
+ #
92
+ # factory = MoveFactory.new
93
+ # factory.fly_command('Indigo Plateau')
94
+ # #=> an instance of FlyCommand with a destination of 'Indigo Plateau'
95
+ def command(name, klass = nil, **metadata, &defn)
96
+ guard_abstract_factory!
97
+
98
+ if klass
99
+ define_command_from_class(klass, name: name, metadata: metadata)
100
+ elsif block_given?
101
+ define_command_from_block(defn, name: name, metadata: metadata)
102
+ else
103
+ require_definition!
104
+ end
105
+ end
106
+
107
+ # Defines a command using the given block, which must return a subclass of
108
+ # Cuprum::Command. For example, when a command is defined with the name
109
+ # "rock_climb" and a block returning a subclass of RockClimbCommand:
110
+ #
111
+ # A factory instance will define the constant ::RockClimb, and accessing
112
+ # factory::RockClimb will call the block and return the resulting command
113
+ # class. This value is memoized, so subsequent factory::RockClimb accesses
114
+ # on the same factory instance will return the same command class.
115
+ #
116
+ # A factory instance will define the method #rock_climb, and calling
117
+ # factory#rock_climb will access the constant at ::RockClimb and return an
118
+ # instance of that subclass of RockClimbCommand. Any arguments passed to
119
+ # the #whirlpool method will be forwarded to the constructor when building
120
+ # the command.
121
+ #
122
+ # @param name [String, Symbol] The name of the command.
123
+ # @yield The block will be executed in the context of the factory
124
+ # instance.
125
+ # @yieldparam *args [Array] Any arguments given to the method
126
+ # factory.name() will be passed on the block.
127
+ # @yieldreturn [Cuprum::Command] The block return an instance of a
128
+ # Cuprum::Command subclass, or else raise an error.
129
+ #
130
+ # @example
131
+ # class MoveFactory < Cuprum::CommandFactory
132
+ # command_class :flash do
133
+ # Class.new(FlashCommand) do
134
+ # def brightness
135
+ # :intense
136
+ # end
137
+ # end
138
+ # end
139
+ # end
140
+ #
141
+ # factory = MoveFactory.new
142
+ # factory::Flash #=> a subclass of FlashCommand
143
+ # factory.flash #=> an instance of factory::Flash
144
+ #
145
+ # command = factory.flash
146
+ # command.brightness #=> :intense
147
+ def command_class(name, **metadata, &defn)
148
+ guard_abstract_factory!
149
+
150
+ raise ArgumentError, 'must provide a block'.freeze unless block_given?
151
+
152
+ name = normalize_command_name(name)
153
+
154
+ (@command_definitions ||= {})[name] =
155
+ metadata.merge(__const_defn__: defn)
156
+
157
+ const_name = tools.string.camelize(name)
158
+
159
+ define_method(name) do |*args, &block|
160
+ command_class = const_get(const_name)
161
+
162
+ build_command(command_class, *args, &block)
163
+ end
164
+ end
165
+
166
+ protected
167
+
168
+ def command_definitions
169
+ definitions = (@command_definitions ||= {})
170
+
171
+ return definitions unless superclass < Cuprum::CommandFactory
172
+
173
+ superclass.command_definitions.merge(definitions)
174
+ end
175
+
176
+ private
177
+
178
+ def abstract_factory?
179
+ self == Cuprum::CommandFactory
180
+ end
181
+
182
+ def define_command_from_block(builder, name:, metadata: {})
183
+ command_name = normalize_command_name(name)
184
+
185
+ (@command_definitions ||= {})[command_name] = metadata
186
+
187
+ define_method(command_name) do |*args|
188
+ instance_exec(*args, &builder)
189
+ end
190
+ end
191
+
192
+ def define_command_from_class(command_class, name:, metadata: {})
193
+ guard_invalid_definition!(command_class)
194
+
195
+ command_name = normalize_command_name(name)
196
+
197
+ (@command_definitions ||= {})[command_name] =
198
+ metadata.merge(__const_defn__: command_class)
199
+
200
+ define_method(command_name) do |*args, &block|
201
+ build_command(command_class, *args, &block)
202
+ end
203
+ end
204
+
205
+ def guard_abstract_factory!
206
+ return unless abstract_factory?
207
+
208
+ raise NotImplementedError,
209
+ 'Cuprum::CommandFactory is an abstract class. Create a subclass to ' \
210
+ 'define commands for a factory.'.freeze
211
+ end
212
+
213
+ def guard_invalid_definition!(command_class)
214
+ return if command_class.is_a?(Class) && command_class < Cuprum::Command
215
+
216
+ raise ArgumentError, 'definition must be a command class'.freeze
217
+ end
218
+
219
+ def normalize_command_name(command_name)
220
+ tools.string.underscore(command_name).intern
221
+ end
222
+
223
+ def require_definition!
224
+ raise ArgumentError, 'must provide a command class or a block'.freeze
225
+ end
226
+
227
+ def tools
228
+ SleepingKingStudios::Tools::Toolbelt.instance
229
+ end
230
+ end
231
+
232
+ # @return [Boolean] true if the factory defines the given command, otherwise
233
+ # false.
234
+ def command?(command_name)
235
+ command_name = normalize_command_name(command_name)
236
+
237
+ commands.include?(command_name)
238
+ end
239
+
240
+ # @return [Array<Symbol>] a list of the commands defined by the factory.
241
+ def commands
242
+ self.class.send(:command_definitions).keys
243
+ end
244
+
245
+ # @private
246
+ def const_defined?(const_name, inherit = true)
247
+ command?(const_name) || super
248
+ end
249
+
250
+ # @private
251
+ def const_missing(const_name)
252
+ definitions = self.class.send(:command_definitions)
253
+ command_name = normalize_command_name(const_name)
254
+ command_defn = definitions.dig(command_name, :__const_defn__)
255
+
256
+ return super unless command_defn
257
+
258
+ command_class =
259
+ command_defn.is_a?(Proc) ? instance_exec(&command_defn) : command_defn
260
+
261
+ const_set(const_name, command_class)
262
+
263
+ command_class
264
+ end
265
+
266
+ private
267
+
268
+ def build_command(command_class, *args, &block)
269
+ command_class.new(*args, &block)
270
+ end
271
+
272
+ def normalize_command_name(command_name)
273
+ self.class.send(:normalize_command_name, command_name)
274
+ end
275
+ end
276
+ end