PageRenderTime 60ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/submodules/chuckr/vendor/ruby-session/lib/session.rb

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