action_command 0.1.3 → 0.1.4

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.
@@ -103,7 +103,7 @@
103
103
  </div>
104
104
 
105
105
  <div id="footer">
106
- Generated on Fri Mar 4 10:20:14 2016 by
106
+ Generated on Mon Mar 7 14:27:17 2016 by
107
107
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
108
108
  0.8.7.6 (ruby-2.2.3).
109
109
  </div>
@@ -34,6 +34,12 @@ module ActionCommand
34
34
  def rake_context?
35
35
  return root_context == ActionCommand::CONTEXT_RAKE
36
36
  end
37
+
38
+ # @return true if this command was executed using ActionCommand.execute_api
39
+ def api_context?
40
+ return root_context == ActionCommand::CONTEXT_API
41
+ end
42
+
37
43
 
38
44
  # @return true if this command is a child of another command
39
45
  def child_context?
@@ -0,0 +1,27 @@
1
+ module ActionCommand
2
+ # Root class for action commands that can be executed by this library.
3
+ # Override execute_internal to implement one, call one of the variants
4
+ # of ActionCommand.execute_... to execute one.
5
+ class ExecutableTransaction < Executable
6
+
7
+ # starts a transaction only if we are not already within one.
8
+ def execute(result)
9
+ if ActiveRecord::Base.connection.open_transactions >= 1
10
+ super(result)
11
+ else
12
+ result.info('start_transaction')
13
+ ActiveRecord::Base.transaction do
14
+ super(result)
15
+ if result.ok?
16
+ result.info('end_transaction')
17
+ else
18
+ result.info('rollback_transaction')
19
+ raise ActiveRecord::Rollback, 'rollback transaction'
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -126,6 +126,11 @@ module ActionCommand
126
126
  insert_io(@input, sym, desc, opts)
127
127
  end
128
128
 
129
+ # Defines output for a command
130
+ # @param sym [Symbol] symbol identifying the parameter
131
+ # @param desc [String] description for use by internal developers, or on a rake task with
132
+ # rake your_task_name[help]
133
+ # @param opts Optional arguments.
129
134
  def output(sym, desc, opts = {})
130
135
  insert_io(@output, sym, desc, opts)
131
136
  end
@@ -0,0 +1,105 @@
1
+
2
+ # Unnecessary comment for rubocop
3
+ module ActionCommand
4
+
5
+ # A single entry in the action command log
6
+ class LogMessage
7
+
8
+ attr_accessor :sequence, :cmd, :kind, :msg, :key
9
+
10
+ # Create a new log message
11
+ def populate(line, msg)
12
+ @line = line
13
+ @sequence = msg['sequence']
14
+ @depth = msg['depth']
15
+ @cmd = msg['cmd']
16
+ @kind = msg['kind']
17
+ @msg = msg['msg']
18
+ @key = msg['key']
19
+ end
20
+
21
+ # @return the number of parents the current command has
22
+ def depth
23
+ return @depth
24
+ end
25
+
26
+ # @return true if this command is the root command
27
+ def root?
28
+ return @depth == 0
29
+ end
30
+
31
+ # @return the line that was used to create this message.
32
+ def line
33
+ return @line
34
+ end
35
+
36
+ def key?(key)
37
+ return @key == key
38
+ end
39
+
40
+ # @return true if the kinds equal (tolerant of string/symbol mismatch)
41
+ def kind?(kind)
42
+ kind = kind.to_s
43
+ return @kind == kind
44
+ end
45
+
46
+ # @return true if the cmds equal (tolerant of being passed a class)
47
+ def command?(cmd)
48
+ cmd = cmd.name if cmd.is_a? Class
49
+ return @cmd == cmd
50
+ end
51
+
52
+ # @ return true if msgs equal
53
+ def match_message?(msg)
54
+ return @msg == msg unless msg.is_a? Hash
55
+ msg.each do |k, v|
56
+ k = k.to_s if k.is_a? Symbol
57
+ return false unless @msg.key?(k)
58
+ return false unless @msg[k] == v
59
+ end
60
+ return true
61
+ end
62
+ end
63
+
64
+ # reads from a stream containing log statements, and returns
65
+ # LogMessage entries for them.
66
+ class LogParser
67
+
68
+ # Create a new log parser for an IO subclass
69
+ def initialize(stream, sequence = nil)
70
+ @stream = stream
71
+ @sequence = sequence
72
+ end
73
+
74
+ # Check if we have reached the end of the stream.
75
+ def eof?
76
+ return @stream.eof?
77
+ end
78
+
79
+ # Populates a message from the next line in the
80
+ def next(msg)
81
+ # be tolerant of the fact that there might be other
82
+ # stuff in the log file.
83
+ next_line do |input, line|
84
+ if input.key?('sequence')
85
+ msg.populate(line, input) unless @sequence && @sequence != input['sequence']
86
+ return true
87
+ end
88
+ end
89
+ return false
90
+ end
91
+
92
+ private
93
+
94
+ def next_line
95
+ until @stream.eof?
96
+ line = @stream.readline
97
+ line.scan(/--\s+:\s+({.*})/) do |item|
98
+ input = JSON.parse(item[0])
99
+ yield input, line
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,114 @@
1
+
2
+ module ActionCommand
3
+
4
+ # Action that parses a log and pretty prints the log,
5
+ # optionally filtering on a particular command sequence.
6
+ class PrettyPrintLogAction < Executable
7
+ attr_accessor :source, :dest, :sequence
8
+
9
+ # specifies the input/output for this command
10
+ def self.describe_io
11
+ return ActionCommand.describe_io(self, 'Command that does some logging') do |io|
12
+ io.input(:source, 'Stream to read')
13
+ io.input(:sequence, 'sequence to filter on', OPTIONAL)
14
+ io.input(:dest, 'Optional output stream, defaults to STDOUT', OPTIONAL)
15
+ end
16
+ end
17
+
18
+ # return the destination stream, default to STDOUT
19
+ def dest
20
+ @dest = STDOUT unless @dest
21
+ return @dest
22
+ end
23
+
24
+ protected
25
+
26
+
27
+ # Say hello to the specified person.
28
+ def execute_internal(_result)
29
+ item = LogMessage.new
30
+ parser = LogParser.new(@source, @sequence)
31
+ sequences = {}
32
+ # keep track of sequences, and when you complete one, then print out the
33
+ # entire thing at once.
34
+ while parser.next(item)
35
+ if item.kind?(ActionCommand::LOG_KIND_COMMAND_OUTPUT) && item.root?
36
+ process_output(sequences, item)
37
+ else
38
+ process_other(sequences, item)
39
+ end
40
+ item = LogMessage.new
41
+ end
42
+
43
+ # print out any incomplete sequences
44
+ print_sequences(sequences)
45
+ end
46
+
47
+ def process_other(sequences, item)
48
+ sequences[item.sequence] = [] unless sequences.key?(item.sequence)
49
+ sequences[item.sequence] << item
50
+ end
51
+
52
+ def process_output(sequences, item)
53
+ seq = sequences[item.sequence]
54
+ sequences.delete(item.sequence)
55
+ seq << item
56
+ print_sequence(seq)
57
+ end
58
+
59
+ def print_sequences(sequences)
60
+ sequences.each do |_k, v|
61
+ print_sequence(v)
62
+ end
63
+ end
64
+
65
+ def print_sequence(sequence)
66
+ sequence.each_with_index do |item, _i|
67
+ print_sequence_item(item)
68
+ end
69
+ end
70
+
71
+ def print_sequence_item(item)
72
+ # indent
73
+ if item.kind?(ActionCommand::LOG_KIND_COMMAND_INPUT)
74
+ print_cmd_input(item)
75
+ elsif item.kind?(ActionCommand::LOG_KIND_COMMAND_OUTPUT)
76
+ print_cmd_output(item)
77
+ else
78
+ print_msg(item.depth + 1, item)
79
+ end
80
+ end
81
+
82
+ def print_cmd_output(item)
83
+ println(item.depth, 'output:')
84
+ print_msg(item.depth + 1, item)
85
+ end
86
+
87
+ def print_cmd_input(item)
88
+ result = item.cmd
89
+ result << " (#{item.sequence})" if item.root?
90
+ println(item.depth, result)
91
+ println(item.depth + 1, 'input:')
92
+ print_msg(item.depth + 2, item)
93
+ end
94
+
95
+ def print_msg(depth, item)
96
+ if item.msg.is_a? String
97
+ println(depth, item.msg)
98
+ return
99
+ end
100
+
101
+ item.msg.each do |k, v|
102
+ println(depth, "#{k}: #{v}")
103
+ end
104
+ end
105
+
106
+ def println(depth, line)
107
+ padding = ''.rjust(depth * 2)
108
+ dest.puts("#{padding}#{line}")
109
+ end
110
+
111
+
112
+ end
113
+
114
+ end
@@ -1,29 +1,79 @@
1
1
 
2
2
  module ActionCommand
3
+
4
+
3
5
  # The result of one or more commands being executed.
4
- class Result
6
+ class Result # rubocop:disable Metrics/ClassLength
5
7
  # By default, a command is ok?
6
- def initialize(logger)
7
- @ok = true
8
+ def initialize
9
+ @result_code = RESULT_CODE_OK
8
10
  @values = [{}]
11
+ @logger = nil
12
+ end
13
+
14
+ # set the logger for this result
15
+ def logger=(logger)
16
+ return unless logger
17
+ @sequence = SecureRandom.hex
18
+ @stack = []
9
19
  @logger = logger
10
20
  end
21
+
22
+ # @return true if logging is enabled.
23
+ def logging?
24
+ return !@logger.nil?
25
+ end
26
+
27
+ # display an debugging message to the logger, if there is one.
28
+ # @yield return a message or hash
29
+ def debug(msg = nil)
30
+ if @logger
31
+ json = build_log(msg || yield, ActionCommand::LOG_KIND_DEBUG)
32
+ @logger.info(json)
33
+ end
34
+ end
35
+
36
+ # display an informational message to the logger, if there is one.
37
+ # @yield return a message or hash
38
+ def info(msg = nil)
39
+ if @logger
40
+ json = build_log(msg || yield, ActionCommand::LOG_KIND_INFO)
41
+ @logger.info(json)
42
+ end
43
+ end
44
+
45
+ # display an error message to the logger, if there is one.
46
+ def error(msg)
47
+ @logger.error(build_log(msg, ActionCommand::LOG_KIND_ERROR)) if @logger
48
+ end
11
49
 
12
50
  # Call this if your command implementation fails. Sets
13
51
  # ok? to false on the result.
14
52
  # @param msg [String] message describing the failure.
15
53
  def failed(msg)
16
- @ok = false
54
+ @result_code = RESULT_CODE_FAILED
55
+ error(msg)
56
+ end
57
+
58
+ # Call this if your command implementation fails. Sets
59
+ # ok? to false on the result.
60
+ # @param msg [String] message describing the failure.
61
+ # @param result_code [Integer]
62
+ def failed_with_code(msg, result_code)
63
+ @result_code = result_code
17
64
  error(msg)
18
65
  end
19
66
 
20
67
  # @return [Boolean] true, up until failed has been called at least once.
21
68
  def ok?
22
- return @ok
69
+ return @result_code == RESULT_CODE_OK
23
70
  end
24
71
 
72
+ # @return [Integer] the current result code
73
+ attr_reader :result_code
74
+
25
75
  # adds results under the subkey until pop is called
26
- def push(key)
76
+ def push(key, cmd)
27
77
  return unless key
28
78
  old_cur = current
29
79
  if old_cur.key?(key)
@@ -32,12 +82,14 @@ module ActionCommand
32
82
  @values << {}
33
83
  old_cur[key] = @values.last
34
84
  end
85
+ @stack << { key: key, cmd: cmd } if @logger
35
86
  end
36
87
 
37
88
  # removes the current set of results from the stack.
38
89
  def pop(key)
39
90
  return unless key
40
91
  @values.pop
92
+ @stack.pop if @logger
41
93
  end
42
94
 
43
95
  # returns the current hash of values we are operating on.
@@ -50,6 +102,11 @@ module ActionCommand
50
102
  current[key] = val
51
103
  end
52
104
 
105
+ # return the unique sequence id for the commands under this result
106
+ def sequence
107
+ return @sequence
108
+ end
109
+
53
110
  # determine if a key exists in the result.
54
111
  def key?(key)
55
112
  return current.key?(key)
@@ -59,18 +116,51 @@ module ActionCommand
59
116
  def [](key)
60
117
  return current[key]
61
118
  end
119
+
120
+ # Used internally to log the input parameters to a command
121
+ def log_input(params)
122
+ return unless @logger
123
+ output = params.reject { |k, _v| internal_key?(k) }
124
+ log_info_hash(output, ActionCommand::LOG_KIND_COMMAND_INPUT)
125
+ end
62
126
 
63
- # display an informational message to the logger, if there is one.
64
- def info(msg)
65
- @logger.info(msg) if @logger
127
+ # Used internally to log the output parameters for a command.
128
+ def log_output
129
+ return unless @logger
130
+ # only log the first level parameters, subcommands will log
131
+ # their own output.
132
+ output = current.reject { |k, v| v.is_a?(Hash) || internal_key?(k) }
133
+ log_info_hash(output, ActionCommand::LOG_KIND_COMMAND_OUTPUT)
66
134
  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
135
+
136
+ # Used internally to establish the class of the root command
137
+ def root_command(cls)
138
+ @stack << { key: nil, cmd: cls } if @logger
139
+ end
140
+
141
+
142
+ private
143
+
144
+ def internal_key?(k)
145
+ return k == :logger || k == :test || k == :parent
146
+ end
147
+
148
+ def log_info_hash(params, kind)
149
+ return unless @logger
150
+ @logger.info(build_log(params, kind))
151
+ end
152
+
153
+ def build_log(msg, kind)
154
+ cur = @stack.last
155
+ out = {
156
+ sequence: @sequence,
157
+ cmd: cur[:cmd].name,
158
+ depth: @stack.length - 1
159
+ }
160
+ out[:key] = cur[:key] if cur[:key]
161
+ out[:kind] = kind
162
+ out[:msg] = msg if msg
163
+ return JSON.generate(out)
73
164
  end
74
-
75
165
  end
76
166
  end
@@ -15,11 +15,6 @@ module ActionCommand
15
15
  return cls.find(item) if item.is_a? Integer
16
16
  return yield(item)
17
17
  end
18
-
19
-
20
-
21
-
22
-
23
18
  end
24
19
 
25
20
  end
@@ -1,4 +1,4 @@
1
1
  module ActionCommand
2
2
  # Version of this Gem
3
- VERSION = '0.1.3'.freeze
3
+ VERSION = '0.1.4'.freeze
4
4
  end
@@ -3,6 +3,8 @@ require 'action_command/result'
3
3
  require 'action_command/input_output'
4
4
  require 'action_command/executable'
5
5
  require 'action_command/utils'
6
+ require 'action_command/log_parser'
7
+ require 'action_command/pretty_print_log_action'
6
8
 
7
9
  # To use action command, create subclasses of ActionCommand::Executable
8
10
  # and run them using the ActionCommand.execute_... variants.
@@ -18,6 +20,32 @@ module ActionCommand
18
20
  # Used as root parent of command if we are executing it from rails (a controller, etc)
19
21
  CONTEXT_RAILS = :rails
20
22
 
23
+ # Used as a root element when the command is executed from an API context
24
+ CONTEXT_API = :api
25
+
26
+ # Used if a result has had no failures
27
+ RESULT_CODE_OK = 0
28
+
29
+ # Used as a generic result code for failure, if you do not provide
30
+ # a more specific one through {ActionCommand::Result#failed_with_code}
31
+ RESULT_CODE_FAILED = 1
32
+
33
+ # log entry for the input to a commmand
34
+ LOG_KIND_COMMAND_INPUT = :command_input
35
+
36
+ # log entry for the output from a command
37
+ LOG_KIND_COMMAND_OUTPUT = :command_output
38
+
39
+ # info message from within a command
40
+ LOG_KIND_INFO = :info
41
+
42
+ # debug message from within a command
43
+ LOG_KIND_DEBUG = :debug
44
+
45
+ # error message from within a command
46
+ LOG_KIND_ERROR = :error
47
+
48
+
21
49
  # Used to create an optional parameter in describe_io
22
50
  OPTIONAL = { optional: true }.freeze
23
51
 
@@ -32,7 +60,7 @@ module ActionCommand
32
60
 
33
61
  # @return a new, valid, empty result.
34
62
  def self.create_result
35
- return ActionCommand::Result.new(@@logger)
63
+ return ActionCommand::Result.new
36
64
  end
37
65
 
38
66
  # Execute a command at the root level of a testing context
@@ -83,6 +111,15 @@ module ActionCommand
83
111
  return ActionCommand.create_and_execute(cls, params, CONTEXT_RAILS, result)
84
112
  end
85
113
 
114
+ # Execute a command at the root level of an api context
115
+ # @param cls [ActionCommand::Executable] The class of an Executable subclass
116
+ # @param params [Hash] parameters used by the command.
117
+ # @return [ActionCommand::Result]
118
+ def self.execute_api(cls, params = {})
119
+ result = create_result
120
+ return ActionCommand.create_and_execute(cls, params, CONTEXT_API, result)
121
+ end
122
+
86
123
  # Execute a child command, placing its results under the specified subkey
87
124
  # @param parent [ActionCommand::Executable] An instance of the parent command
88
125
  # @param cls [ActionCommand::Executable] The class of an Executable subclass
@@ -92,7 +129,7 @@ module ActionCommand
92
129
  # @param params [Hash] parameters used by the command.
93
130
  # @return [ActionCommand::Result]
94
131
  def self.execute_child(parent, cls, result, result_key, params = {})
95
- result.push(result_key)
132
+ result.push(result_key, cls)
96
133
  ActionCommand.create_and_execute(cls, params, parent, result)
97
134
  result.pop(result_key)
98
135
  return result
@@ -115,14 +152,24 @@ module ActionCommand
115
152
 
116
153
  # Used internally, not for general purpose use.
117
154
  def self.create_and_execute(cls, params, parent, result)
155
+ check_params(cls, params)
156
+ params[:parent] = parent
157
+ result.logger = params[:logger]
158
+ result.logger = @@logger unless params[:logger]
159
+ result.root_command(cls) if parent.is_a? Symbol
160
+ action = cls.new(params)
161
+
162
+ result.log_input(params)
163
+ action.execute(result)
164
+ result.log_output
165
+ return result
166
+ end
167
+
168
+ def self.check_params(cls, params)
118
169
  raise ArgumentError, 'Expected params to be a Hash' unless params.is_a? Hash
119
170
 
120
171
  unless cls.is_a?(Class) && cls.ancestors.include?(ActionCommand::Executable)
121
172
  raise ArgumentError, 'Expected an ActionCommand::Executable as class'
122
173
  end
123
-
124
- params[:parent] = parent
125
- action = cls.new(params)
126
- return action.execute(result)
127
174
  end
128
175
  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.3
4
+ version: 0.1.4
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-03-04 00:00:00.000000000 Z
11
+ date: 2016-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +206,34 @@ dependencies:
192
206
  - - ">="
193
207
  - !ruby/object:Gem::Version
194
208
  version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: activerecord
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: 4.0.0
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: 4.0.0
223
+ - !ruby/object:Gem::Dependency
224
+ name: sqlite3
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
195
237
  description: Simple implementation of command pattern focused on reuse in multiple
196
238
  contexts
197
239
  email:
@@ -200,6 +242,7 @@ executables: []
200
242
  extensions: []
201
243
  extra_rdoc_files: []
202
244
  files:
245
+ - ".byebug_history"
203
246
  - ".codeclimate.yml"
204
247
  - ".gitignore"
205
248
  - ".rspec"
@@ -236,7 +279,10 @@ files:
236
279
  - doc/top-level-namespace.html
237
280
  - lib/action_command.rb
238
281
  - lib/action_command/executable.rb
282
+ - lib/action_command/executable_transaction.rb
239
283
  - lib/action_command/input_output.rb
284
+ - lib/action_command/log_parser.rb
285
+ - lib/action_command/pretty_print_log_action.rb
240
286
  - lib/action_command/result.rb
241
287
  - lib/action_command/utils.rb
242
288
  - lib/action_command/version.rb