rvt 0.9.9

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