rvt 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +25 -0
- data/app/assets/javascripts/rvt/application.js +1 -0
- data/app/assets/javascripts/rvt/console_sessions.js +182 -0
- data/app/assets/stylesheets/rvt/application.css +13 -0
- data/app/assets/stylesheets/rvt/console_sessions.css.erb +6 -0
- data/app/controllers/rvt/application_controller.rb +13 -0
- data/app/controllers/rvt/console_sessions_controller.rb +47 -0
- data/app/models/rvt/console_session.rb +109 -0
- data/app/views/layouts/rvt/application.html.erb +14 -0
- data/app/views/rvt/console_sessions/index.html.erb +17 -0
- data/config/routes.rb +11 -0
- data/lib/assets/javascripts/rvt.js +41 -0
- data/lib/rvt.rb +21 -0
- data/lib/rvt/colors.rb +87 -0
- data/lib/rvt/colors/light.rb +24 -0
- data/lib/rvt/colors/monokai.rb +24 -0
- data/lib/rvt/colors/solarized.rb +47 -0
- data/lib/rvt/colors/tango.rb +24 -0
- data/lib/rvt/colors/xterm.rb +24 -0
- data/lib/rvt/engine.rb +85 -0
- data/lib/rvt/slave.rb +147 -0
- data/lib/rvt/version.rb +3 -0
- data/test/controllers/rvt/console_sessions_controller_test.rb +100 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/controller_helper_test/index.html.erb +1 -0
- data/test/dummy/app/views/exception_test/xhr.html.erb +1 -0
- data/test/dummy/app/views/helper_test/index.html.erb +220 -0
- data/test/dummy/app/views/layouts/application.html.erb +16 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +44 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +34 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +16 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +247222 -0
- data/test/dummy/log/test.log +963 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0509a6e0e75d9ac5a88bba7291b04686 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2bd9d10dae311aa2dfb6dea9c1e0ad50 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2f41a6a41d0a16db31cabd2b8689ca00 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/4de66e84a0d6f009fac50cd608fb8581 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/56e8026311075410507152df2aea4307 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/9542de15712d45f70221931bf78c00e5 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/98cea396a602c53d876e79bf4b89de3b +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/9df3968b0f171feec749766fffa50b2e +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/aa5fc4cb46c5294192c9d3af8824f88b +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ac7197dc7dd9ab362d915d19e37a8e01 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/b238befd6eff46b26d56322c1450ea45 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/c69b8b79c21fd48ad84c3fad87945a5c +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/da4318a4d8364d0616edd706508371bc +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/dd71b14df42fd6dc95355cded9e4d0f9 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/eb06cae1627276e46965809e766aff13 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/edffb5017d27ddb65965203636286405 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f0ced5f3d4a75fce1c596d6c3f97a42d +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/fb9e611526e612ba797f0c11b987826b +0 -0
- data/test/models/console_session_test.rb +58 -0
- data/test/rvt/colors_test.rb +58 -0
- data/test/rvt/engine_test.rb +145 -0
- data/test/rvt/slave_test.rb +72 -0
- data/test/test_helper.rb +27 -0
- data/vendor/assets/javascripts/term.js +5771 -0
- 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>
|
data/config/routes.rb
ADDED
@@ -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);
|
data/lib/rvt.rb
ADDED
@@ -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
|
data/lib/rvt/colors.rb
ADDED
@@ -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
|
data/lib/rvt/engine.rb
ADDED
@@ -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
|
data/lib/rvt/slave.rb
ADDED
@@ -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
|