dolzenko 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ # Safe way to intercept IO stream
2
+ # where just replacing STDOUT doesn't work:
3
+ # http://rubyforge.org/tracker/index.php?func=detail&aid=5217&group_id=426&atid=1698
4
+ #
5
+ module IoInterceptor
6
+ def intercept
7
+ begin
8
+ @intercept = true
9
+ @intercepted = ""
10
+ yield
11
+ ensure
12
+ @intercept = false
13
+ end
14
+ @intercepted
15
+ end
16
+
17
+ def supress
18
+ begin
19
+ @supress = true
20
+ yield
21
+ ensure
22
+ @supress = false
23
+ end
24
+ end
25
+
26
+ def write(str)
27
+ if @supress || @intercept
28
+ @intercepted << str.to_s unless @supress
29
+ str.size
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+
36
+ if $PROGRAM_NAME == __FILE__
37
+ require 'spec'
38
+
39
+ describe IoInterceptor do
40
+ before do
41
+ STDOUT.extend(IoInterceptor)
42
+ end
43
+
44
+ it "intercepts output to stream when the stream is extended with it" do
45
+ STDOUT.intercept { STDOUT.puts("42") }.should == "42\n"
46
+ STDOUT.intercept { STDOUT.puts("24") }.should == "24\n"
47
+ end
48
+
49
+ it "intercepted IO#write still returns the number of bytes written" do
50
+ STDOUT.intercept { STDOUT.write("42").should == 2 }
51
+ end
52
+
53
+ it "intercepted IO#write argument is converted using to_s" do
54
+ obj = "42"
55
+ def obj.to_s
56
+ "custom to_s"
57
+ end
58
+
59
+ STDOUT.intercept { STDOUT.puts(obj) }.should == "#{ obj.to_s }\n"
60
+ end
61
+ end
62
+
63
+ exit ::Spec::Runner::CommandLine.run
64
+ end
@@ -0,0 +1,90 @@
1
+ module RemoteDownload
2
+ # Returns IO object to be used with attachment_fu models, if retrieval fails - return nil
3
+ def get_uploaded_data(url, follow_redirect = 2)
4
+ uri = URI.parse(url)
5
+
6
+ dputs(%{Trying to retrieve image from #{ uri } })
7
+
8
+ response = nil
9
+ Net::HTTP.start(uri.host, uri.port) do |http|
10
+ # http.set_debug_output $stderr
11
+ dputs(%{In Net::HTTP.start})
12
+
13
+ response = http.request_get(uri.path + (uri.query ? "?" + uri.query : ""), {
14
+ "User-Agent" => "curl/7.14.0 (i586-pc-mingw32msvc) libcurl/7.14.0 zlib/1.2.2",
15
+ "Host" => uri.host,
16
+ "Accept" => "*/*",
17
+ "Referer" => SELFPORT,
18
+ })
19
+
20
+ dputs("got response: #{ response }")
21
+ end
22
+
23
+ case response
24
+ when Net::HTTPSuccess
25
+ MyStringIO.from_http_response(response, url)
26
+
27
+ when Net::HTTPRedirection
28
+ return nil if follow_redirect <= 0
29
+
30
+ defined?(logger) && logger.debug(%{Following redirect from #{uri} to #{response['location']}})
31
+
32
+ get_uploaded_data(response['location'], follow_redirect - 1)
33
+
34
+ else
35
+ nil
36
+ end
37
+ end
38
+ module_function :get_uploaded_data
39
+
40
+ # Returns page content, if retrieval failed - just return empty string
41
+ def download_page(url, follow_redirect = 2)
42
+ uri = URI.parse(url)
43
+
44
+ defined?(logger) && logger.debug(%{Trying to retrieve page #{uri}})
45
+
46
+ response = nil
47
+ Net::HTTP.start(uri.host, uri.port) do |http|
48
+ response = http.request_get(uri.path + (uri.query ? "?" + uri.query : ""), {
49
+ "User-Agent" => "curl/7.14.0 (i586-pc-mingw32msvc) libcurl/7.14.0 zlib/1.2.2",
50
+ "Host" => uri.host,
51
+ "Accept" => "text/html",
52
+ "Referer" => SELFPORT,
53
+ }
54
+ )
55
+ end
56
+
57
+ case response
58
+ when Net::HTTPSuccess
59
+ response.body
60
+ when Net::HTTPRedirection
61
+ return "" if follow_redirect <= 0
62
+
63
+ defined?(logger) && logger.debug(%{Following redirect from #{uri} to #{response['location']}})
64
+
65
+ download_page(response['location'], follow_redirect - 1)
66
+ else
67
+ ""
68
+ end
69
+ end
70
+
71
+ module_function :download_page
72
+
73
+ class MyStringIO < StringIO
74
+ def initialize(*args)
75
+ super(*args)
76
+ end
77
+
78
+ # Constructs IO object from HTTPResponse
79
+ def self.from_http_response(response, request_url)
80
+ new(response.body).tap do |io|
81
+ io.size = response.body.size
82
+ io.content_type = response['content-type']
83
+ io.filename = File.basename(request_url)
84
+ io.original_filename = File.basename(request_url)
85
+ end
86
+ end
87
+
88
+ attr_accessor :content_type, :filename, :size, :original_filename
89
+ end
90
+ end
@@ -0,0 +1,386 @@
1
+ # ## ShellOut
2
+ #
3
+ # Provides a convenient feature-rich way to "shell out" to external commands.
4
+ # Most useful features come from using `PTY` to execute the command. Not available
5
+ # on Windows, `Kernel#system` will be used instead.
6
+ #
7
+ # ## Features
8
+ #
9
+ # ### Interruption
10
+ #
11
+ # The external command can be easily interrupted and `Interrupt` exception
12
+ # will propagate to the calling program.
13
+ #
14
+ # For example while something like this can hang your terminal
15
+ #
16
+ # loop { system("ls -R /") } # => lists directories indefinitely,
17
+ # # Ctrl-C only stops ls
18
+ #
19
+ # That won't be the case with ShellOut:
20
+ #
21
+ # require "shell_out"
22
+ # include ShellOut
23
+ # loop { shell_out("ls -R /") } # => when Ctrl-C is pressed ls is terminated
24
+ # # and Interrupt exception is propagated
25
+ #
26
+ # Yes it's possible to examine the `$CHILD_STATUS.exitstatus` variable but that's
27
+ # not nearly as robust and flexible as `PTY` solution.
28
+ #
29
+ # ### TTY-like Output
30
+ #
31
+ # External command is running in pseudo TTY provided by `PTY` library on Unix,
32
+ # which means that commands like `ffmpeg`, `git` can report progress and
33
+ # **otherwise interact with user as usual**.
34
+ #
35
+ # ### Output Capturing
36
+ #
37
+ # Output of the command can be captured using `:out` option
38
+ #
39
+ # io = StringIO.new
40
+ # shell_out("echo 42", :out => io) # doesn't print anything
41
+ # io.string.chomp # => "42"
42
+ #
43
+ # If `:out => :return` option is passed - the `shell_out` return the output
44
+ # of the command instead of exit status.
45
+ #
46
+ # ### :raise_exceptions, :verbose, :noop, and :dry_run Options
47
+ #
48
+ # * `:raise_exceptions => true` will raise `ShellOutException` for any non-zero
49
+ # exit status of the command
50
+ #
51
+ # Following options have the same semantics as `FileUtils` method options do
52
+ #
53
+ # * `:verbose => true` will echo command before execution
54
+ #
55
+ # * `:noop => true` will just return zero exit status without executing
56
+ # the command
57
+ #
58
+ # * `:dry_run => true` equivalent to `:verbose => true, :noop => true`
59
+ #
60
+ module ShellOut
61
+ class ShellOutException < Exception
62
+ end
63
+
64
+ CTRL_C_CODE = ?\C-c
65
+ SUCCESS_EXIT_STATUS = 0
66
+
67
+ class << self
68
+ def before(*args)
69
+ if args.last.is_a?(Hash)
70
+ options = args.last
71
+
72
+ verbose, dry_run, noop = options.delete(:verbose), options.delete(:dry_run), options.delete(:noop)
73
+ verbose = noop = true if dry_run
74
+
75
+ puts "Executing: #{ args[0..-2].join(" ") }" if verbose
76
+
77
+ return false if noop
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ def after(exitstatus, out_stream, *args)
84
+ if args.last.is_a?(Hash) && args.last[:raise_exceptions] == true
85
+ unless exitstatus == SUCCESS_EXIT_STATUS
86
+ raise ShellOutException, "`#{ args[0..-2].join(" ") }' command finished with non-zero (#{ exitstatus }) exit status"
87
+ end
88
+ end
89
+ if args.last.is_a?(Hash) && args.last[:out] == :return
90
+ out_stream.rewind if out_stream.is_a?(StringIO)
91
+ out_stream.read
92
+ else
93
+ exitstatus
94
+ end
95
+ end
96
+
97
+ def command(*args)
98
+ (args.last.is_a?(Hash) ? args[0..-2] : args).join(" ")
99
+ end
100
+
101
+ def getopt(opt, default, *args)
102
+ if args.last.is_a?(Hash)
103
+ if opt == :out && args.last[:out] == :return
104
+ StringIO.new
105
+ else
106
+ args.last.fetch(opt, default)
107
+ end
108
+ else
109
+ default
110
+ end
111
+ end
112
+ end
113
+
114
+ module_function
115
+
116
+ def shell_out_with_pty(*args)
117
+ old_state = `stty -g`
118
+ return SUCCESS_EXIT_STATUS unless ShellOut::before(*args)
119
+
120
+ # stolen from ruby/ext/pty/script.rb
121
+ # disable echoing and enable raw (not having to press enter)
122
+ system "stty -echo raw lnext ^_"
123
+
124
+ in_stream = ShellOut.getopt(:in, STDIN, *args)
125
+ out_stream = ShellOut.getopt(:out, STDOUT, *args)
126
+
127
+ PTY.spawn(ShellOut.command(*args)) do |r_pty, w_pty, pid|
128
+ reader = Thread.current
129
+ writer = Thread.new do
130
+ while true
131
+ break if (ch = in_stream.getc).nil?
132
+ ch = ch.chr
133
+ if ch == ShellOut::CTRL_C_CODE
134
+ reader.raise Interrupt, "Interrupted by user"
135
+ else
136
+ w_pty.print ch
137
+ w_pty.flush
138
+ end
139
+ end
140
+ end
141
+ writer.abort_on_exception = true
142
+
143
+ loop do
144
+ c = begin
145
+ r_pty.sysread(512)
146
+ rescue Errno::EIO, EOFError
147
+ nil
148
+ end
149
+ break if c.nil?
150
+
151
+ out_stream.print c
152
+ out_stream.flush
153
+ end
154
+
155
+ begin
156
+ # try to invoke waitpid() before the signal handler does it
157
+ return ShellOut::after(Process::waitpid2(pid)[1].exitstatus, out_stream, *args)
158
+ rescue Errno::ECHILD
159
+ # the signal handler managed to call waitpid() first;
160
+ # PTY::ChildExited will be delivered pretty soon, so just wait for it
161
+ sleep 1
162
+ end
163
+ end
164
+ rescue PTY::ChildExited => e
165
+ return ShellOut::after(e.status.exitstatus, out_stream, *args)
166
+ ensure
167
+ system "stty #{ old_state }"
168
+ end
169
+
170
+ def shell_out_with_system(*args)
171
+ return SUCCESS_EXIT_STATUS unless ShellOut::before(*args)
172
+
173
+ cleaned_args = if args.last.is_a?(Hash)
174
+ cleaned_options = args.last.dup.delete_if { |k, | [ :verbose, :raise_exceptions ].include?(k) }
175
+ require "stringio"
176
+ if cleaned_options[:out].is_a?(StringIO) ||
177
+ cleaned_options[:out] == :return
178
+ r, w = IO.pipe
179
+ cleaned_options[:out] = w
180
+ cleaned_options[:err] = [ :child, :out ]
181
+ end
182
+ if cleaned_options[:in].is_a?(StringIO)
183
+ in_r, in_w = IO.pipe
184
+ in_w.write cleaned_options[:in].read
185
+ in_w.close
186
+ cleaned_options[:in] = in_r
187
+ end
188
+ cleaned_options.empty? ? args[0..-2] : args[0..-2].dup << cleaned_options
189
+ else
190
+ args
191
+ end
192
+
193
+ exitstatus = if Kernel.system(*cleaned_args)
194
+ SUCCESS_EXIT_STATUS
195
+ else
196
+ require "English"
197
+ $CHILD_STATUS.exitstatus
198
+ end
199
+
200
+ if r
201
+ w.close
202
+ unless args.last[:out] == :return
203
+ args.last[:out] << r.read
204
+ end
205
+ end
206
+
207
+ ShellOut::after(exitstatus, r, *args)
208
+ end
209
+
210
+ begin
211
+ require "pty"
212
+ alias shell_out shell_out_with_pty
213
+ rescue LoadError
214
+ alias shell_out shell_out_with_system
215
+ end
216
+
217
+ module_function :shell_out
218
+ end
219
+
220
+ def ShellOut(*args)
221
+ ShellOut.shell_out(*args)
222
+ end
223
+
224
+ if $PROGRAM_NAME == __FILE__
225
+ require "spec"
226
+ require "stringio"
227
+ require "./io_interceptor.rb"
228
+
229
+ describe "ShellOut" do
230
+ before do
231
+ STDOUT.extend(IoInterceptor)
232
+ end
233
+
234
+ share_examples_for "having base capabilities" do
235
+
236
+ it "shells out to successful command and returns 0 exit code" do
237
+ ShellOut("ruby -e ''").should == 0
238
+ end
239
+
240
+ it "passes arbitrary exit codes" do
241
+ ShellOut("ruby -e 'exit(42)'").should == 42
242
+ end
243
+
244
+ it "passes STDIN (or options[:in]) stream to the command" do
245
+ fake_stdin = StringIO.new
246
+ fake_stdin.write "testing\n"
247
+ fake_stdin.rewind
248
+
249
+ STDOUT.supress do # TODO still mystery why "testing\n" gets written to the output
250
+ ShellOut("ruby -e 'exit(123) if gets.chomp == \"testing\"'", :in => fake_stdin).should == 123
251
+ end
252
+ end
253
+
254
+ describe ":raise_exceptions option" do
255
+ it "raises exception for non-zero exit codes" do
256
+ lambda do
257
+ ShellOut("false", :raise_exceptions => true)
258
+ end.should raise_error(ShellOut::ShellOutException)
259
+ end
260
+
261
+ it "raises exception for non-existing command" do
262
+ lambda do
263
+ ShellOut("nonexisting", :raise_exceptions => true)
264
+ end.should raise_error(ShellOut::ShellOutException)
265
+ end
266
+ end
267
+
268
+ describe ":out option" do
269
+ it "redirects output of command" do
270
+ fake_stdout = StringIO.new
271
+ ShellOut("echo 42", :out => fake_stdout)
272
+ fake_stdout.string.chomp.should == "42"
273
+ end
274
+ end
275
+
276
+ describe ":out => :return option" do
277
+ it "return the output of command" do
278
+ ShellOut("echo 42", :out => :return).chomp.should == "42"
279
+ end
280
+
281
+ it "return STDERR output of command" do
282
+ ShellOut("ruby -e 'STDERR.puts 42'", :out => :return).chomp.should == "42"
283
+ end
284
+ end
285
+
286
+ describe ":verbose option" do
287
+ it "echoes the command name" do
288
+ STDOUT.intercept { ShellOut("true", :verbose => true) }.should include("true")
289
+ end
290
+ end
291
+
292
+ def with_vanishing_file(f)
293
+ yield
294
+ ensure
295
+ File.delete(f) rescue nil
296
+ end
297
+
298
+ describe ":noop option" do
299
+ it "always returns 0 status" do
300
+ ShellOut("ruby -e 'exit 123'", :noop => true).should == 0
301
+ end
302
+
303
+ it "never executes command" do
304
+ f = "never_executes_command.test"
305
+ with_vanishing_file(f) do
306
+ ShellOut("touch #{ f }", :noop => true)
307
+ File.exist?(f).should == false
308
+ end
309
+ end
310
+ end
311
+
312
+ describe ":dry_run option" do
313
+ it "echoes the command name" do
314
+ STDOUT.intercept { ShellOut("true", :dry_run => true) }.should include("true")
315
+ end
316
+
317
+ it "always returns 0 status" do
318
+ STDOUT.supress { ShellOut("ruby -e 'exit 123'", :dry_run => true) }.should == 0
319
+ end
320
+
321
+ it "never executes command" do
322
+ f = "never_executes_command.test"
323
+ with_vanishing_file(f) do
324
+ STDOUT.supress { ShellOut("touch #{ f }", :dry_run => true) }
325
+ File.exist?(f).should == false
326
+ end
327
+ end
328
+ end
329
+ end
330
+
331
+ describe "#shell_out_with_system" do
332
+ before do
333
+ def ShellOut(*args)
334
+ ShellOut.shell_out_with_system(*args);
335
+ end
336
+ end
337
+
338
+ it_should_behave_like "having base capabilities"
339
+ end
340
+
341
+ describe "#shell_out_with_pty" do
342
+ begin
343
+ require "pty"
344
+ before do
345
+ def ShellOut(*args)
346
+ ShellOut.shell_out_with_pty(*args);
347
+ end
348
+ end
349
+
350
+ it_should_behave_like "having base capabilities"
351
+
352
+ it "uses pseudo-tty" do
353
+ ShellOut("ruby -e 'exit(123) if [STDIN, STDOUT, STDERR].all? { |stream| stream.tty? }'").should == 123
354
+ end
355
+
356
+ it "shows the output of the executing command on own STDOUT" do
357
+ STDOUT.intercept { ShellOut("echo '42'") }.should include("42")
358
+ end
359
+
360
+ it "doesn't pass Ctrl-C to the command and raises Interrupt exception when Ctrl-C is sent" do
361
+ fake_stdin = StringIO.new
362
+ fake_stdin.write ShellOut::CTRL_C_CODE
363
+ fake_stdin.rewind
364
+ int_trap = "ruby -e 'trap(\"INT\") { puts \"SIGINT received\" }; sleep 999'"
365
+ lambda do
366
+ ShellOut(int_trap, :in => fake_stdin)
367
+ end.should raise_error(Interrupt)
368
+
369
+ fake_stdin = StringIO.new
370
+ fake_stdin.write ShellOut::CTRL_C_CODE
371
+ fake_stdin.rewind
372
+ STDOUT.intercept do
373
+ begin
374
+ ShellOut(int_trap, :in => fake_stdin)
375
+ rescue Interrupt
376
+ end
377
+ end.should_not include("SIGINT received")
378
+ end
379
+ rescue LoadError
380
+ it "cannot be tested because 'pty' is not available on this system"
381
+ end
382
+ end
383
+ end
384
+
385
+ exit ::Spec::Runner::CommandLine.run
386
+ end