action_command 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: