dolzenko 0.0.10 → 0.0.11

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.
@@ -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