cheetah 0.3.0 → 0.4.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 (5) hide show
  1. data/CHANGELOG +10 -0
  2. data/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/lib/cheetah.rb +74 -35
  5. metadata +76 -86
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ 0.4.0 (2013-11-21)
2
+ ------------------
3
+
4
+ * Implemented incremental logging. The input and both outputs of the executed
5
+ command are now logged one-by-line by the default recorder. A custom recorder
6
+ can record them on even finer granularity.
7
+ * Dropped support of Ruby 1.8.7.
8
+ * Added support of Ruby 2.0.0.
9
+ * Internal code improvements.
10
+
1
11
  0.3.0 (2012-06-21)
2
12
  ------------------
3
13
 
data/README.md CHANGED
@@ -164,7 +164,7 @@ For more information, see the
164
164
  Compatibility
165
165
  -------------
166
166
 
167
- Cheetah should run well on any Unix system with Ruby 1.8.7 or 1.9.3. Non-Unix
167
+ Cheetah should run well on any Unix system with Ruby 1.9.3 or 2.0.0. Non-Unix
168
168
  systems and different Ruby implementations/versions may work too but they were
169
169
  not tested.
170
170
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
@@ -90,35 +90,36 @@ module Cheetah
90
90
  abstract_method :record_commands
91
91
 
92
92
  # @!method record_stdin(stdin)
93
- # Called to record the executed command input (if it wasn't read from a
94
- # stream).
93
+ # Called to record part of the executed command input.
95
94
  #
96
95
  # @abstract
97
- # @param [String] stdin the executed command input
96
+ # @param [String] stdin part of the executed command input
98
97
  abstract_method :record_stdin
99
98
 
100
- # @!method record_status(status)
101
- # Called to record the executed command exit status.
102
- #
103
- # @abstract
104
- # @param [Process::Status] status the executed command exit status
105
- abstract_method :record_status
106
-
107
99
  # @!method record_stdout(stdout)
108
- # Called to record the output the executed command wrote to stdout (if it
109
- # wasn't captured into a stream).
100
+ # Called to record part of the output the executed command wrote to
101
+ # stdout.
110
102
  #
111
103
  # @abstract
112
- # @param [String] stdout the output the executed command wrote to stdout
104
+ # @param [String] stdout part of the output the executed command wrote to
105
+ # stdout
113
106
  abstract_method :record_stdout
114
107
 
115
108
  # @!method record_stderr(stderr)
116
- # Called to record the output the executed command wrote to stderr (if it
117
- # wasn't captured into a stream).
109
+ # Called to record part of the output the executed command wrote to
110
+ # stderr.
118
111
  #
119
112
  # @abstract
120
- # @param [String] stderr the output the executed command wrote to stderr
113
+ # @param [String] stderr part of the output the executed command wrote to
114
+ # stderr
121
115
  abstract_method :record_stderr
116
+
117
+ # @!method record_status(status)
118
+ # Called to record the executed command exit status.
119
+ #
120
+ # @abstract
121
+ # @param [Process::Status] status the executed command exit status
122
+ abstract_method :record_status
122
123
  end
123
124
 
124
125
  # A recorder that does not record anyting. Used by {Cheetah.run} when no
@@ -126,17 +127,27 @@ module Cheetah
126
127
  class NullRecorder < Recorder
127
128
  def record_commands(commands); end
128
129
  def record_stdin(stdin); end
129
- def record_status(status); end
130
130
  def record_stdout(stdout); end
131
131
  def record_stderr(stderr); end
132
+ def record_status(status); end
132
133
  end
133
134
 
134
135
  # A default recorder. It uses the `Logger::INFO` level for normal messages and
135
136
  # the `Logger::ERROR` level for messages about errors (non-zero exit status or
136
137
  # non-empty error output). Used by {Cheetah.run} when a logger is passed.
137
138
  class DefaultRecorder < Recorder
139
+ # @private
140
+ STREAM_INFO = {
141
+ :stdin => { :name => "Standard input", :method => :info },
142
+ :stdout => { :name => "Standard output", :method => :info },
143
+ :stderr => { :name => "Error output", :method => :error }
144
+ }
145
+
138
146
  def initialize(logger)
139
147
  @logger = logger
148
+
149
+ @stream_used = { :stdin => false, :stdout => false, :stderr => false }
150
+ @stream_buffer = { :stdin => "", :stdout => "", :stderr => "" }
140
151
  end
141
152
 
142
153
  def record_commands(commands)
@@ -144,32 +155,54 @@ module Cheetah
144
155
  end
145
156
 
146
157
  def record_stdin(stdin)
147
- @logger.info "Standard input: #{format_input_output(stdin)}"
148
- end
149
-
150
- def record_status(status)
151
- @logger.send status.success? ? :info : :error,
152
- "Status: #{status.exitstatus}"
158
+ log_stream_increment(:stdin, stdin)
153
159
  end
154
160
 
155
161
  def record_stdout(stdout)
156
- @logger.info "Standard output: #{format_input_output(stdout)}"
162
+ log_stream_increment(:stdout, stdout)
157
163
  end
158
164
 
159
165
  def record_stderr(stderr)
160
- @logger.send stderr.empty? ? :info : :error,
161
- "Error output: #{format_input_output(stderr)}"
166
+ log_stream_increment(:stderr, stderr)
162
167
  end
163
168
 
164
- protected
169
+ def record_status(status)
170
+ log_stream_remainder(:stdin)
171
+ log_stream_remainder(:stdout)
172
+ log_stream_remainder(:stderr)
165
173
 
166
- def format_input_output(s)
167
- s.empty? ? "(none)" : s
174
+ @logger.send status.success? ? :info : :error,
175
+ "Status: #{status.exitstatus}"
168
176
  end
169
177
 
178
+ protected
179
+
170
180
  def format_commands(commands)
171
181
  '"' + commands.map { |c| Shellwords.join(c) }.join(" | ") + '"'
172
182
  end
183
+
184
+ def log_stream_increment(stream, data)
185
+ @stream_buffer[stream] + data =~ /\A((?:.*\n)*)(.*)\z/
186
+ lines, rest = $1, $2
187
+
188
+ lines.each_line { |l| log_stream_line(stream, l) }
189
+
190
+ @stream_used[stream] = true
191
+ @stream_buffer[stream] = rest
192
+ end
193
+
194
+ def log_stream_remainder(stream)
195
+ if @stream_used[stream] && !@stream_buffer[stream].empty?
196
+ log_stream_line(stream, @stream_buffer[stream])
197
+ end
198
+ end
199
+
200
+ def log_stream_line(stream, line)
201
+ @logger.send(
202
+ STREAM_INFO[stream][:method],
203
+ "#{STREAM_INFO[stream][:name]}: #{line.chomp}"
204
+ )
205
+ end
173
206
  end
174
207
 
175
208
  # @private
@@ -322,18 +355,15 @@ module Cheetah
322
355
  recorder = build_recorder(options)
323
356
 
324
357
  recorder.record_commands(commands)
325
- recorder.record_stdin(streams[:stdin].string) unless streamed[:stdin]
326
358
 
327
359
  pid, pipes = fork_commands(commands)
328
- select_loop(streams, pipes)
360
+ select_loop(streams, pipes, recorder)
329
361
  pid, status = Process.wait2(pid)
330
362
 
331
363
  begin
332
364
  check_errors(commands, status, streams, streamed)
333
365
  ensure
334
366
  recorder.record_status(status)
335
- recorder.record_stdout(streams[:stdout].string) unless streamed[:stdout]
336
- recorder.record_stderr(streams[:stderr].string) unless streamed[:stderr]
337
367
  end
338
368
 
339
369
  build_result(streams, options)
@@ -449,7 +479,7 @@ module Cheetah
449
479
  [pid, pipes]
450
480
  end
451
481
 
452
- def select_loop(streams, pipes)
482
+ def select_loop(streams, pipes, recorder)
453
483
  # We write the command's input and read its output using a select loop.
454
484
  # Why? Because otherwise we could end up with a deadlock.
455
485
  #
@@ -465,6 +495,10 @@ module Cheetah
465
495
  pipes[:stdout][READ] => streams[:stdout],
466
496
  pipes[:stderr][READ] => streams[:stderr]
467
497
  }
498
+ recorder_methods = {
499
+ pipes[:stdout][READ] => :record_stdout,
500
+ pipes[:stderr][READ] => :record_stderr
501
+ }
468
502
  pipes_readable = [pipes[:stdout][READ], pipes[:stderr][READ]]
469
503
  pipes_writable = [pipes[:stdin][WRITE]]
470
504
  loop do
@@ -482,10 +516,14 @@ module Cheetah
482
516
 
483
517
  ios_read.each do |pipe|
484
518
  begin
485
- outputs[pipe] << pipe.readpartial(4096)
519
+ data = pipe.readpartial(4096)
486
520
  rescue EOFError
487
521
  pipe.close
522
+ next
488
523
  end
524
+
525
+ outputs[pipe] << data
526
+ recorder.send(recorder_methods[pipe], data)
489
527
  end
490
528
 
491
529
  ios_write.each do |pipe|
@@ -496,6 +534,7 @@ module Cheetah
496
534
  end
497
535
 
498
536
  n = pipe.syswrite(stdin_buffer)
537
+ recorder.record_stdin(stdin_buffer[0...n])
499
538
  stdin_buffer = stdin_buffer[n..-1]
500
539
  end
501
540
  end
metadata CHANGED
@@ -1,128 +1,118 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: cheetah
3
- version: !ruby/object:Gem::Version
4
- hash: 19
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 3
9
- - 0
10
- version: 0.3.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - David Majda
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-06-21 00:00:00 +02:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2013-11-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: abstract_method
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
25
17
  none: false
26
- requirements:
18
+ requirements:
27
19
  - - ~>
28
- - !ruby/object:Gem::Version
29
- hash: 11
30
- segments:
31
- - 1
32
- - 2
33
- version: "1.2"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
34
22
  type: :runtime
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: rspec
38
23
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
40
25
  none: false
41
- requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- hash: 3
45
- segments:
46
- - 0
47
- version: "0"
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
48
38
  type: :development
49
- version_requirements: *id002
50
- - !ruby/object:Gem::Dependency
51
- name: redcarpet
52
39
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
54
41
  none: false
55
- requirements:
56
- - - ">="
57
- - !ruby/object:Gem::Version
58
- hash: 3
59
- segments:
60
- - 0
61
- version: "0"
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: redcarpet
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
62
54
  type: :development
63
- version_requirements: *id003
64
- - !ruby/object:Gem::Dependency
65
- name: yard
66
55
  prerelease: false
67
- requirement: &id004 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: yard
64
+ requirement: !ruby/object:Gem::Requirement
68
65
  none: false
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- hash: 3
73
- segments:
74
- - 0
75
- version: "0"
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
76
70
  type: :development
77
- version_requirements: *id004
78
- description: Your swiss army knife for executing external commands in Ruby safely and conveniently.
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Your swiss army knife for executing external commands in Ruby safely
79
+ and conveniently.
79
80
  email: dmajda@suse.de
80
81
  executables: []
81
-
82
82
  extensions: []
83
-
84
83
  extra_rdoc_files: []
85
-
86
- files:
84
+ files:
87
85
  - CHANGELOG
88
86
  - LICENSE
89
87
  - README.md
90
88
  - VERSION
91
89
  - lib/cheetah.rb
92
90
  - lib/cheetah/version.rb
93
- has_rdoc: true
94
91
  homepage: https://github.com/openSUSE/cheetah
95
- licenses:
92
+ licenses:
96
93
  - MIT
97
94
  post_install_message:
98
95
  rdoc_options: []
99
-
100
- require_paths:
96
+ require_paths:
101
97
  - lib
102
- required_ruby_version: !ruby/object:Gem::Requirement
98
+ required_ruby_version: !ruby/object:Gem::Requirement
103
99
  none: false
104
- requirements:
105
- - - ">="
106
- - !ruby/object:Gem::Version
107
- hash: 3
108
- segments:
109
- - 0
110
- version: "0"
111
- required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
105
  none: false
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- hash: 3
117
- segments:
118
- - 0
119
- version: "0"
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
120
110
  requirements: []
121
-
122
111
  rubyforge_project:
123
- rubygems_version: 1.6.2
112
+ rubygems_version: 1.8.23
124
113
  signing_key:
125
114
  specification_version: 3
126
- summary: Your swiss army knife for executing external commands in Ruby safely and conveniently.
115
+ summary: Your swiss army knife for executing external commands in Ruby safely and
116
+ conveniently.
127
117
  test_files: []
128
-
118
+ has_rdoc: