action_command 0.1.0 → 0.1.1

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.
@@ -0,0 +1,149 @@
1
+
2
+ module ActionCommand
3
+
4
+ # A static description of the input and output from a given command. Although
5
+ # adding this adds a bunch of documentation and validation, it is not required.
6
+ # If you don't want to specify your input and output, you can just access the hash
7
+ # you passed into the command as @params
8
+ class InputOutput
9
+ # shorthand to indicate the parameter is optional.
10
+ OPTIONAL = { optional: true }.freeze
11
+
12
+ # Do not use this. Instead, implment self.describe_io in your command subclass, and
13
+ # call the method ActionCommand#self.describe_io from within it, returning its result.
14
+ def initialize(action, desc)
15
+ @action = action
16
+ @desc = desc
17
+ @input = []
18
+ @output = []
19
+
20
+ # universal parameters.
21
+ # input(:help, 'Help on this command', OPTIONAL)
22
+ end
23
+
24
+ # @param dest [ActionCommand::Executable] the executable in question
25
+ # @return true if the executable is not in a testing context.
26
+ def should_validate(dest)
27
+ return dest.test_context?
28
+ end
29
+
30
+ # Validates that the specified parameters are valid for this input description.
31
+ # @param args [Hash] the arguments to validate
32
+ def validate_input(dest, args)
33
+ return true unless should_validate(dest)
34
+ @input.each do |p|
35
+ val = args[p[:symbol]]
36
+
37
+ # if the argument has a value, no need to test whether it is optional.
38
+ next unless !val || val == '*' || val == ''
39
+
40
+ opts = p[:opts]
41
+ unless opts[:optional]
42
+ raise ArgumentError, "You must specify the required input #{p[:symbol]}"
43
+ end
44
+ end
45
+ return true
46
+ end
47
+
48
+ # Goes through, and assigns the value for each declared parameter to an accessor
49
+ # with the same name, validating that required parameters are not missing
50
+ def process_input(dest, args)
51
+ # pass down predefined attributes.
52
+ dest.parent = args[:parent]
53
+ dest.test = args[:test]
54
+
55
+ return unless validate_input(dest, args)
56
+
57
+ @input.each do |param|
58
+ sym = param[:symbol]
59
+ if args.key? sym
60
+ sym_assign = "#{sym}=".to_sym
61
+ dest.send(sym_assign, args[sym])
62
+ end
63
+ end
64
+ end
65
+
66
+ # Goes through, and makes sure that required output parameters exist
67
+ def process_output(dest, result)
68
+ return unless should_validate(dest)
69
+
70
+ @output.each do |param|
71
+ sym = param[:symbol]
72
+ unless result.key?(sym)
73
+ opts = param[:opts]
74
+ raise ArgumentError, "Missing required value #{sym} in output" unless opts[:optional]
75
+ end
76
+ end
77
+ end
78
+
79
+ # convert rake task arguments to a standard hash.
80
+ def rake_input(rake_arg)
81
+ params = {}
82
+ rake_arg.each do |key, val|
83
+ params[key] = val
84
+ end
85
+ return params
86
+ end
87
+
88
+ # print out the defined output of the command
89
+ def print_output(result)
90
+ @output.each do |param|
91
+ sym = param[:symbol]
92
+ puts "#{sym}: #{result[sym]}"
93
+ end
94
+
95
+ end
96
+
97
+ # Returns the description for this command.
98
+ attr_reader :desc
99
+
100
+
101
+ def help?(args)
102
+ first_arg_sym = @input.first[:symbol]
103
+ first_arg_val = args[first_arg_sym]
104
+ return first_arg_val == 'help'
105
+ end
106
+
107
+ # displays the help for this command
108
+ def show_help
109
+ puts "#{@action.name}: #{desc}"
110
+ print_params('Input', @input)
111
+ print_params('Output', @output)
112
+ end
113
+
114
+
115
+ # Defines input for a command
116
+ # @param sym [Symbol] symbol identifying the parameter
117
+ # @param desc [String] description for use by internal developers, or on a rake task with
118
+ # rake your_task_name[help]
119
+ # @param opts Optional arguments.
120
+ def input(sym, desc, opts = {}, &_block)
121
+ insert_io(@input, sym, desc, opts)
122
+ end
123
+
124
+ def output(sym, desc, opts = {})
125
+ insert_io(@output, sym, desc, opts)
126
+ end
127
+
128
+ # @return an array with the set of parameter symbols this command accepts.
129
+ def keys
130
+ @input.collect { |p| p[:symbol] }
131
+ end
132
+
133
+ private
134
+
135
+ def print_params(title, vals)
136
+ puts " #{title}: "
137
+ vals.each do |p|
138
+ out = " #{p[:symbol]}: #{p[:desc]}"
139
+ out << ' (optional)' if p[:opts][:optional]
140
+ puts out
141
+ end
142
+ end
143
+
144
+ def insert_io(dest, sym, desc, opts)
145
+ dest << { symbol: sym, desc: desc, opts: opts }
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,76 @@
1
+
2
+ module ActionCommand
3
+ # The result of one or more commands being executed.
4
+ class Result
5
+ # By default, a command is ok?
6
+ def initialize(logger)
7
+ @ok = true
8
+ @values = [{}]
9
+ @logger = logger
10
+ end
11
+
12
+ # Call this if your command implementation fails. Sets
13
+ # ok? to false on the result.
14
+ # @param msg [String] message describing the failure.
15
+ def failed(msg)
16
+ @ok = false
17
+ error(msg)
18
+ end
19
+
20
+ # @return [Boolean] true, up until failed has been called at least once.
21
+ def ok?
22
+ return @ok
23
+ end
24
+
25
+ # adds results under the subkey until pop is called
26
+ def push(key)
27
+ return unless key
28
+ old_cur = current
29
+ if old_cur.key?(key)
30
+ @values << old_cur[key]
31
+ else
32
+ @values << {}
33
+ old_cur[key] = @values.last
34
+ end
35
+ end
36
+
37
+ # removes the current set of results from the stack.
38
+ def pop(key)
39
+ return unless key
40
+ @values.pop
41
+ end
42
+
43
+ # returns the current hash of values we are operating on.
44
+ def current
45
+ return @values.last
46
+ end
47
+
48
+ # Assign some kind of a return value for use by the caller.
49
+ def []=(key, val)
50
+ current[key] = val
51
+ end
52
+
53
+ # determine if a key exists in the result.
54
+ def key?(key)
55
+ return current.key?(key)
56
+ end
57
+
58
+ # Return a value return by the command
59
+ def [](key)
60
+ return current[key]
61
+ end
62
+
63
+ # display an informational message to the logger, if there is one.
64
+ def info(msg)
65
+ @logger.info(msg) if @logger
66
+ end
67
+
68
+ protected
69
+
70
+ # display an error message to the logger, if there is one.
71
+ def error(msg)
72
+ @logger.error(msg) if @logger
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module ActionCommand
3
+
4
+ # class with utilities for working with action_commands
5
+ class Utils
6
+ # Used for cases where you might want to pass an action a User object, or
7
+ # the integer ID of a user object, or the unique email of a user object, and
8
+ # have the command operate on the user object.
9
+
10
+ # Converts an item into an object as follows:
11
+ # 1. If item is an object of cls, then returns it
12
+ # 2. If item is an integer, then assumes its and id and returns cls.find(item)
13
+ # 3. Otherwise, executes the code block and passes it item.
14
+ def self.find_object(cls, item)
15
+ return item if item.is_a? cls
16
+ return cls.find(item) if item.is_a? Integer
17
+ return yield(item)
18
+ end
19
+ end
20
+
21
+ end
@@ -1,4 +1,4 @@
1
1
  module ActionCommand
2
2
  # Version of this Gem
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.1.1'.freeze
4
4
  end
@@ -1,8 +1,14 @@
1
1
  require 'action_command/version'
2
+ require 'action_command/result'
3
+ require 'action_command/input_output'
4
+ require 'action_command/executable'
5
+ require 'action_command/utils'
2
6
 
3
7
  # To use action command, create subclasses of ActionCommand::Executable
4
8
  # and run them using the ActionCommand.execute_... variants.
5
9
  module ActionCommand
10
+ # 5. Begin adding documentation for how to use it.
11
+
6
12
  # Used as root parent of command if we are in a testing context.
7
13
  CONTEXT_TEST = :test
8
14
 
@@ -11,6 +17,9 @@ module ActionCommand
11
17
 
12
18
  # Used as root parent of command if we are executing it from rails (a controller, etc)
13
19
  CONTEXT_RAILS = :rails
20
+
21
+ # Used to create an optional parameter in describe_io
22
+ OPTIONAL = { optional: true }.freeze
14
23
 
15
24
  @@logger = nil # rubocop:disable Style/ClassVars
16
25
  @@params = {} # rubocop:disable Style/ClassVars
@@ -42,19 +51,27 @@ module ActionCommand
42
51
 
43
52
  # Execute a command at the root level of a rake task context.
44
53
  # @param cls [ActionCommand::Executable] The class of an Executable subclass
45
- # @param args [Hash] parameters used by the command.
54
+ # @param args parameters used by the command.
46
55
  # @return [ActionCommand::Result]
47
- def self.execute_rake(cls, args = {})
56
+ def self.execute_rake(cls, args)
48
57
  io = cls.describe_io
49
58
  if io.help? args
50
59
  io.show_help
51
60
  return
52
61
  end
53
-
54
- return unless io.validate(args)
55
62
 
56
63
  result = create_result
57
- return ActionCommand.create_and_execute(cls, args, CONTEXT_RAKE, result)
64
+ ActionCommand.create_and_execute(cls, io.rake_input(args), CONTEXT_RAKE, result)
65
+ io.print_output(result)
66
+ return result
67
+ end
68
+
69
+ # Install a command as a rake task in a
70
+ def self.install_rake(rake, sym, cls, deps)
71
+ rake.send(:desc, cls.describe_io.desc)
72
+ rake.send(:task, sym, cls.describe_io.keys => deps) do |_t, args|
73
+ ActionCommand.execute_rake(cls, args)
74
+ end
58
75
  end
59
76
 
60
77
  # Execute a command at the root level of a rails context
@@ -66,6 +83,19 @@ module ActionCommand
66
83
  return ActionCommand.create_and_execute(cls, params, CONTEXT_RAILS, result)
67
84
  end
68
85
 
86
+ # Execute a child command, placing its results under the specified subkey
87
+ # @param parent [ActionCommand::Executable] An instance of the parent command
88
+ # @param cls [ActionCommand::Executable] The class of an Executable subclass
89
+ # @param result [ActionCommand::Result] The result to populate
90
+ # @param result_key [Symbo] a key to place the results under, or nil if you want
91
+ # the result stored directly on the current results object.
92
+ # @param params [Hash] parameters used by the command.
93
+ # @return [ActionCommand::Result]
94
+ def self.execute_child(parent, cls, result, result_key, params = {})
95
+ result.push(result_key)
96
+ ActionCommand.create_and_execute(cls, params, parent, result)
97
+ result.pop(result_key)
98
+ end
69
99
 
70
100
  # Create a global description of the inputs and outputs of a command. Should
71
101
  # usually be called within an ActionCommand::Executable subclass in its
@@ -93,185 +123,4 @@ module ActionCommand
93
123
  action = cls.new(params)
94
124
  return action.execute(result)
95
125
  end
96
-
97
- # The result of one or more commands being executed.
98
- class Result
99
-
100
- # By default, a command is ok?
101
- def initialize(logger)
102
- @ok = true
103
- @values = {}
104
- @logger = logger
105
- end
106
-
107
- # Call this if your command implementation fails. Sets
108
- # ok? to false on the result.
109
- # @param msg [String] message describing the failure.
110
- def failed(msg)
111
- @ok = false
112
- error(msg)
113
- end
114
-
115
- # @return [Boolean] true, up until failed has been called at least once.
116
- def ok?
117
- return @ok
118
- end
119
-
120
- # Assign some kind of a return value for use by the caller.
121
- def []=(key, val)
122
- @values[key] = val
123
- end
124
-
125
- # Return a value return by the command
126
- def [](key)
127
- return @values[key]
128
- end
129
-
130
- # display an informational message to the logger, if there is one.
131
- def info(msg)
132
- @logger.info(msg) if @logger
133
- end
134
-
135
- protected
136
-
137
- # display an error message to the logger, if there is one.
138
- def error(msg)
139
- @logger.error(msg) if @logger
140
- end
141
-
142
- end
143
-
144
- # A static description of the input and output from a given command. Although
145
- # adding this adds a bunch of documentation and validation, it is not required.
146
- # If you don't want to specify your input and output, you can just access the hash
147
- # you passed into the command as @params
148
- class InputOutput
149
- # shorthand to indicate the parameter is optional.
150
- OPTIONAL = { optional: true }.freeze
151
-
152
- # Do not use this. Instead, implment self.describe_io in your command subclass, and
153
- # call the method ActionCommand#self.describe_io from within it, returning its result.
154
- def initialize(action, desc)
155
- @action = action
156
- @desc = desc
157
- @params = []
158
-
159
- # universal parameters.
160
- input(:help, 'Help on this command', OPTIONAL)
161
- input(:test,
162
- 'Optional rspec context for performing validations via rspec_validate',
163
- OPTIONAL)
164
- input(:parent, 'Reference to the parent of this command, a symbol at the root', OPTIONAL)
165
- end
166
-
167
- # Validates that the specified parameters are valid for this input description.
168
- # @param args [Hash] the arguments to validate
169
- def validate(args)
170
- @params.each do |p|
171
- val = args[p[:symbol]]
172
-
173
- # if the argument has a value, no need to test whether it is optional.
174
- next unless !val || val == '*' || val == ''
175
-
176
- opts = p[:opts]
177
- unless opts[:optional]
178
- raise ArgumentError, "You must specify the required input #{p[:symbol]}"
179
- end
180
- end
181
- return true
182
- end
183
-
184
- # Goes through, and assigns the value for each declared parameter to an accessor
185
- # with the same name.
186
- def assign_args(dest, args)
187
- # handle aliasing
188
- if validate(args)
189
- @params.each do |param|
190
- sym = param[:symbol]
191
- if args.key? sym
192
- sym_assign = "#{sym}=".to_sym
193
- dest.send(sym_assign, args[sym])
194
- end
195
- end
196
- end
197
- end
198
-
199
- # Returns the description for this command.
200
- def description
201
- @desc
202
- end
203
-
204
-
205
- def help?(args)
206
- first_arg_sym = @params.first[:symbol]
207
- first_arg_val = args[first_arg_sym]
208
- return first_arg_val == 'help'
209
- end
210
-
211
- # displays the help for this command
212
- def show_help
213
- puts "#{@action.name}: #{description}"
214
- @params.each do |p|
215
- puts " #{p[:symbol]}: #{p[:desc]}"
216
- end
217
- end
218
-
219
- # Defines input for a command
220
- # @param sym [Symbol] symbol identifying the parameter
221
- # @param desc [String] description for use by internal developers, or on a rake task with
222
- # rake your_task_name[help]
223
- # @param opts Optional arguments.
224
- def input(sym, desc, opts = {})
225
- @params.insert(0, symbol: sym, desc: desc, opts: opts)
226
- end
227
-
228
- # @return an array with the set of parameter symbols this command accepts.
229
- def keys
230
- @params.collect { |p| p[:symbol] }
231
- end
232
- end
233
-
234
-
235
- # Root class for action commands that can be executed by this library.
236
- # Override execute_internal to implement one, call one of the variants
237
- # of ActionCommand.execute_... to execute one.
238
- class Executable
239
-
240
- attr_accessor :parent, :test
241
-
242
- # Do not call new directly, instead use ActionCommand#execute_... variants.
243
- def initialize(args)
244
- self.class.describe_io.assign_args(self, args)
245
- end
246
-
247
- # Execute the logic of a command. Should not usually be called
248
- # directly. Command executors should call one of the ActionCommand.execute_...
249
- # variants. Command implementors should override
250
- # execute_internal.
251
- # @return [ActionCommand::Result]
252
- def execute(result)
253
- execute_internal(result)
254
- return result
255
- end
256
-
257
- # Call this within a commands execution if you'd like to perform validations
258
- # within the testing context.
259
- # @yield [context] Yields back the testing context that you
260
- # passed in to ActionCommand#execute_test.
261
- def testing
262
- yield @test if @test
263
- end
264
-
265
- protected
266
-
267
- # @!visibility public
268
- # Override this method to implement the logic of your command
269
- # @param result [ActionCommand::Result] a result object where you can store
270
- # the results of your logic, or indicate that the command failed.
271
- def execute_internal(result)
272
-
273
- end
274
-
275
- end
276
-
277
126
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_command
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Jones
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-13 00:00:00.000000000 Z
11
+ date: 2016-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -235,6 +235,10 @@ files:
235
235
  - doc/method_list.html
236
236
  - doc/top-level-namespace.html
237
237
  - lib/action_command.rb
238
+ - lib/action_command/executable.rb
239
+ - lib/action_command/input_output.rb
240
+ - lib/action_command/result.rb
241
+ - lib/action_command/utils.rb
238
242
  - lib/action_command/version.rb
239
243
  homepage: https://github.com/chrisjones-tripletri/action_command
240
244
  licenses: