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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +106 -1
- data/Rakefile +2 -0
- data/doc/ActionCommand/Executable.html +452 -29
- data/doc/ActionCommand/InputOutput.html +559 -158
- data/doc/ActionCommand/Result.html +321 -32
- data/doc/ActionCommand.html +319 -65
- data/doc/_index.html +16 -1
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +101 -2
- data/doc/index.html +101 -2
- data/doc/method_list.html +118 -16
- data/doc/top-level-namespace.html +1 -1
- data/lib/action_command/executable.rb +75 -0
- data/lib/action_command/input_output.rb +149 -0
- data/lib/action_command/result.rb +76 -0
- data/lib/action_command/utils.rb +21 -0
- data/lib/action_command/version.rb +1 -1
- data/lib/action_command.rb +35 -186
- metadata +6 -2
@@ -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
|
data/lib/action_command.rb
CHANGED
@@ -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
|
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
|
-
|
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.
|
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-
|
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:
|