session 2.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.
- data/lib/session-2.4.0.rb +744 -0
- data/lib/session.rb +744 -0
- data/test/session.rb +227 -0
- metadata +41 -0
@@ -0,0 +1,744 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'thread'
|
4
|
+
require 'yaml'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
module Session
|
8
|
+
#--{{{
|
9
|
+
VERSION = '2.4.0'
|
10
|
+
|
11
|
+
@track_history = ENV['SESSION_HISTORY'] || ENV['SESSION_TRACK_HISTORY']
|
12
|
+
@use_spawn = ENV['SESSION_USE_SPAWN']
|
13
|
+
@use_open3 = ENV['SESSION_USE_OPEN3']
|
14
|
+
@debug = ENV['SESSION_DEBUG']
|
15
|
+
|
16
|
+
class << self
|
17
|
+
#--{{{
|
18
|
+
attr :track_history, true
|
19
|
+
attr :use_spawn, true
|
20
|
+
attr :use_open3, true
|
21
|
+
attr :debug, true
|
22
|
+
def new(*a, &b)
|
23
|
+
#--{{{
|
24
|
+
Sh::new(*a, &b)
|
25
|
+
#--}}}
|
26
|
+
end
|
27
|
+
alias [] new
|
28
|
+
#--}}}
|
29
|
+
end
|
30
|
+
|
31
|
+
class PipeError < StandardError; end
|
32
|
+
class ExecutionError < StandardError; end
|
33
|
+
|
34
|
+
class History
|
35
|
+
#--{{{
|
36
|
+
def initialize; @a = []; end
|
37
|
+
def method_missing(m,*a,&b); @a.send(m,*a,&b); end
|
38
|
+
def to_yaml(*a,&b); @a.to_yaml(*a,&b); end
|
39
|
+
alias to_s to_yaml
|
40
|
+
alias to_str to_yaml
|
41
|
+
#--}}}
|
42
|
+
end # class History
|
43
|
+
class Command
|
44
|
+
#--{{{
|
45
|
+
class << self
|
46
|
+
#--{{{
|
47
|
+
def cmdno; @cmdno ||= 0; end
|
48
|
+
def cmdno= n; @cmdno = n; end
|
49
|
+
#--}}}
|
50
|
+
end
|
51
|
+
|
52
|
+
# attributes
|
53
|
+
#--{{{
|
54
|
+
attr :cmd
|
55
|
+
attr :cmdno
|
56
|
+
attr :out,true
|
57
|
+
attr :err,true
|
58
|
+
attr :cid
|
59
|
+
attr :begin_out
|
60
|
+
attr :end_out
|
61
|
+
attr :begin_out_pat
|
62
|
+
attr :end_out_pat
|
63
|
+
attr :begin_err
|
64
|
+
attr :end_err
|
65
|
+
attr :begin_err_pat
|
66
|
+
attr :end_err_pat
|
67
|
+
#--}}}
|
68
|
+
|
69
|
+
def initialize(command)
|
70
|
+
#--{{{
|
71
|
+
@cmd = command.to_s
|
72
|
+
@cmdno = self.class.cmdno
|
73
|
+
self.class.cmdno += 1
|
74
|
+
@err = ''
|
75
|
+
@out = ''
|
76
|
+
@cid = "%d_%d_%d" % [$$, cmdno, rand(Time.now.usec)]
|
77
|
+
@begin_out = "__CMD_OUT_%s_BEGIN__" % cid
|
78
|
+
@end_out = "__CMD_OUT_%s_END__" % cid
|
79
|
+
@begin_out_pat = %r/#{ Regexp.escape(@begin_out) }/
|
80
|
+
@end_out_pat = %r/#{ Regexp.escape(@end_out) }/
|
81
|
+
@begin_err = "__CMD_ERR_%s_BEGIN__" % cid
|
82
|
+
@end_err = "__CMD_ERR_%s_END__" % cid
|
83
|
+
@begin_err_pat = %r/#{ Regexp.escape(@begin_err) }/
|
84
|
+
@end_err_pat = %r/#{ Regexp.escape(@end_err) }/
|
85
|
+
#--}}}
|
86
|
+
end
|
87
|
+
def to_hash
|
88
|
+
#--{{{
|
89
|
+
%w(cmdno cmd out err cid).inject({}){|h,k| h.update k => send(k) }
|
90
|
+
#--}}}
|
91
|
+
end
|
92
|
+
def to_yaml(*a,&b)
|
93
|
+
#--{{{
|
94
|
+
to_hash.to_yaml(*a,&b)
|
95
|
+
#--}}}
|
96
|
+
end
|
97
|
+
alias to_s to_yaml
|
98
|
+
alias to_str to_yaml
|
99
|
+
#--}}}
|
100
|
+
end # class Command
|
101
|
+
class AbstractSession
|
102
|
+
#--{{{
|
103
|
+
|
104
|
+
# class methods
|
105
|
+
class << self
|
106
|
+
#--{{{
|
107
|
+
def default_prog
|
108
|
+
#--{{{
|
109
|
+
return @default_prog if defined? @default_prog and @default_prog
|
110
|
+
if defined? self::DEFAULT_PROG
|
111
|
+
return @default_prog = self::DEFAULT_PROG
|
112
|
+
else
|
113
|
+
@default_prog = ENV["SESSION_#{ self }_PROG"]
|
114
|
+
end
|
115
|
+
nil
|
116
|
+
#--}}}
|
117
|
+
end
|
118
|
+
def default_prog= prog
|
119
|
+
#--{{{
|
120
|
+
@default_prog = prog
|
121
|
+
#--}}}
|
122
|
+
end
|
123
|
+
attr :track_history, true
|
124
|
+
attr :use_spawn, true
|
125
|
+
attr :use_open3, true
|
126
|
+
attr :debug, true
|
127
|
+
def init
|
128
|
+
#--{{{
|
129
|
+
@track_history = nil
|
130
|
+
@use_spawn = nil
|
131
|
+
@use_open3 = nil
|
132
|
+
@debug = nil
|
133
|
+
#--}}}
|
134
|
+
end
|
135
|
+
alias [] new
|
136
|
+
#--}}}
|
137
|
+
end
|
138
|
+
|
139
|
+
# class init
|
140
|
+
init
|
141
|
+
|
142
|
+
# attributes
|
143
|
+
#--{{{
|
144
|
+
attr :opts
|
145
|
+
attr :prog
|
146
|
+
attr :stdin
|
147
|
+
alias i stdin
|
148
|
+
attr :stdout
|
149
|
+
alias o stdout
|
150
|
+
attr :stderr
|
151
|
+
alias e stderr
|
152
|
+
attr :history
|
153
|
+
attr :track_history
|
154
|
+
attr :outproc, true
|
155
|
+
attr :errproc, true
|
156
|
+
attr :use_spawn
|
157
|
+
attr :use_open3
|
158
|
+
attr :debug, true
|
159
|
+
alias debug? debug
|
160
|
+
attr :threads
|
161
|
+
#--}}}
|
162
|
+
|
163
|
+
# instance methods
|
164
|
+
def initialize(*args)
|
165
|
+
#--{{{
|
166
|
+
@opts = hashify(*args)
|
167
|
+
|
168
|
+
@prog = getopt('prog', opts, getopt('program', opts, self.class::default_prog))
|
169
|
+
|
170
|
+
raise(ArgumentError, "no program specified") unless @prog
|
171
|
+
|
172
|
+
@track_history = nil
|
173
|
+
@track_history = Session::track_history unless Session::track_history.nil?
|
174
|
+
@track_history = self.class::track_history unless self.class::track_history.nil?
|
175
|
+
@track_history = getopt('history', opts) if hasopt('history', opts)
|
176
|
+
@track_history = getopt('track_history', opts) if hasopt('track_history', opts)
|
177
|
+
|
178
|
+
@use_spawn = nil
|
179
|
+
@use_spawn = Session::use_spawn unless Session::use_spawn.nil?
|
180
|
+
@use_spawn = self.class::use_spawn unless self.class::use_spawn.nil?
|
181
|
+
@use_spawn = getopt('use_spawn', opts) if hasopt('use_spawn', opts)
|
182
|
+
|
183
|
+
@use_open3 = nil
|
184
|
+
@use_open3 = Session::use_open3 unless Session::use_open3.nil?
|
185
|
+
@use_open3 = self.class::use_open3 unless self.class::use_open3.nil?
|
186
|
+
@use_open3 = getopt('use_open3', opts) if hasopt('use_open3', opts)
|
187
|
+
|
188
|
+
@debug = nil
|
189
|
+
@debug = Session::debug unless Session::debug.nil?
|
190
|
+
@debug = self.class::debug unless self.class::debug.nil?
|
191
|
+
@debug = getopt('debug', opts) if hasopt('debug', opts)
|
192
|
+
|
193
|
+
@history = nil
|
194
|
+
@history = History::new if @track_history
|
195
|
+
|
196
|
+
@outproc = nil
|
197
|
+
@errproc = nil
|
198
|
+
|
199
|
+
@stdin, @stdout, @stderr =
|
200
|
+
if @use_spawn
|
201
|
+
Spawn::spawn @prog
|
202
|
+
elsif @use_open3
|
203
|
+
Open3::popen3 @prog
|
204
|
+
else
|
205
|
+
__popen3 @prog
|
206
|
+
end
|
207
|
+
|
208
|
+
@threads = []
|
209
|
+
|
210
|
+
clear
|
211
|
+
|
212
|
+
if block_given?
|
213
|
+
ret = nil
|
214
|
+
begin
|
215
|
+
ret = yield self
|
216
|
+
ensure
|
217
|
+
self.close!
|
218
|
+
end
|
219
|
+
return ret
|
220
|
+
end
|
221
|
+
|
222
|
+
return self
|
223
|
+
#--}}}
|
224
|
+
end
|
225
|
+
def getopt opt, hash, default = nil
|
226
|
+
#--{{{
|
227
|
+
key = opt
|
228
|
+
return hash[key] if hash.has_key? key
|
229
|
+
key = "#{ key }"
|
230
|
+
return hash[key] if hash.has_key? key
|
231
|
+
key = key.intern
|
232
|
+
return hash[key] if hash.has_key? key
|
233
|
+
return default
|
234
|
+
#--}}}
|
235
|
+
end
|
236
|
+
def hasopt opt, hash
|
237
|
+
#--{{{
|
238
|
+
key = opt
|
239
|
+
return key if hash.has_key? key
|
240
|
+
key = "#{ key }"
|
241
|
+
return key if hash.has_key? key
|
242
|
+
key = key.intern
|
243
|
+
return key if hash.has_key? key
|
244
|
+
return false
|
245
|
+
#--}}}
|
246
|
+
end
|
247
|
+
def __popen3(*cmd)
|
248
|
+
#--{{{
|
249
|
+
pw = IO::pipe # pipe[0] for read, pipe[1] for write
|
250
|
+
pr = IO::pipe
|
251
|
+
pe = IO::pipe
|
252
|
+
|
253
|
+
pid =
|
254
|
+
__fork{
|
255
|
+
# child
|
256
|
+
pw[1].close
|
257
|
+
STDIN.reopen(pw[0])
|
258
|
+
pw[0].close
|
259
|
+
|
260
|
+
pr[0].close
|
261
|
+
STDOUT.reopen(pr[1])
|
262
|
+
pr[1].close
|
263
|
+
|
264
|
+
pe[0].close
|
265
|
+
STDERR.reopen(pe[1])
|
266
|
+
pe[1].close
|
267
|
+
|
268
|
+
exec(*cmd)
|
269
|
+
}
|
270
|
+
|
271
|
+
Process::detach pid # avoid zombies
|
272
|
+
|
273
|
+
pw[0].close
|
274
|
+
pr[1].close
|
275
|
+
pe[1].close
|
276
|
+
pi = [pw[1], pr[0], pe[0]]
|
277
|
+
pw[1].sync = true
|
278
|
+
if defined? yield
|
279
|
+
begin
|
280
|
+
return yield(*pi)
|
281
|
+
ensure
|
282
|
+
pi.each{|p| p.close unless p.closed?}
|
283
|
+
end
|
284
|
+
end
|
285
|
+
pi
|
286
|
+
#--}}}
|
287
|
+
end
|
288
|
+
def __fork(*a, &b)
|
289
|
+
#--{{{
|
290
|
+
verbose = $VERBOSE
|
291
|
+
begin
|
292
|
+
$VERBOSE = nil
|
293
|
+
Kernel::fork(*a, &b)
|
294
|
+
ensure
|
295
|
+
$VERBOSE = verbose
|
296
|
+
end
|
297
|
+
#--}}}
|
298
|
+
end
|
299
|
+
|
300
|
+
# abstract methods
|
301
|
+
def clear
|
302
|
+
#--{{{
|
303
|
+
raise NotImplementedError
|
304
|
+
#--}}}
|
305
|
+
end
|
306
|
+
alias flush clear
|
307
|
+
def path
|
308
|
+
#--{{{
|
309
|
+
raise NotImplementedError
|
310
|
+
#--}}}
|
311
|
+
end
|
312
|
+
def path=
|
313
|
+
#--{{{
|
314
|
+
raise NotImplementedError
|
315
|
+
#--}}}
|
316
|
+
end
|
317
|
+
def send_command cmd
|
318
|
+
#--{{{
|
319
|
+
raise NotImplementedError
|
320
|
+
#--}}}
|
321
|
+
end
|
322
|
+
|
323
|
+
# concrete methods
|
324
|
+
def track_history= bool
|
325
|
+
#--{{{
|
326
|
+
@history ||= History::new
|
327
|
+
@track_history = bool
|
328
|
+
#--}}}
|
329
|
+
end
|
330
|
+
def ready?
|
331
|
+
#--{{{
|
332
|
+
(stdin and stdout and stderr) and
|
333
|
+
(IO === stdin and IO === stdout and IO === stderr) and
|
334
|
+
(not (stdin.closed? or stdout.closed? or stderr.closed?))
|
335
|
+
#--}}}
|
336
|
+
end
|
337
|
+
def close!
|
338
|
+
#--{{{
|
339
|
+
[stdin, stdout, stderr].each{|pipe| pipe.close}
|
340
|
+
stdin, stdout, stderr = nil, nil, nil
|
341
|
+
true
|
342
|
+
#--}}}
|
343
|
+
end
|
344
|
+
alias close close!
|
345
|
+
def hashify(*a)
|
346
|
+
#--{{{
|
347
|
+
a.inject({}){|o,h| o.update(h)}
|
348
|
+
#--}}}
|
349
|
+
end
|
350
|
+
private :hashify
|
351
|
+
def execute(command, redirects = {})
|
352
|
+
#--{{{
|
353
|
+
$session_command = command if @debug
|
354
|
+
|
355
|
+
raise(PipeError, command) unless ready?
|
356
|
+
|
357
|
+
# clear buffers
|
358
|
+
clear
|
359
|
+
|
360
|
+
# setup redirects
|
361
|
+
rerr = redirects[:e] || redirects[:err] || redirects[:stderr] ||
|
362
|
+
redirects['stderr'] || redirects['e'] || redirects['err'] ||
|
363
|
+
redirects[2] || redirects['2']
|
364
|
+
|
365
|
+
rout = redirects[:o] || redirects[:out] || redirects[:stdout] ||
|
366
|
+
redirects['stdout'] || redirects['o'] || redirects['out'] ||
|
367
|
+
redirects[1] || redirects['1']
|
368
|
+
|
369
|
+
# create cmd object and add to history
|
370
|
+
cmd = Command::new command.to_s
|
371
|
+
|
372
|
+
# store cmd if tracking history
|
373
|
+
history << cmd if track_history
|
374
|
+
|
375
|
+
# mutex for accessing shared data
|
376
|
+
mutex = Mutex::new
|
377
|
+
|
378
|
+
# io data for stderr and stdout
|
379
|
+
err = {
|
380
|
+
:io => stderr,
|
381
|
+
:cmd => cmd.err,
|
382
|
+
:name => 'stderr',
|
383
|
+
:begin => false,
|
384
|
+
:end => false,
|
385
|
+
:begin_pat => cmd.begin_err_pat,
|
386
|
+
:end_pat => cmd.end_err_pat,
|
387
|
+
:redirect => rerr,
|
388
|
+
:proc => errproc,
|
389
|
+
:yield => lambda{|buf| yield(nil, buf)},
|
390
|
+
:mutex => mutex,
|
391
|
+
}
|
392
|
+
out = {
|
393
|
+
:io => stdout,
|
394
|
+
:cmd => cmd.out,
|
395
|
+
:name => 'stdout',
|
396
|
+
:begin => false,
|
397
|
+
:end => false,
|
398
|
+
:begin_pat => cmd.begin_out_pat,
|
399
|
+
:end_pat => cmd.end_out_pat,
|
400
|
+
:redirect => rout,
|
401
|
+
:proc => outproc,
|
402
|
+
:yield => lambda{|buf| yield(buf, nil)},
|
403
|
+
:mutex => mutex,
|
404
|
+
}
|
405
|
+
|
406
|
+
begin
|
407
|
+
# send command in the background so we can begin processing output
|
408
|
+
# immediately - thanks to tanaka akira for this suggestion
|
409
|
+
threads << Thread::new { send_command cmd }
|
410
|
+
|
411
|
+
# init
|
412
|
+
main = Thread::current
|
413
|
+
exceptions = []
|
414
|
+
|
415
|
+
# fire off reader threads
|
416
|
+
[err, out].each do |iodat|
|
417
|
+
threads <<
|
418
|
+
Thread::new(iodat, main) do |iodat, main|
|
419
|
+
|
420
|
+
loop do
|
421
|
+
main.raise(PipeError, command) unless ready?
|
422
|
+
main.raise ExecutionError, iodat[:name] if iodat[:end] and not iodat[:begin]
|
423
|
+
|
424
|
+
break if iodat[:end] or iodat[:io].eof?
|
425
|
+
|
426
|
+
line = iodat[:io].gets
|
427
|
+
|
428
|
+
buf = nil
|
429
|
+
|
430
|
+
case line
|
431
|
+
when iodat[:end_pat]
|
432
|
+
iodat[:end] = true
|
433
|
+
# handle the special case of non-newline terminated output
|
434
|
+
if((m = %r/(.+)__CMD/o.match(line)) and (pre = m[1]))
|
435
|
+
buf = pre
|
436
|
+
end
|
437
|
+
when iodat[:begin_pat]
|
438
|
+
iodat[:begin] = true
|
439
|
+
else
|
440
|
+
next unless iodat[:begin] and not iodat[:end] # ignore chaff
|
441
|
+
buf = line
|
442
|
+
end
|
443
|
+
|
444
|
+
if buf
|
445
|
+
iodat[:mutex].synchronize do
|
446
|
+
iodat[:cmd] << buf
|
447
|
+
iodat[:redirect] << buf if iodat[:redirect]
|
448
|
+
iodat[:proc].call buf if iodat[:proc]
|
449
|
+
iodat[:yield].call buf if block_given?
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
true
|
455
|
+
end
|
456
|
+
end
|
457
|
+
ensure
|
458
|
+
# reap all threads - accumulating and rethrowing any exceptions
|
459
|
+
begin
|
460
|
+
while((t = threads.shift))
|
461
|
+
t.join
|
462
|
+
raise ExecutionError, 'iodat thread failure' unless t.value
|
463
|
+
end
|
464
|
+
rescue => e
|
465
|
+
exceptions << e
|
466
|
+
retry unless threads.empty?
|
467
|
+
ensure
|
468
|
+
unless exceptions.empty?
|
469
|
+
meta_message = '<' << exceptions.map{|e| "#{ e.message } - (#{ e.class })"}.join('|') << '>'
|
470
|
+
meta_backtrace = exceptions.map{|e| e.backtrace}.flatten
|
471
|
+
raise ExecutionError, meta_message, meta_backtrace
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# this should only happen if eof was reached before end pat
|
477
|
+
[err, out].each do |iodat|
|
478
|
+
raise ExecutionError, iodat[:name] unless iodat[:begin] and iodat[:end]
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
# get the exit status
|
483
|
+
get_status if respond_to? :get_status
|
484
|
+
|
485
|
+
out = err = iodat = nil
|
486
|
+
|
487
|
+
return [cmd.out, cmd.err]
|
488
|
+
#--}}}
|
489
|
+
end
|
490
|
+
#--}}}
|
491
|
+
end # class AbstractSession
|
492
|
+
class Sh < AbstractSession
|
493
|
+
#--{{{
|
494
|
+
DEFAULT_PROG = 'sh'
|
495
|
+
ECHO = 'echo'
|
496
|
+
|
497
|
+
attr :status
|
498
|
+
alias exit_status status
|
499
|
+
alias exitstatus status
|
500
|
+
|
501
|
+
def clear
|
502
|
+
#--{{{
|
503
|
+
stdin.puts "#{ ECHO } __clear__ 1>&2"
|
504
|
+
stdin.puts "#{ ECHO } __clear__"
|
505
|
+
stdin.flush
|
506
|
+
while((line = stderr.gets) and line !~ %r/__clear__/o); end
|
507
|
+
while((line = stdout.gets) and line !~ %r/__clear__/o); end
|
508
|
+
self
|
509
|
+
#--}}}
|
510
|
+
end
|
511
|
+
def send_command cmd
|
512
|
+
#--{{{
|
513
|
+
stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.begin_err
|
514
|
+
stdin.printf "%s '%s' \n", ECHO, cmd.begin_out
|
515
|
+
|
516
|
+
stdin.printf "%s\n", cmd.cmd
|
517
|
+
stdin.printf "export __exit_status__=$?\n"
|
518
|
+
|
519
|
+
stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.end_err
|
520
|
+
stdin.printf "%s '%s' \n", ECHO, cmd.end_out
|
521
|
+
|
522
|
+
stdin.flush
|
523
|
+
#--}}}
|
524
|
+
end
|
525
|
+
def get_status
|
526
|
+
#--{{{
|
527
|
+
@status = get_var '__exit_status__'
|
528
|
+
unless @status =~ /^\s*\d+\s*$/o
|
529
|
+
raise ExecutionError, "could not determine exit status from <#{ @status.inspect }>"
|
530
|
+
end
|
531
|
+
|
532
|
+
@status = Integer @status
|
533
|
+
#--}}}
|
534
|
+
end
|
535
|
+
def set_var name, value
|
536
|
+
#--{{{
|
537
|
+
stdin.puts "export #{ name }=#{ value }"
|
538
|
+
stdin.flush
|
539
|
+
#--}}}
|
540
|
+
end
|
541
|
+
def get_var name
|
542
|
+
#--{{{
|
543
|
+
stdin.puts "#{ ECHO } \"#{ name }=${#{ name }}\""
|
544
|
+
stdin.flush
|
545
|
+
|
546
|
+
var = nil
|
547
|
+
while((line = stdout.gets))
|
548
|
+
m = %r/#{ name }\s*=\s*(.*)/.match line
|
549
|
+
if m
|
550
|
+
var = m[1]
|
551
|
+
raise ExecutionError, "could not determine <#{ name }> from <#{ line.inspect }>" unless var
|
552
|
+
break
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
var
|
557
|
+
#--}}}
|
558
|
+
end
|
559
|
+
def path
|
560
|
+
#--{{{
|
561
|
+
var = get_var 'PATH'
|
562
|
+
var.strip.split %r/:/o
|
563
|
+
#--}}}
|
564
|
+
end
|
565
|
+
def path= arg
|
566
|
+
#--{{{
|
567
|
+
case arg
|
568
|
+
when Array
|
569
|
+
arg = arg.join ':'
|
570
|
+
else
|
571
|
+
arg = arg.to_s.strip
|
572
|
+
end
|
573
|
+
|
574
|
+
set_var 'PATH', "'#{ arg }'"
|
575
|
+
self.path
|
576
|
+
#--}}}
|
577
|
+
end
|
578
|
+
def execute(command, redirects = {}, &block)
|
579
|
+
#--{{{
|
580
|
+
# setup redirect on stdin
|
581
|
+
rin = redirects[:i] || redirects[:in] || redirects[:stdin] ||
|
582
|
+
redirects['stdin'] || redirects['i'] || redirects['in'] ||
|
583
|
+
redirects[0] || redirects['0']
|
584
|
+
|
585
|
+
if rin
|
586
|
+
tmp =
|
587
|
+
begin
|
588
|
+
Tempfile::new rand.to_s
|
589
|
+
rescue
|
590
|
+
Tempfile::new rand.to_s
|
591
|
+
end
|
592
|
+
|
593
|
+
begin
|
594
|
+
tmp.write(
|
595
|
+
if rin.respond_to? 'read'
|
596
|
+
rin.read
|
597
|
+
elsif rin.respond_to? 'to_s'
|
598
|
+
rin.to_s
|
599
|
+
else
|
600
|
+
rin
|
601
|
+
end
|
602
|
+
)
|
603
|
+
tmp.flush
|
604
|
+
command = "{ #{ command } ;} < #{ tmp.path }"
|
605
|
+
#puts command
|
606
|
+
super(command, redirects, &block)
|
607
|
+
ensure
|
608
|
+
tmp.close! if tmp
|
609
|
+
end
|
610
|
+
|
611
|
+
else
|
612
|
+
super
|
613
|
+
end
|
614
|
+
#--}}}
|
615
|
+
end
|
616
|
+
#--}}}
|
617
|
+
end # class Sh
|
618
|
+
class Bash < Sh
|
619
|
+
#--{{{
|
620
|
+
DEFAULT_PROG = 'bash'
|
621
|
+
class Login < Bash
|
622
|
+
DEFAULT_PROG = 'bash --login'
|
623
|
+
end
|
624
|
+
#--}}}
|
625
|
+
end # class Bash
|
626
|
+
class Shell < Bash; end
|
627
|
+
# IDL => interactive data language - see http://www.rsinc.com/
|
628
|
+
class IDL < AbstractSession
|
629
|
+
#--{{{
|
630
|
+
class LicenseManagerError < StandardError; end
|
631
|
+
DEFAULT_PROG = 'idl'
|
632
|
+
MAX_TRIES = 32
|
633
|
+
def initialize(*args)
|
634
|
+
#--{{{
|
635
|
+
tries = 0
|
636
|
+
ret = nil
|
637
|
+
begin
|
638
|
+
ret = super
|
639
|
+
rescue LicenseManagerError => e
|
640
|
+
tries += 1
|
641
|
+
if tries < MAX_TRIES
|
642
|
+
sleep 1
|
643
|
+
retry
|
644
|
+
else
|
645
|
+
raise LicenseManagerError, "<#{ MAX_TRIES }> attempts <#{ e.message }>"
|
646
|
+
end
|
647
|
+
end
|
648
|
+
ret
|
649
|
+
#--}}}
|
650
|
+
end
|
651
|
+
def clear
|
652
|
+
#--{{{
|
653
|
+
stdin.puts "retall"
|
654
|
+
stdin.puts "printf, -2, '__clear__'"
|
655
|
+
stdin.puts "printf, -1, '__clear__'"
|
656
|
+
stdin.flush
|
657
|
+
while((line = stderr.gets) and line !~ %r/__clear__/o)
|
658
|
+
raise LicenseManagerError, line if line =~ %r/license\s*manager/io
|
659
|
+
end
|
660
|
+
while((line = stdout.gets) and line !~ %r/__clear__/o)
|
661
|
+
raise LicenseManagerError, line if line =~ %r/license\s*manager/io
|
662
|
+
end
|
663
|
+
self
|
664
|
+
#--}}}
|
665
|
+
end
|
666
|
+
def send_command cmd
|
667
|
+
#--{{{
|
668
|
+
stdin.printf "printf, -2, '%s'\n", cmd.begin_err
|
669
|
+
stdin.printf "printf, -1, '%s'\n", cmd.begin_out
|
670
|
+
|
671
|
+
stdin.printf "%s\n", cmd.cmd
|
672
|
+
stdin.printf "retall\n"
|
673
|
+
|
674
|
+
stdin.printf "printf, -2, '%s'\n", cmd.end_err
|
675
|
+
stdin.printf "printf, -1, '%s'\n", cmd.end_out
|
676
|
+
stdin.flush
|
677
|
+
#--}}}
|
678
|
+
end
|
679
|
+
def path
|
680
|
+
#--{{{
|
681
|
+
stdout, stderr = execute "print, !path"
|
682
|
+
stdout.strip.split %r/:/o
|
683
|
+
#--}}}
|
684
|
+
end
|
685
|
+
def path= arg
|
686
|
+
#--{{{
|
687
|
+
case arg
|
688
|
+
when Array
|
689
|
+
arg = arg.join ':'
|
690
|
+
else
|
691
|
+
arg = arg.to_s.strip
|
692
|
+
end
|
693
|
+
stdout, stderr = execute "!path='#{ arg }'"
|
694
|
+
|
695
|
+
self.path
|
696
|
+
#--}}}
|
697
|
+
end
|
698
|
+
#--}}}
|
699
|
+
end # class IDL
|
700
|
+
module Spawn
|
701
|
+
#--{{{
|
702
|
+
class << self
|
703
|
+
def spawn command
|
704
|
+
#--{{{
|
705
|
+
ipath = tmpfifo
|
706
|
+
opath = tmpfifo
|
707
|
+
epath = tmpfifo
|
708
|
+
|
709
|
+
cmd = "#{ command } < #{ ipath } 1> #{ opath } 2> #{ epath } &"
|
710
|
+
system cmd
|
711
|
+
|
712
|
+
i = open ipath, 'w'
|
713
|
+
o = open opath, 'r'
|
714
|
+
e = open epath, 'r'
|
715
|
+
|
716
|
+
[i,o,e]
|
717
|
+
#--}}}
|
718
|
+
end
|
719
|
+
def tmpfifo
|
720
|
+
#--{{{
|
721
|
+
path = nil
|
722
|
+
42.times do |i|
|
723
|
+
tpath = File::join(Dir::tmpdir, "#{ $$ }.#{ rand }.#{ i }")
|
724
|
+
v = $VERBOSE
|
725
|
+
begin
|
726
|
+
$VERBOSE = nil
|
727
|
+
system "mkfifo #{ tpath }"
|
728
|
+
ensure
|
729
|
+
$VERBOSE = v
|
730
|
+
end
|
731
|
+
next unless $? == 0
|
732
|
+
path = tpath
|
733
|
+
at_exit{ File::unlink(path) rescue STDERR.puts("rm <#{ path }> failed") }
|
734
|
+
break
|
735
|
+
end
|
736
|
+
raise "could not generate tmpfifo" unless path
|
737
|
+
path
|
738
|
+
#--}}}
|
739
|
+
end
|
740
|
+
end
|
741
|
+
#--}}}
|
742
|
+
end # module Spawn
|
743
|
+
#--}}}
|
744
|
+
end # module Session
|