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.
- 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:
|