command_exec 0.1.3 → 0.2.0

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.
Files changed (49) hide show
  1. data/Gemfile +6 -2
  2. data/Gemfile.lock +42 -18
  3. data/README.md +707 -72
  4. data/RELEASE_NOTES.md +62 -0
  5. data/Rakefile +40 -9
  6. data/TODO.md +8 -2
  7. data/command_exec.gemspec +3 -2
  8. data/gemfiles/Gemfile.default +28 -0
  9. data/gemfiles/Gemfile.travis +16 -0
  10. data/gemfiles/Gemfile.travis.lock +48 -0
  11. data/lib/command_exec.rb +22 -2
  12. data/lib/command_exec/command.rb +307 -157
  13. data/lib/command_exec/exceptions.rb +16 -6
  14. data/lib/command_exec/field_helper.rb +263 -0
  15. data/lib/command_exec/formatter/array.rb +158 -0
  16. data/lib/command_exec/formatter/hash.rb +78 -0
  17. data/lib/command_exec/formatter/json.rb +22 -0
  18. data/lib/command_exec/formatter/string.rb +22 -0
  19. data/lib/command_exec/formatter/xml.rb +22 -0
  20. data/lib/command_exec/formatter/yaml.rb +22 -0
  21. data/lib/command_exec/logger.rb +9 -0
  22. data/lib/command_exec/process.rb +294 -0
  23. data/lib/command_exec/spec_helper_module.rb +52 -0
  24. data/lib/command_exec/version.rb +1 -1
  25. data/script/console +8 -0
  26. data/spec/command/command_spec.rb +413 -117
  27. data/spec/command/test_data/echo_test +3 -0
  28. data/spec/command/test_data/exit_status_test +2 -0
  29. data/spec/command/test_data/log_file_test +3 -0
  30. data/spec/command/test_data/logger_test +2 -0
  31. data/spec/command/test_data/not_raise_error_test +4 -0
  32. data/spec/command/test_data/not_throw_error_test +4 -0
  33. data/spec/command/test_data/output_test +6 -0
  34. data/spec/command/test_data/raise_error_test +6 -0
  35. data/spec/command/test_data/runner_open3_test +4 -0
  36. data/spec/command/test_data/runner_system_test +4 -0
  37. data/spec/command/test_data/stderr_test +4 -0
  38. data/spec/command/test_data/stdout_multiple_lines_test +4 -0
  39. data/spec/command/test_data/stdout_test +4 -0
  40. data/spec/command/test_data/throw_error_test +6 -0
  41. data/spec/command/test_data/true_test +2 -0
  42. data/spec/formatter/array_spec.rb +215 -0
  43. data/spec/formatter/hash_spec.rb +117 -0
  44. data/spec/formatter/json_spec.rb +21 -0
  45. data/spec/formatter/xml_spec.rb +33 -0
  46. data/spec/formatter/yaml_spec.rb +21 -0
  47. data/spec/process/process_spec.rb +329 -0
  48. data/spec/spec_helper.rb +15 -4
  49. metadata +79 -5
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,62 @@
1
+ # 0.2.0
2
+
3
+ This release is a major rewrite of the library. Please keep in mind it is still
4
+ under very active development and till 1.0.0 the API can change at any time.
5
+ Though I'm quite satisfied with the api and will try to add things only.
6
+
7
+ * *TREATMENT OF COMMAND NAMES*:
8
+
9
+ Now only commands given as symbols will be searched in search path. Which can
10
+ be set as well now. If a string is given, it will be searched in the current
11
+ working directory. Furthermore the resolver was refactored. It is now a ruby
12
+ only solution.
13
+
14
+ * *TEST SUITE*:
15
+
16
+ Improved test suite. Added more tests and add `simplecov` for coverage check.
17
+
18
+ * *WORKING DIRECTORY*:
19
+
20
+ No side effects any more when using a working directory. This might have had an
21
+ effect on your library if you start your script from directory 'A', but want the
22
+ command to be run in directory 'B'. After running the command with `CommandExec`
23
+ the current working directory of your script was changed as well. This is fixed
24
+ now.
25
+
26
+ * *LOGGING*:
27
+
28
+ Now you have the possibility to change the log level of `CommandExec`. If you
29
+ choose one of `:silent`, `:unknown`, `:fatal`, `:error`, `:warn`, `:info` and
30
+ `:debug`. All possibilities map to their `Logger`-counterpart, so please see
31
+ the ruby logger documentation on the net (google for ruby logger). The only
32
+ exception is `:silent`. Choosing this option `CommandExec` will not output
33
+ anything.
34
+
35
+ * *VERSIONING*:
36
+
37
+ This gem now uses semvar versioning scheme. See www.semvar.org for more information.
38
+
39
+ * *RESULT OF COMMAND EXECUTION*:
40
+
41
+ Now it's possible that you get the result of the command execution in different formats:
42
+ * Array
43
+ * Hash
44
+ * JSON-String
45
+ * Simple String
46
+ * XML-String
47
+ * YAML-String
48
+
49
+ * *ERROR DETECTION*:
50
+
51
+ As part of the new interface there are methods put in place to detect errors
52
+ during command execution: Return code, STDERR, STDOUT and log file.
53
+
54
+ * *REACTION ON ERRORS*:
55
+
56
+ If an error happend, you can raise an exception, throw an error or ask
57
+ `CommandExec`to do nothing at all.
58
+
59
+ * *COMMAND RUNNER*
60
+
61
+ The new version switches from `POpen4` to `Open3` as default runner.
62
+ Furthermore `system`-call is also supported.
data/Rakefile CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env rake
2
- require 'bundler/gem_tasks'
3
- require 'yard'
4
- require 'rubygems/package_task'
5
- require 'active_support/core_ext/string/strip'
6
-
7
- YARD::Rake::YardocTask.new do |t|
8
- t.files = ['lib/**/*.rb', 'README.md', 'LICENCE.md']
9
- t.options = ['--output-dir=doc/yard', '--markup-provider=redcarpet', '--markup=markdown' ]
2
+
3
+ unless ENV['TRAVIS_CI'] == 'true'
4
+ namespace :gem do
5
+ require 'bundler/gem_tasks'
6
+ end
7
+
8
+ require 'yard'
9
+ require 'rubygems/package_task'
10
+ require 'active_support/core_ext/string/strip'
10
11
  end
11
12
 
13
+ YARD::Rake::YardocTask.new do; end
14
+
15
+ desc 'start tmux'
12
16
  task :terminal do
13
17
  sh "script/terminal"
14
18
  end
@@ -19,9 +23,10 @@ task :t => :terminal
19
23
  namespace :version do
20
24
  version_file = Dir.glob('lib/**/version.rb').first
21
25
 
26
+ desc 'bump version of library to new version'
22
27
  task :bump do
23
28
 
24
- new_version = ENV['VERSION']
29
+ new_version = ENV['VERSION'] || ENV['version']
25
30
 
26
31
  raw_module_name = File.open(version_file, "r").readlines.grep(/module/).first
27
32
  module_name = raw_module_name.chomp.match(/module\s+(\S+)/) {$1}
@@ -40,6 +45,7 @@ end}
40
45
  sh "git tag data_uri-#{new_version}"
41
46
  end
42
47
 
48
+ desc 'show version of library'
43
49
  task :show do
44
50
  raw_version = File.open(version_file, "r").readlines.grep(/VERSION/).first
45
51
 
@@ -52,8 +58,33 @@ end}
52
58
 
53
59
  end
54
60
 
61
+ desc 'Restore version file from git repository'
55
62
  task :restore do
56
63
  sh "git checkout #{version_file}"
57
64
  end
58
65
 
59
66
  end
67
+
68
+ namespace :travis do
69
+ desc 'Runs travis-lint to check .travis.yml'
70
+ task :check do
71
+ sh 'travis-lint'
72
+ end
73
+ end
74
+
75
+ namespace :test do
76
+ desc 'Run specs'
77
+ task :specs do
78
+ sh 'bundle exec rspec spec'
79
+ end
80
+
81
+ desc 'Run tests in "travis mode"'
82
+ task :travis_specs do
83
+ ENV['TRAVIS_CI'] = 'true'
84
+ sh 'rspec spec'
85
+ end
86
+ end
87
+
88
+ task :console do
89
+ sh 'script/console'
90
+ end
data/TODO.md CHANGED
@@ -1,2 +1,8 @@
1
- * Add options to execute a command silently
2
- * Check if attr_accessor or attr_reader is better in command.rb
1
+ * Add tests for search command
2
+ * Support shell environment for command
3
+ * Support Time of run in output
4
+ * Support run time in output
5
+ * Support stdin
6
+ * Limit output to n lines
7
+ * YAML should be the same output like the others
8
+ * Support for Proc as an reaction on an error
data/command_exec.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.require_paths = ['lib']
18
18
 
19
19
  # specify any dependencies here; for example:
20
- s.add_runtime_dependency 'POpen4'
21
- s.add_runtime_dependency 'colored'
20
+ s.add_runtime_dependency 'smart_colored'
21
+ s.add_runtime_dependency 'activesupport'
22
+ s.add_runtime_dependency 'xml-simple'
22
23
  end
@@ -0,0 +1,28 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in workplace-letter_generator.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ gem 'simplecov'
10
+ gem 'aruba'
11
+ gem 'fuubar'
12
+ end
13
+
14
+ group :documentation do
15
+ gem 'yard'
16
+ gem 'redcarpet'
17
+ gem 'github-markup'
18
+ end
19
+
20
+ group :development do
21
+ gem 'tmrb'
22
+ gem 'debugger'
23
+ gem 'pry'
24
+ gem 'pry-doc'
25
+ gem 'awesome_print'
26
+ gem 'travis-lint'
27
+ gem 'activesupport'
28
+ end
@@ -0,0 +1,16 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in workplace-letter_generator.gemspec
4
+ #gemspec path: '../command_exec.gemspec'
5
+ gemspec path: '../'
6
+
7
+ group :test do
8
+ gem 'rake'
9
+ gem 'rspec'
10
+ end
11
+
12
+ group :documentation do
13
+ gem 'yard'
14
+ gem 'redcarpet'
15
+ gem 'github-markup'
16
+ end
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: /home/d/work/projects/ruby-command_exec
3
+ specs:
4
+ command_exec (0.1.3)
5
+ POpen4
6
+ activesupport
7
+ colored (>= 1.2)
8
+ xml-simple
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ POpen4 (0.1.4)
14
+ Platform (>= 0.4.0)
15
+ open4
16
+ Platform (0.4.0)
17
+ activesupport (3.2.8)
18
+ i18n (~> 0.6)
19
+ multi_json (~> 1.0)
20
+ colored (1.2)
21
+ diff-lcs (1.1.3)
22
+ github-markup (0.7.4)
23
+ i18n (0.6.1)
24
+ multi_json (1.3.6)
25
+ open4 (1.3.0)
26
+ rake (0.9.2.2)
27
+ redcarpet (2.1.1)
28
+ rspec (2.11.0)
29
+ rspec-core (~> 2.11.0)
30
+ rspec-expectations (~> 2.11.0)
31
+ rspec-mocks (~> 2.11.0)
32
+ rspec-core (2.11.1)
33
+ rspec-expectations (2.11.3)
34
+ diff-lcs (~> 1.1.3)
35
+ rspec-mocks (2.11.2)
36
+ xml-simple (1.1.1)
37
+ yard (0.8.2.1)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ command_exec!
44
+ github-markup
45
+ rake
46
+ redcarpet
47
+ rspec
48
+ yard
data/lib/command_exec.rb CHANGED
@@ -1,11 +1,31 @@
1
1
  #encoding: utf-8
2
2
 
3
- require 'popen4'
4
- require 'colored'
3
+ require 'smart_colored/extend'
4
+ require 'json'
5
+ require 'psych'
6
+ require 'xmlsimple'
7
+ require 'open3'
8
+
5
9
  require 'logger'
10
+ require 'command_exec/logger'
11
+
12
+ require 'active_support/core_ext/object/blank'
13
+ require 'active_support/core_ext/string/filters'
14
+ require 'active_support/core_ext/hash/deep_merge'
15
+ require 'active_support/core_ext/hash/conversions'
16
+
17
+ require 'command_exec/field_helper'
18
+
19
+ require 'command_exec/formatter/array'
20
+ require 'command_exec/formatter/hash'
21
+ require 'command_exec/formatter/json'
22
+ require 'command_exec/formatter/yaml'
23
+ require 'command_exec/formatter/xml'
24
+ require 'command_exec/formatter/string'
6
25
 
7
26
  require 'command_exec/version'
8
27
  require 'command_exec/exceptions'
9
28
  require 'command_exec/command'
29
+ require 'command_exec/process'
10
30
 
11
31
  module CommandExec; end
@@ -5,49 +5,190 @@ module CommandExec
5
5
  # Run commands
6
6
  class Command
7
7
 
8
- attr_accessor :logfile, :options , :parameter, :error_keywords
8
+ # @!attribute [rw] log_file
9
+ # Set/Get log file for command
10
+ #
11
+ # @!attribute [rw] options
12
+ # Set/Get options for the command
13
+ #
14
+ # @!attribute [rw] parameter
15
+ # Set/Get parameter for the command
16
+ attr_accessor :log_file, :options , :parameter
17
+
18
+ # @!attribute [r] result
19
+ # Return the result of command execution
20
+ #
21
+ # @!attribute [r] path
22
+ # Return path to the executable of the command
23
+ #
24
+ # @!attribute [r] working_directory
25
+ # Return the working directory of the command
9
26
  attr_reader :result, :path, :working_directory
10
27
 
11
28
  # Create a new command to execute
12
29
  #
13
- # @param [Symbol] name name of command
14
- # @param [optional,Hash] opts options for the command
15
- # @option opts [String] :options options for binary
16
- # @option opts [String] :parameter parameter for binary
17
- # @option opts [String] :error_keywords keyword indicating an error on stdout
18
- # @option opts [String] :working_directory working directory where the process should run in
19
- # @option opts [String] :logfile file path to log file of process
30
+ # @param [Symbol] name
31
+ # name of command
32
+ #
33
+ # @param [optional,Hash] opts
34
+ # options for the command
35
+ #
36
+ # @option opts [String] :options ('')
37
+ # options for the command
38
+ #
39
+ # @option opts [String] :working_directory (current working directory)
40
+ # working_directory for the command
41
+ #
42
+ # @option opts [String] :log_file ('')
43
+ # log file of the command
44
+ #
45
+ # @option opts [String,Array] :search_paths ($PATH)
46
+ # where to search for the command (please mind the 's' at the end.
47
+ #
48
+ # @option opts [String,Array] :error_detection_on (:return_code)
49
+ # what information should be considered for error detection,
50
+ # available options are :return_code, :stderr, :stdout, :log_file.
51
+ # You can use one or more of them.
52
+ #
53
+ # @option opts [Hash] :error_indicators
54
+ # what keywords etc. should be considered as errors.
55
+ #
56
+ # You can define allowed or forbidden keywords or exit codes.
57
+ # To search for errors in a log file you need to provide one.
58
+ #
59
+ # For each option you can provide a single word or an Array of words.
60
+ #
61
+ # ```
62
+ # :allowed_return_code => [0],
63
+ # :forbidden_return_code => [],
64
+ # :allowed_words_in_stderr => [],
65
+ # :forbidden_words_in_stderr => [],
66
+ # :allowed_words_in_stdout => [],
67
+ # :forbidden_words_in_stdout => [],
68
+ # :allowed_words_in_log_file => [],
69
+ # :forbidden_words_in_log_file => [],
70
+ # ```
71
+ #
72
+ # @option opts [Symbol] :on_error_do
73
+ # Oh, an error happend, what to do next? Raise an error (:raise_error),
74
+ # Throw an error (:throw_error) or do nothing at all (:nothing, default).
75
+ #
76
+ # @option opts [Symbol] :run_via
77
+ # Which runner should be used to execute the command: :open3 (default)
78
+ # or :system.
79
+ #
80
+ # @option opts [Logger] :lib_logger
81
+ # The logger which is used to output information generated by the
82
+ # library. The logger which is provided needs to be compatible with api
83
+ # of the Ruby `Logger`-class.
84
+ #
85
+ # @option opts [Symbol] :lib_log_level
86
+ # What information should handled by the logger:
87
+ # :debug, :info, :warn, :error, :fatal, :unknown. Additionally the
88
+ # :silent-option is understood: do not output anything (@see README for
89
+ # further information).
20
90
  def initialize(name,opts={})
21
91
 
22
92
  @name = name
23
93
  @opts = {
24
- :logger => Logger.new($stderr),
25
94
  :options => '',
26
95
  :parameter => '',
27
- :error_keywords => [],
28
96
  :working_directory => Dir.pwd,
29
- :logfile => '',
30
- :log_level => :info,
31
- }.update opts
97
+ :log_file => '',
98
+ :search_paths => ENV['PATH'].split(':'),
99
+ :error_detection_on => [:return_code],
100
+ :error_indicators => {
101
+ :allowed_return_code => [0],
102
+ :forbidden_return_code => [],
103
+ #
104
+ :allowed_words_in_stderr => [],
105
+ :forbidden_words_in_stderr => [],
106
+ #
107
+ :allowed_words_in_stdout => [],
108
+ :forbidden_words_in_stdout => [],
109
+ #
110
+ :allowed_words_in_log_file => [],
111
+ :forbidden_words_in_log_file => [],
112
+ },
113
+ :on_error_do => :nothing,
114
+ :run_via => :open3,
115
+ :lib_logger => Logger.new($stderr),
116
+ :lib_log_level => :info,
117
+ }.deep_merge opts
118
+
119
+ @logger = @opts[:lib_logger]
120
+ configure_logging
121
+
122
+ @logger.debug @opts
32
123
 
33
- @logger = @opts[:logger]
34
124
  @options = @opts[:options]
125
+ @path = resolve_path @name, @opts[:search_paths]
35
126
  @parameter = @opts[:parameter]
36
- @path = resolve_cmd_name(name)
37
- @error_keywords = @opts[:error_keywords]
38
- @logfile = @opts[:logfile]
127
+ @log_file = @opts[:log_file]
39
128
 
40
- configure_logging
129
+ *@error_detection_on = @opts[:error_detection_on]
130
+ @error_indicators = @opts[:error_indicators]
131
+ @on_error_do = @opts[:on_error_do]
41
132
 
42
- @working_directory = @opts[:working_directory]
43
- Dir.chdir(working_directory)
133
+ @run_via = @opts[:run_via]
44
134
 
135
+ @working_directory = @opts[:working_directory]
136
+ @result = nil
45
137
  end
46
138
 
47
139
  private
48
140
 
141
+ # Find path to cmd
142
+ #
143
+ # @param [String] name
144
+ # Name of command. It accepts :cmd, 'cmd', 'rel_path/cmd' or
145
+ # '/fq_path/to/cmd'. When :cmd is used it searches 'search_paths' for the
146
+ # executable. Whenn 'cmd' is used it looks for cmd in local dir. The same
147
+ # happens when 'rel_path/cmd' is used. A full qualified path
148
+ # '/fq_path/to/cmd' is used as normal.
149
+ #
150
+ # @param [Array] search_paths
151
+ # Where to look for executables
152
+ #
153
+ # @return [String] fully qualified path to command
154
+ def resolve_path(name,*search_paths)
155
+ search_paths ||= ['/bin', '/usr/bin']
156
+ search_paths = search_paths.flatten
157
+
158
+ if name.kind_of? Symbol
159
+ path = search_paths.map{ |p| File.join(p, name.to_s) }.find {|p| File.exists? p } || ""
160
+ else
161
+ path = File.expand_path(name)
162
+ end
163
+
164
+ path
165
+ end
166
+
167
+ # Check if executable exists, if it's executable and is a file
168
+ #
169
+ # @raise [CommandExec::Exceptions::CommandNotFound] if command does not exist
170
+ # @raise [CommandExec::Exceptions::CommandNotExecutable] if command is not executable
171
+ # @raise [CommandExec::Exceptions::CommandIsNotAFile] if command is not a file
172
+ def check_path
173
+ unless exists?
174
+ @logger.fatal("Command '#{@name}' not found.")
175
+ raise Exceptions::CommandNotFound , "Command '#{@name}' not found."
176
+ end
177
+
178
+ unless executable?
179
+ @logger.fatal("Command '#{@name}' not executable.")
180
+ raise Exceptions::CommandNotExecutable , "Command '#{@name}' not executable."
181
+ end
182
+
183
+ unless file?
184
+ @logger.fatal("Command '#{@name}' not a file.")
185
+ raise Exceptions::CommandIsNotAFile, "Command '#{@name}' not a file."
186
+ end
187
+ end
188
+
189
+ # Set appropriate log level
49
190
  def configure_logging
50
- case @opts[:log_level]
191
+ case @opts[:lib_log_level]
51
192
  when :debug
52
193
  @logger.level = Logger::DEBUG
53
194
  when :error
@@ -61,191 +202,200 @@ module CommandExec
61
202
  when :warn
62
203
  @logger.level = Logger::WARN
63
204
  when :silent
64
- @logger.instance_variable_set(:@logdev, nil)
205
+ @logger.level = Logger::SILENT
65
206
  else
66
- log_level = Logger::INFO
207
+ @logger.level = Logger::INFO
67
208
  end
68
- end
69
209
 
70
- # Find utility path
71
- #
72
- # @param [Symbol] name Name of utility
73
- # @return [Path] Returns the path to the binary of the binary
74
- def resolve_cmd_name(name)
75
- path=''
76
- path = %x[which #{name} 2>/dev/null].chomp
210
+ @logger.debug "Logger configured with log level #{@logger.level}"
77
211
 
78
- if path.empty?
79
- @logger.fatal("Command not found #{name}")
80
- raise Exceptions::CommandNotFound
81
- end
212
+ nil
213
+ end
82
214
 
83
- path
215
+ public
216
+
217
+ #Is executable valid
218
+ def valid?
219
+ exists? and executable? and file?
84
220
  end
85
221
 
86
- # Build string to execute command
87
- #
88
- # @return [String] Returns to whole command with parameters and options
89
- def build_cmd_string
90
- cmd = ''
91
- cmd += path
92
- cmd += options.empty? ? "" : " #{options}"
93
- cmd += parameter.empty? ? "" : " #{parameter}"
222
+ # Does the command exist?
223
+ #
224
+ # @return [True,False] result of check
225
+ def exists?
226
+ File.exists? @path
227
+ end
94
228
 
95
- cmd
229
+ # Is the command executable
230
+ #
231
+ # @return [True,False] result of check
232
+ def executable?
233
+ File.executable? @path
96
234
  end
97
235
 
98
- public
236
+ # Is the provided string a file
237
+ #
238
+ # @return [True,False] result of check
239
+ def file?
240
+ File.file? @path
241
+ end
99
242
 
100
243
  # Output the textual representation of a command
101
- # public alias for build_cmd_string
102
244
  #
103
245
  # @return [String] command in text form
104
- def to_txt
105
- build_cmd_string
246
+ def to_s
247
+ cmd = ''
248
+ cmd += @path
249
+ cmd += @options.blank? ? "" : " #{@options}"
250
+ cmd += @parameter.blank? ? "" : " #{@parameter}"
251
+
252
+ @logger.debug cmd
253
+
254
+ cmd
106
255
  end
107
256
 
108
257
  # Run the program
109
258
  #
259
+ # @raise [CommandExec::Exceptions::CommandExecutionFailed] if an error
260
+ # occured and `command_exec` should raise an exception in the case of an
261
+ # error.
262
+ # @throw [:command_execution_failed] if an error
263
+ # occured and `command_exec` should throw an error (which you can catch)
264
+ # in the case of an error
110
265
  def run
266
+ process = CommandExec::Process.new(:lib_logger => @logger)
267
+ process.log_file = @log_file if @log_file
268
+ process.status = :success
111
269
 
112
- _stdout = ''
113
- _stderr = ''
114
-
115
- status = POpen4::popen4(build_cmd_string) do |stdout, stderr, stdin, pid|
116
- _stdout = stdout.read.strip
117
- _stderr = stderr.read.strip
118
- end
270
+ check_path
119
271
 
272
+ process.start_time = Time.now
120
273
 
121
- error_in_stdout_found = error_in_string_found?(error_keywords,_stdout)
122
- @result = run_successful?( status.success? , error_in_stdout_found )
123
-
124
- if @result == false
125
- msg = message(
126
- @result,
127
- help_output(
128
- {
129
- :error_in_exec => not(status.success?),
130
- :error_in_stdout => error_in_stdout_found
131
- }, {
132
- :stdout => StringIO.new(_stdout),
133
- :stderr => StringIO.new(_stderr),
134
- :logfile => read_logfile(logfile),
135
- }
136
- )
137
- )
274
+ case @run_via
275
+ when :open3
276
+ Open3::popen3(to_s, :chdir => @working_directory) do |stdin, stdout, stderr, wait_thr|
277
+ process.stdout = stdout.readlines.map(&:chomp)
278
+ process.stderr = stderr.readlines.map(&:chomp)
279
+ process.pid = wait_thr.pid
280
+ process.return_code = wait_thr.value.exitstatus
281
+ end
282
+ when :system
283
+ Dir.chdir(@working_directory) do
284
+ system(to_s)
285
+ process.stdout = []
286
+ process.stderr = []
287
+ process.pid = $?.pid
288
+ process.return_code = $?.exitstatus
289
+ end
138
290
  else
139
- msg = message(@result)
291
+ Open3::popen3(to_s, :chdir => @working_directory) do |stdin, stdout, stderr, wait_thr|
292
+ process.stdout = stdout.readlines.map(&:chomp)
293
+ process.stderr = stderr.readlines.map(&:chomp)
294
+ process.pid = wait_thr.pid
295
+ process.return_code = wait_thr.value.exitstatus
296
+ end
140
297
  end
141
298
 
142
- @logger.info "#{@name.to_s}: #{msg}"
299
+ process.end_time = Time.now
143
300
 
144
- @result
145
- end
301
+ if @error_detection_on.include?(:return_code)
302
+ if not @error_indicators[:allowed_return_code].include? process.return_code or
303
+ @error_indicators[:forbidden_return_code].include? process.return_code
146
304
 
147
- # Read the content of the logfile
148
- #
149
- # @param [Path] file path to logfile
150
- # @param [Integer] num_of_lines the number of lines which should be read -- e.g. 30 lines = -30
151
- def read_logfile(file, num_of_lines=-30)
152
- content = StringIO.new
305
+ @logger.debug "Error detection on return code found an error"
306
+ process.status = :failed
307
+ process.reason_for_failure = :return_code
308
+ end
309
+ end
153
310
 
154
- unless file.empty?
155
- begin
156
- content << File.readlines(logfile)[num_of_lines..-1].join("")
157
- rescue Errno::ENOENT
158
- @logger.warn "Warning: logfile not found!"
311
+ if @error_detection_on.include?(:stderr) and not process.status == :failed
312
+ if error_occured?( @error_indicators[:forbidden_words_in_stderr], @error_indicators[:allowed_words_in_stderr], process.stderr)
313
+ @logger.debug "Error detection on stderr found an error"
314
+ process.status = :failed
315
+ process.reason_for_failure = :stderr
316
+ end
159
317
  end
160
- end
161
318
 
162
- content
163
- end
319
+ if @error_detection_on.include?(:stdout) and not process.status == :failed
320
+ if error_occured?( @error_indicators[:forbidden_words_in_stdout], @error_indicators[:allowed_words_in_stdout], process.stdout)
321
+ @logger.debug "Error detection on stdout found an error"
322
+ process.status = :failed
323
+ process.reason_for_failure = :stdout
324
+ end
325
+ end
164
326
 
165
- # Decide if a program run was successful
166
- #
167
- # @return [Boolean] Returns the decision
168
- def run_successful?(success,error_in_stdout)
169
- if success == false or error_in_stdout == true
170
- return false
171
- else
172
- return true
173
- end
174
- end
327
+ if @error_detection_on.include?(:log_file) and not process.status == :failed
328
+ if error_occured?( @error_indicators[:forbidden_words_in_log_file], @error_indicators[:allowed_words_in_log_file], process.log_file)
329
+ @logger.debug "Error detection on log file found an error"
330
+ process.status = :failed
331
+ process.reason_for_failure = :log_file
332
+ end
333
+ end
175
334
 
176
- # Decide which output to return to the user
177
- # to help him with debugging
178
- #
179
- # @return [Array] Returns lines of log/stdout/stderr
180
- def help_output(error_indicators={},output={})
181
- error_in_exec = error_indicators[:error_in_exec]
182
- error_in_stdout = error_indicators[:error_in_stdout]
183
-
184
- logfile = output[:logfile].string
185
- stdout = output[:stdout].string
186
- stderr = output[:stderr].string
187
-
188
- result = []
189
-
190
- if error_in_exec == true
191
- result << '================== LOGFILE ================== '
192
- result << logfile if logfile.empty? == false
193
- result << '================== STDOUT ================== '
194
- result << stdout if stdout.empty? == false
195
- result << '================== STDERR ================== '
196
- result << stderr if stderr.empty? == false
197
- elsif error_in_stdout == true
198
- result << '================== STDOUT ================== '
199
- result << stdout
335
+ @logger.debug "Result of command run #{process.status}"
336
+
337
+ @result = process
338
+ if process.status == :failed
339
+ case @on_error_do
340
+ when :nothing
341
+ #nothing
342
+ when :raise_error
343
+ raise CommandExec::Exceptions::CommandExecutionFailed, "An error occured. Please check for reason via command.reason_for_failure and/or command.stdout, comand.stderr, command.log_file, command.return_code"
344
+ when :throw_error
345
+ throw :command_execution_failed
346
+ else
347
+ #nothing
348
+ end
200
349
  end
201
-
202
- result
203
350
  end
204
351
 
205
- # Find error in stdout
206
- #
352
+ # Find error in data
353
+ #
354
+ # @param [Array,String] forbidden_word
355
+ # what are the forbidden words which indidcate an error
356
+ #
357
+ # @param [Array,String] exception
358
+ # Is there any exception from that forbidden words, maybe a string
359
+ # which contains the forbidden word, but is no error?
360
+ #
361
+ # @param [Array,String] data
362
+ # Where to look for errors.
363
+ #
207
364
  # @return [Boolean] Returns true if it finds an error
208
- def error_in_string_found? (keywords=[], string )
209
- return false if keywords.empty? or not keywords.is_a? Array
210
- return false if string.nil? or not string.is_a? String
211
-
365
+ def error_occured?(forbidden_word, exception, data )
212
366
  error_found = false
213
- keywords.each do |word|
214
- if string.include? word
215
- error_found = true
216
- break
367
+ *forbidden_word = forbidden_word
368
+ *exception = exception
369
+ *data = data
370
+
371
+ return false if forbidden_word.blank?
372
+ return false if data.blank?
373
+
374
+ forbidden_word.each do |word|
375
+ data.each do |line|
376
+ line.strip!
377
+
378
+ #line includes word -> error
379
+ #exception does not include line/substring of line -> error, if
380
+ # includes line/substring of line -> no error
381
+ if line.include? word and exception.find{ |e| line[e] }.blank?
382
+ error_found = true
383
+ break
384
+ end
217
385
  end
218
386
  end
219
387
 
220
388
  error_found
221
389
  end
222
390
 
223
- # Generate the message which is return to the user
224
- #
225
- # @param [Boolean] run_successful true if a positive message should be returned
226
- # @param [Array] msg Message which should be returned
227
- def message(run_successful, *msg)
228
-
229
- message = []
230
- if run_successful
231
- message << 'OK'.green.bold
232
- else
233
- message << 'FAILED'.red.bold
234
- message.concat msg.flatten
235
- end
236
-
237
- message.join("\n")
238
- end
239
-
240
- # Constructur to initiate a new command and run it later
391
+ # Run a command
241
392
  #
242
393
  # @see #initialize
243
- def Command.execute(name,opts={})
394
+ def self.execute(name,opts={})
244
395
  command = new(name,opts)
245
396
  command.run
246
397
 
247
398
  command
248
399
  end
249
-
250
400
  end
251
401
  end