rvt 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +25 -0
  4. data/app/assets/javascripts/rvt/application.js +1 -0
  5. data/app/assets/javascripts/rvt/console_sessions.js +182 -0
  6. data/app/assets/stylesheets/rvt/application.css +13 -0
  7. data/app/assets/stylesheets/rvt/console_sessions.css.erb +6 -0
  8. data/app/controllers/rvt/application_controller.rb +13 -0
  9. data/app/controllers/rvt/console_sessions_controller.rb +47 -0
  10. data/app/models/rvt/console_session.rb +109 -0
  11. data/app/views/layouts/rvt/application.html.erb +14 -0
  12. data/app/views/rvt/console_sessions/index.html.erb +17 -0
  13. data/config/routes.rb +11 -0
  14. data/lib/assets/javascripts/rvt.js +41 -0
  15. data/lib/rvt.rb +21 -0
  16. data/lib/rvt/colors.rb +87 -0
  17. data/lib/rvt/colors/light.rb +24 -0
  18. data/lib/rvt/colors/monokai.rb +24 -0
  19. data/lib/rvt/colors/solarized.rb +47 -0
  20. data/lib/rvt/colors/tango.rb +24 -0
  21. data/lib/rvt/colors/xterm.rb +24 -0
  22. data/lib/rvt/engine.rb +85 -0
  23. data/lib/rvt/slave.rb +147 -0
  24. data/lib/rvt/version.rb +3 -0
  25. data/test/controllers/rvt/console_sessions_controller_test.rb +100 -0
  26. data/test/dummy/Rakefile +6 -0
  27. data/test/dummy/app/assets/javascripts/application.js +13 -0
  28. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  29. data/test/dummy/app/controllers/application_controller.rb +5 -0
  30. data/test/dummy/app/helpers/application_helper.rb +2 -0
  31. data/test/dummy/app/views/controller_helper_test/index.html.erb +1 -0
  32. data/test/dummy/app/views/exception_test/xhr.html.erb +1 -0
  33. data/test/dummy/app/views/helper_test/index.html.erb +220 -0
  34. data/test/dummy/app/views/layouts/application.html.erb +16 -0
  35. data/test/dummy/bin/bundle +3 -0
  36. data/test/dummy/bin/rails +4 -0
  37. data/test/dummy/bin/rake +4 -0
  38. data/test/dummy/config.ru +4 -0
  39. data/test/dummy/config/application.rb +44 -0
  40. data/test/dummy/config/boot.rb +5 -0
  41. data/test/dummy/config/database.yml +25 -0
  42. data/test/dummy/config/environment.rb +5 -0
  43. data/test/dummy/config/environments/development.rb +29 -0
  44. data/test/dummy/config/environments/production.rb +80 -0
  45. data/test/dummy/config/environments/test.rb +34 -0
  46. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  47. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  48. data/test/dummy/config/initializers/inflections.rb +16 -0
  49. data/test/dummy/config/initializers/mime_types.rb +5 -0
  50. data/test/dummy/config/initializers/secret_token.rb +12 -0
  51. data/test/dummy/config/initializers/session_store.rb +3 -0
  52. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  53. data/test/dummy/config/locales/en.yml +23 -0
  54. data/test/dummy/config/routes.rb +2 -0
  55. data/test/dummy/db/development.sqlite3 +0 -0
  56. data/test/dummy/db/schema.rb +16 -0
  57. data/test/dummy/db/test.sqlite3 +0 -0
  58. data/test/dummy/log/development.log +247222 -0
  59. data/test/dummy/log/test.log +963 -0
  60. data/test/dummy/public/404.html +58 -0
  61. data/test/dummy/public/422.html +58 -0
  62. data/test/dummy/public/500.html +57 -0
  63. data/test/dummy/public/favicon.ico +0 -0
  64. data/test/dummy/tmp/cache/assets/development/sprockets/0509a6e0e75d9ac5a88bba7291b04686 +0 -0
  65. data/test/dummy/tmp/cache/assets/development/sprockets/2bd9d10dae311aa2dfb6dea9c1e0ad50 +0 -0
  66. data/test/dummy/tmp/cache/assets/development/sprockets/2f41a6a41d0a16db31cabd2b8689ca00 +0 -0
  67. data/test/dummy/tmp/cache/assets/development/sprockets/4de66e84a0d6f009fac50cd608fb8581 +0 -0
  68. data/test/dummy/tmp/cache/assets/development/sprockets/56e8026311075410507152df2aea4307 +0 -0
  69. data/test/dummy/tmp/cache/assets/development/sprockets/9542de15712d45f70221931bf78c00e5 +0 -0
  70. data/test/dummy/tmp/cache/assets/development/sprockets/98cea396a602c53d876e79bf4b89de3b +0 -0
  71. data/test/dummy/tmp/cache/assets/development/sprockets/9df3968b0f171feec749766fffa50b2e +0 -0
  72. data/test/dummy/tmp/cache/assets/development/sprockets/aa5fc4cb46c5294192c9d3af8824f88b +0 -0
  73. data/test/dummy/tmp/cache/assets/development/sprockets/ac7197dc7dd9ab362d915d19e37a8e01 +0 -0
  74. data/test/dummy/tmp/cache/assets/development/sprockets/b238befd6eff46b26d56322c1450ea45 +0 -0
  75. data/test/dummy/tmp/cache/assets/development/sprockets/c69b8b79c21fd48ad84c3fad87945a5c +0 -0
  76. data/test/dummy/tmp/cache/assets/development/sprockets/da4318a4d8364d0616edd706508371bc +0 -0
  77. data/test/dummy/tmp/cache/assets/development/sprockets/dd71b14df42fd6dc95355cded9e4d0f9 +0 -0
  78. data/test/dummy/tmp/cache/assets/development/sprockets/eb06cae1627276e46965809e766aff13 +0 -0
  79. data/test/dummy/tmp/cache/assets/development/sprockets/edffb5017d27ddb65965203636286405 +0 -0
  80. data/test/dummy/tmp/cache/assets/development/sprockets/f0ced5f3d4a75fce1c596d6c3f97a42d +0 -0
  81. data/test/dummy/tmp/cache/assets/development/sprockets/fb9e611526e612ba797f0c11b987826b +0 -0
  82. data/test/models/console_session_test.rb +58 -0
  83. data/test/rvt/colors_test.rb +58 -0
  84. data/test/rvt/engine_test.rb +145 -0
  85. data/test/rvt/slave_test.rb +72 -0
  86. data/test/test_helper.rb +27 -0
  87. data/vendor/assets/javascripts/term.js +5771 -0
  88. metadata +284 -0
@@ -0,0 +1,17 @@
1
+ <script>
2
+ var config = {
3
+ terminal: {
4
+ colors: <%= raw RVT.config.style.colors.to_json %>
5
+ },
6
+
7
+ transport: {
8
+ url: {
9
+ input: "<%= rvt.input_console_session_path(@console_session) %>",
10
+ pendingOutput: "<%= rvt.pending_output_console_session_path(@console_session) %>",
11
+ configuration: "<%= rvt.configuration_console_session_path(@console_session) %>"
12
+ },
13
+
14
+ uid: "<%= @console_session.uid %>"
15
+ }
16
+ };
17
+ </script>
@@ -0,0 +1,11 @@
1
+ RVT::Engine.routes.draw do
2
+ root to: 'console_sessions#index'
3
+
4
+ resources :console_sessions do
5
+ member do
6
+ put :input
7
+ get :pending_output
8
+ put :configuration
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ //= require term
2
+
3
+ ;(function(BaseTerminal) {
4
+
5
+ // Expose the main RVT namespace.
6
+ var RVT = this.RVT = {};
7
+
8
+ // Follow term.js example and expose inherits and EventEmitter.
9
+ var inherits = RVT.inherits = BaseTerminal.inherits;
10
+ var EventEmitter = RVT.EventEmitter = BaseTerminal.EventEmitter;
11
+
12
+ var Terminal = RVT.Terminal = function(options) {
13
+ if (typeof options === 'number') {
14
+ return BaseTerminal.apply(this, arguments);
15
+ }
16
+
17
+ BaseTerminal.call(this, options || (options = {}));
18
+
19
+ this.open();
20
+
21
+ if (!(options.rows || options.cols) || !options.geometry) {
22
+ this.fitScreen();
23
+ }
24
+ };
25
+
26
+ // Make RVT.Terminal inherit from BaseTerminal (term.js).
27
+ inherits(Terminal, BaseTerminal);
28
+
29
+ Terminal.prototype.fitScreen = function() {
30
+ var width = Math.floor(this.element.clientWidth / this.cols);
31
+ var height = Math.floor(this.element.clientHeight / this.rows);
32
+
33
+ var rows = Math.floor(window.innerHeight / height);
34
+ var cols = Math.floor(this.parent.clientWidth / width);
35
+
36
+ this.resize(cols, rows);
37
+
38
+ return [cols, rows];
39
+ };
40
+
41
+ }).call(this, Terminal);
@@ -0,0 +1,21 @@
1
+ require 'active_support/lazy_load_hooks'
2
+ require 'active_support/inflector'
3
+
4
+ require 'rvt/colors'
5
+ require 'rvt/engine'
6
+ require 'rvt/slave'
7
+
8
+ module RVT
9
+ # Shortcut for +RVT::Engine.config.rvt+.
10
+ def self.config
11
+ Engine.config.rvt
12
+ end
13
+
14
+ ActiveSupport.run_load_hooks(:rvt, self)
15
+ end
16
+
17
+ # Inflect the name as an acronym to help Rails auto loading find constants
18
+ # under +RVT::+ instead of +Rvt::+.
19
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
20
+ inflect.acronym 'RVT'
21
+ end
@@ -0,0 +1,87 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module RVT
4
+ # = Colors
5
+ #
6
+ # Manages the creation and serialization of terminal color themes.
7
+ #
8
+ # Colors is a subclass of +Array+ and it stores a collection of CSS color
9
+ # values, to be used from the client-side terminal.
10
+ #
11
+ # You can specify 8 or 16 colors and additional +background+ and +foreground+
12
+ # colors. If not explicitly specified, +background+ and +foreground+ are
13
+ # considered to be the first and the last of the given colors.
14
+ class Colors < Array
15
+ class << self
16
+ # Registry of color themes mapped to a name.
17
+ #
18
+ # Don't manually alter the registry. Use RVT::Colors.register_theme
19
+ # for adding entries.
20
+ def themes
21
+ @@themes ||= {}.with_indifferent_access
22
+ end
23
+
24
+ # Register a color theme into the color themes registry.
25
+ #
26
+ # Registration maps a name and Colors instance.
27
+ #
28
+ # If a block is given, it would be yielded with a new Colors instance to
29
+ # populate the theme colors in.
30
+ #
31
+ # If a Colors instance is already instantiated it can be passed directly
32
+ # as the second (_colors_) argument. In this case, if a block is given,
33
+ # it won't be executed.
34
+ def register_theme(name, colors = nil)
35
+ themes[name] = colors || new.tap { |c| yield c }
36
+ end
37
+
38
+ # The default colors theme.
39
+ def default
40
+ self[:light]
41
+ end
42
+
43
+ # Shortcut for RVT::Colors.themes#[].
44
+ def [](name)
45
+ themes[name]
46
+ end
47
+ end
48
+
49
+ alias :add :<<
50
+
51
+ # Background color getter and setter.
52
+ #
53
+ # If called without arguments it acts like a getter. Otherwise it acts like
54
+ # a setter.
55
+ #
56
+ # The default background color will be the first entry in the colors theme.
57
+ def background(value = nil)
58
+ @background = value unless value.nil?
59
+ @background ||= self.first
60
+ end
61
+
62
+ alias :background= :background
63
+
64
+ # Foreground color getter and setter.
65
+ #
66
+ # If called without arguments it acts like a getter. Otherwise it acts like
67
+ # a setter.
68
+ #
69
+ # The default foreground color will be the last entry in the colors theme.
70
+ def foreground(value = nil)
71
+ @foreground = value unless value.nil?
72
+ @foreground ||= self.last
73
+ end
74
+
75
+ alias :foreground= :foreground
76
+
77
+ def as_json(*)
78
+ (dup << background << foreground).to_a
79
+ end
80
+ end
81
+ end
82
+
83
+ require 'rvt/colors/light'
84
+ require 'rvt/colors/monokai'
85
+ require 'rvt/colors/solarized'
86
+ require 'rvt/colors/tango'
87
+ require 'rvt/colors/xterm'
@@ -0,0 +1,24 @@
1
+ module RVT
2
+ Colors.register_theme(:light) do |c|
3
+ c.add '#000000'
4
+ c.add '#cd0000'
5
+ c.add '#00cd00'
6
+ c.add '#cdcd00'
7
+ c.add '#0000ee'
8
+ c.add '#cd00cd'
9
+ c.add '#00cdcd'
10
+ c.add '#e5e5e5'
11
+
12
+ c.add '#7f7f7f'
13
+ c.add '#ff0000'
14
+ c.add '#00ff00'
15
+ c.add '#ffff00'
16
+ c.add '#5c5cff'
17
+ c.add '#ff00ff'
18
+ c.add '#00ffff'
19
+ c.add '#ffffff'
20
+
21
+ c.background '#ffffff'
22
+ c.foreground '#000000'
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module RVT
2
+ Colors.register_theme(:monokai) do |c|
3
+ c.add '#1c1d19'
4
+ c.add '#d01b24'
5
+ c.add '#a7d32C'
6
+ c.add '#d8cf67'
7
+ c.add '#61b8d0'
8
+ c.add '#695abb'
9
+ c.add '#d53864'
10
+ c.add '#fefffe'
11
+
12
+ c.add '#1c1d19'
13
+ c.add '#d12a24'
14
+ c.add '#a7d32c'
15
+ c.add '#d8cf67'
16
+ c.add '#61b8d0'
17
+ c.add '#695abb'
18
+ c.add '#d53864'
19
+ c.add '#fefffe'
20
+
21
+ c.background '#1c1d19'
22
+ c.foreground '#fefffe'
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ module RVT
2
+ Colors.register_theme(:solarized_dark) do |c|
3
+ c.add '#073642'
4
+ c.add '#dc322f'
5
+ c.add '#859900'
6
+ c.add '#b58900'
7
+ c.add '#268bd2'
8
+ c.add '#d33682'
9
+ c.add '#2aa198'
10
+ c.add '#eee8d5'
11
+
12
+ c.add '#002b36'
13
+ c.add '#cb4b16'
14
+ c.add '#586e75'
15
+ c.add '#657b83'
16
+ c.add '#839496'
17
+ c.add '#6c71c4'
18
+ c.add '#93a1a1'
19
+ c.add '#fdf6e3'
20
+
21
+ c.background '#002b36'
22
+ c.foreground '#657b83'
23
+ end
24
+
25
+ Colors.register_theme(:solarized_light) do |c|
26
+ c.add '#073642'
27
+ c.add '#dc322f'
28
+ c.add '#859900'
29
+ c.add '#b58900'
30
+ c.add '#268bd2'
31
+ c.add '#d33682'
32
+ c.add '#2aa198'
33
+ c.add '#eee8d5'
34
+
35
+ c.add '#002b36'
36
+ c.add '#cb4b16'
37
+ c.add '#586e75'
38
+ c.add '#657b83'
39
+ c.add '#839496'
40
+ c.add '#6c71c4'
41
+ c.add '#93a1a1'
42
+ c.add '#fdf6e3'
43
+
44
+ c.background '#fdf6e3'
45
+ c.foreground '#657b83'
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ module RVT
2
+ Colors.register_theme(:tango) do |c|
3
+ c.add '#2e3436'
4
+ c.add '#cc0000'
5
+ c.add '#4e9a06'
6
+ c.add '#c4a000'
7
+ c.add '#3465a4'
8
+ c.add '#75507b'
9
+ c.add '#06989a'
10
+ c.add '#d3d7cf'
11
+
12
+ c.add '#555753'
13
+ c.add '#ef2929'
14
+ c.add '#8ae234'
15
+ c.add '#fce94f'
16
+ c.add '#729fcf'
17
+ c.add '#ad7fa8'
18
+ c.add '#34e2e2'
19
+ c.add '#eeeeec'
20
+
21
+ c.background '#2e3436'
22
+ c.foreground '#eeeeec'
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module RVT
2
+ Colors.register_theme(:xterm) do |c|
3
+ c.add '#000000'
4
+ c.add '#cd0000'
5
+ c.add '#00cd00'
6
+ c.add '#cdcd00'
7
+ c.add '#0000ee'
8
+ c.add '#cd00cd'
9
+ c.add '#00cdcd'
10
+ c.add '#e5e5e5'
11
+
12
+ c.add '#7f7f7f'
13
+ c.add '#ff0000'
14
+ c.add '#00ff00'
15
+ c.add '#ffff00'
16
+ c.add '#5c5cff'
17
+ c.add '#ff00ff'
18
+ c.add '#00ffff'
19
+ c.add '#ffffff'
20
+
21
+ c.background '#000000'
22
+ c.foreground '#ffffff'
23
+ end
24
+ end
@@ -0,0 +1,85 @@
1
+ require 'ipaddr'
2
+ require 'active_support/core_ext/numeric/time'
3
+ require 'rails/engine'
4
+
5
+ require 'active_model'
6
+ require 'sprockets/rails'
7
+
8
+ module RVT
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace RVT
11
+
12
+ config.rvt = ActiveSupport::OrderedOptions.new.tap do |c|
13
+ c.automount = true
14
+ c.command = nil
15
+ c.default_mount_path = '/console'
16
+ c.timeout = 0.seconds
17
+ c.term = 'xterm-color'
18
+ c.whitelisted_ips = ['127.0.0.1', '::1']
19
+
20
+ c.style = ActiveSupport::OrderedOptions.new.tap do |s|
21
+ s.colors = 'light'
22
+ s.font = 'large Menlo, DejaVu Sans Mono, Liberation Mono, monospace'
23
+ end
24
+ end
25
+
26
+ initializer 'rvt.add_default_route' do |app|
27
+ # While we don't need the route in the test environment, we define it
28
+ # there as well, so we can easily test it.
29
+ if config.rvt.automount && (Rails.env.development? || Rails.env.test?)
30
+ app.routes.append do
31
+ mount RVT::Engine => app.config.rvt.default_mount_path
32
+ end
33
+ end
34
+ end
35
+
36
+ initializer 'rvt.process_whitelisted_ips' do
37
+ config.rvt.tap do |c|
38
+ # Ensure that it is an array of IPAddr instances and it is defaulted to
39
+ # 127.0.0.1 if not present. Only unique entries are left in the end.
40
+ c.whitelisted_ips = Array(c.whitelisted_ips).map { |ip|
41
+ if ip.is_a?(IPAddr)
42
+ ip
43
+ else
44
+ IPAddr.new(ip.presence || '127.0.0.1')
45
+ end
46
+ }.uniq
47
+
48
+ # IPAddr instances can cover whole networks, so simplify the #include?
49
+ # check for the most common case.
50
+ def (c.whitelisted_ips).include?(ip)
51
+ if ip.is_a?(IPAddr)
52
+ super
53
+ else
54
+ any? { |net| net.include?(ip.to_s) }
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ initializer 'rvt.process_command' do
61
+ config.rvt.tap do |c|
62
+ # +Rails.root+ is not available while we set the default values of the
63
+ # other options. Default it during initialization.
64
+
65
+ # Not all people created their Rails 4 applications with the Rails 4
66
+ # generator, so bin/rails may not be available.
67
+ if c.command.blank?
68
+ local_rails = Rails.root.join('bin/rails')
69
+ c.command = "#{local_rails.executable? ? local_rails : 'rails'} console"
70
+ end
71
+ end
72
+ end
73
+
74
+ initializer 'rvt.process_colors' do
75
+ config.rvt.style.tap do |c|
76
+ case colors = c.colors
77
+ when Symbol, String
78
+ c.colors = Colors[colors] || Colors.default
79
+ else
80
+ c.colors = Colors.new(colors)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,147 @@
1
+ module RVT
2
+ # = Slave\ Process\ Wrapper
3
+ #
4
+ # Creates and communicates with slave processes.
5
+ #
6
+ # The communication happens through an input with attached psuedo-terminal.
7
+ # All of the communication is done in asynchronous way, meaning that when you
8
+ # send input to the process, you have get the output by polling for it.
9
+ class Slave
10
+ # Different OS' and platforms raises different errors when trying to read
11
+ # on output end of a closed process.
12
+ READING_ON_CLOSED_END_ERRORS = [ Errno::EIO, EOFError ]
13
+
14
+ # Raised when trying to read from a closed (exited) process.
15
+ Closed = Class.new(IOError)
16
+
17
+ # The slave process id.
18
+ attr_reader :pid
19
+
20
+ # Unique identifier for each slave process.
21
+ attr_reader :uid
22
+
23
+ def initialize(command = RVT.config.command, options = {})
24
+ # Windows doesn't have PTY, requiring it at the top level will fail the
25
+ # whole program execution.
26
+ require 'pty'
27
+ require 'io/console'
28
+
29
+ @uid = SecureRandom.hex(16)
30
+
31
+ using_term(options[:term] || RVT.config.term) do
32
+ @output, @input, @pid = PTY.spawn(command.to_s)
33
+ end
34
+
35
+ configure(options)
36
+ end
37
+
38
+ # Configure the psuedo terminal properties.
39
+ #
40
+ # Options:
41
+ # :width The width of the terminal in number of columns.
42
+ # :height The height of the terminal in number of rows.
43
+ #
44
+ # If any of the width or height is missing (or zero), the terminal size
45
+ # won't be set.
46
+ def configure(options = {})
47
+ dimentions = options.values_at(:height, :width).collect(&:to_i)
48
+ @input.winsize = dimentions unless dimentions.any?(&:zero?)
49
+ end
50
+
51
+ # Sends input to the slave process STDIN.
52
+ #
53
+ # Returns immediately.
54
+ def send_input(input)
55
+ raise ArgumentError if input.nil? or input.try(:empty?)
56
+ input.each_char { |char| @input.putc(char) }
57
+ end
58
+
59
+ # Returns whether the slave process has any pending output in +wait+
60
+ # seconds.
61
+ #
62
+ # By default, the +timeout+ follows +config.rvt.timeout+. Usually,
63
+ # it is zero, making the response immediate.
64
+ def pending_output?(timeout = RVT.config.timeout)
65
+ # JRuby's select won't automatically coerce ActiveSupport::Duration.
66
+ !!IO.select([@output], [], [], timeout.to_i)
67
+ end
68
+
69
+ # Gets the pending output of the process.
70
+ #
71
+ # The pending output is read in an non blocking way by chunks, in the size
72
+ # of +chunk_len+. By default, +chunk_len+ is 49152 bytes.
73
+ #
74
+ # Returns +nil+, if there is no pending output at the moment. Otherwise,
75
+ # returns the output that hasn't been read since the last invocation.
76
+ #
77
+ # Raises Errno:EIO on closed output stream. This can happen if the
78
+ # underlying process exits.
79
+ def pending_output(chunk_len = 49152)
80
+ # Returns nil if there is no pending output.
81
+ return unless pending_output?
82
+
83
+ pending = String.new
84
+ while chunk = @output.read_nonblock(chunk_len)
85
+ pending << chunk
86
+ end
87
+ pending.force_encoding('UTF-8')
88
+ rescue IO::WaitReadable
89
+ pending.force_encoding('UTF-8')
90
+ rescue
91
+ raise Closed if READING_ON_CLOSED_END_ERRORS.any? { |exc| $!.is_a?(exc) }
92
+ end
93
+
94
+ # Dispose the underlying process, sending +SIGTERM+.
95
+ #
96
+ # After the process is disposed, it is detached from the parent to prevent
97
+ # zombies.
98
+ #
99
+ # If the process is already disposed an Errno::ESRCH will be raised and
100
+ # handled internally. If you want to handle Errno::ESRCH yourself, pass
101
+ # +{raise: true}+ as options.
102
+ #
103
+ # Returns a thread, which can be used to wait for the process termination.
104
+ def dispose(options = {})
105
+ dispose_with(:SIGTERM, options)
106
+ end
107
+
108
+ # Dispose the underlying process, sending +SIGKILL+.
109
+ #
110
+ # After the process is disposed, it is detached from the parent to prevent
111
+ # zombies.
112
+ #
113
+ # If the process is already disposed an Errno::ESRCH will be raised and
114
+ # handled internally. If you want to handle Errno::ESRCH yourself, pass
115
+ # +{raise: true}+ as options.
116
+ #
117
+ # Returns a thread, which can be used to wait for the process termination.
118
+ def dispose!(options = {})
119
+ dispose_with(:SIGKILL, options)
120
+ end
121
+
122
+ private
123
+
124
+ LOCK = Mutex.new
125
+
126
+ def using_term(term)
127
+ if term.nil?
128
+ yield
129
+ else
130
+ LOCK.synchronize do
131
+ begin
132
+ (previous_term, ENV['TERM'] = ENV['TERM'], term) and yield
133
+ ensure
134
+ ENV['TERM'] = previous_term
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def dispose_with(signal, options = {})
141
+ Process.kill(signal, @pid)
142
+ Process.detach(@pid)
143
+ rescue Errno::ESRCH
144
+ raise if options[:raise]
145
+ end
146
+ end
147
+ end