command_exec 0.1.3 → 0.2.0

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