cheetah 0.3.0 → 0.4.0

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