iruby 0.2.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ubuntu.yml +62 -0
- data/CHANGES +62 -0
- data/Gemfile +3 -1
- data/LICENSE +1 -1
- data/README.md +148 -27
- data/Rakefile +36 -10
- data/ci/Dockerfile.base.erb +41 -0
- data/ci/Dockerfile.main.erb +7 -0
- data/ci/requirements.txt +1 -0
- data/docker/setup.sh +15 -0
- data/docker/test.sh +7 -0
- data/iruby.gemspec +14 -18
- data/lib/iruby.rb +19 -3
- data/lib/iruby/backend.rb +22 -2
- data/lib/iruby/command.rb +76 -13
- data/lib/iruby/display.rb +69 -39
- data/lib/iruby/formatter.rb +5 -4
- data/lib/iruby/input.rb +41 -0
- data/lib/iruby/input/README.ipynb +502 -0
- data/lib/iruby/input/README.md +299 -0
- data/lib/iruby/input/autoload.rb +25 -0
- data/lib/iruby/input/builder.rb +67 -0
- data/lib/iruby/input/button.rb +47 -0
- data/lib/iruby/input/cancel.rb +32 -0
- data/lib/iruby/input/checkbox.rb +74 -0
- data/lib/iruby/input/date.rb +37 -0
- data/lib/iruby/input/field.rb +31 -0
- data/lib/iruby/input/file.rb +57 -0
- data/lib/iruby/input/form.rb +77 -0
- data/lib/iruby/input/label.rb +27 -0
- data/lib/iruby/input/multiple.rb +76 -0
- data/lib/iruby/input/popup.rb +41 -0
- data/lib/iruby/input/radio.rb +59 -0
- data/lib/iruby/input/select.rb +59 -0
- data/lib/iruby/input/textarea.rb +23 -0
- data/lib/iruby/input/widget.rb +34 -0
- data/lib/iruby/jupyter.rb +77 -0
- data/lib/iruby/kernel.rb +67 -22
- data/lib/iruby/ostream.rb +24 -8
- data/lib/iruby/session.rb +85 -67
- data/lib/iruby/session/cztop.rb +70 -0
- data/lib/iruby/session/ffi_rzmq.rb +87 -0
- data/lib/iruby/session/mixin.rb +47 -0
- data/lib/iruby/session_adapter.rb +66 -0
- data/lib/iruby/session_adapter/cztop_adapter.rb +45 -0
- data/lib/iruby/session_adapter/ffirzmq_adapter.rb +55 -0
- data/lib/iruby/session_adapter/pyzmq_adapter.rb +77 -0
- data/lib/iruby/utils.rb +5 -2
- data/lib/iruby/version.rb +1 -1
- data/run-test.sh +12 -0
- data/tasks/ci.rake +65 -0
- data/test/helper.rb +90 -0
- data/test/integration_test.rb +22 -11
- data/test/iruby/backend_test.rb +37 -0
- data/test/iruby/command_test.rb +207 -0
- data/test/iruby/jupyter_test.rb +27 -0
- data/test/iruby/mime_test.rb +32 -0
- data/test/iruby/multi_logger_test.rb +1 -2
- data/test/iruby/session_adapter/cztop_adapter_test.rb +20 -0
- data/test/iruby/session_adapter/ffirzmq_adapter_test.rb +20 -0
- data/test/iruby/session_adapter/session_adapter_test_base.rb +27 -0
- data/test/iruby/session_adapter_test.rb +91 -0
- data/test/iruby/session_test.rb +47 -0
- data/test/run-test.rb +18 -0
- metadata +130 -46
- data/.travis.yml +0 -16
- data/CONTRIBUTORS +0 -19
- data/test/test_helper.rb +0 -5
@@ -0,0 +1,59 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Input
|
3
|
+
class Select < Label
|
4
|
+
needs :options, :default
|
5
|
+
|
6
|
+
builder :select do |*args, **params|
|
7
|
+
key = :select
|
8
|
+
key, *args = args if args.first.is_a? Symbol
|
9
|
+
|
10
|
+
params[:key] = unique_key(key)
|
11
|
+
params[:options] = args
|
12
|
+
params[:default] ||= false
|
13
|
+
|
14
|
+
unless params[:options].include? params[:default]
|
15
|
+
params[:options] = [nil, *params[:options].compact]
|
16
|
+
end
|
17
|
+
|
18
|
+
add_field Select.new(**params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def widget_css
|
22
|
+
<<-CSS
|
23
|
+
.iruby-select {
|
24
|
+
min-width: 25%;
|
25
|
+
margin-left: 0 !important;
|
26
|
+
}
|
27
|
+
CSS
|
28
|
+
end
|
29
|
+
|
30
|
+
def widget_js
|
31
|
+
<<-JS
|
32
|
+
$('.iruby-select').change(function(){
|
33
|
+
$(this).data('iruby-value',
|
34
|
+
$(this).find('option:selected').text()
|
35
|
+
);
|
36
|
+
});
|
37
|
+
JS
|
38
|
+
end
|
39
|
+
|
40
|
+
def widget_html
|
41
|
+
widget_label do
|
42
|
+
div class: 'form-control' do
|
43
|
+
params = {
|
44
|
+
class: 'iruby-select',
|
45
|
+
:'data-iruby-key' => @key,
|
46
|
+
:'data-iruby-value' => @default
|
47
|
+
}
|
48
|
+
|
49
|
+
select **params do
|
50
|
+
@options.each do |o|
|
51
|
+
option o, selected: @default == o
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Input
|
3
|
+
class Textarea < Field
|
4
|
+
needs rows: 5
|
5
|
+
|
6
|
+
builder :textarea do |key='textarea', **params|
|
7
|
+
params[:key] = unique_key key
|
8
|
+
add_field Textarea.new(**params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def widget_html
|
12
|
+
widget_label do
|
13
|
+
textarea(
|
14
|
+
@default,
|
15
|
+
rows: @rows,
|
16
|
+
:'data-iruby-key' => @key,
|
17
|
+
class: 'form-control iruby-field'
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Input
|
3
|
+
class Widget < Erector::Widget
|
4
|
+
needs key: nil
|
5
|
+
|
6
|
+
def widget_js; end
|
7
|
+
def widget_css; end
|
8
|
+
def widget_html; end
|
9
|
+
def content; widget_html; end
|
10
|
+
|
11
|
+
def self.builder method, &block
|
12
|
+
Builder.instance_eval do
|
13
|
+
define_method method, &block
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def widget_join method, *args
|
18
|
+
strings = args.map do |arg|
|
19
|
+
arg.is_a?(String) ? arg : arg.send(method)
|
20
|
+
end
|
21
|
+
strings.uniq.join("\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
def widget_display
|
25
|
+
IRuby.display(IRuby.html(
|
26
|
+
Erector.inline{ style raw(widget_css) }.to_html
|
27
|
+
))
|
28
|
+
|
29
|
+
IRuby.display(IRuby.html(to_html))
|
30
|
+
IRuby.display(IRuby.javascript(widget_js))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Jupyter
|
3
|
+
class << self
|
4
|
+
# User's default kernelspec directory is described here:
|
5
|
+
# https://jupyter.readthedocs.io/en/latest/projects/jupyter-directories.html
|
6
|
+
def default_data_dir
|
7
|
+
case
|
8
|
+
when windows?
|
9
|
+
appdata = windows_user_appdata
|
10
|
+
if !appdata.empty?
|
11
|
+
File.join(appdata, 'jupyter')
|
12
|
+
else
|
13
|
+
jupyter_config_dir = ENV.fetch('JUPYTER_CONFIG_DIR', File.expand_path('~/.jupyter'))
|
14
|
+
File.join(jupyter_config_dir, 'data')
|
15
|
+
end
|
16
|
+
when apple?
|
17
|
+
File.expand_path('~/Library/Jupyter')
|
18
|
+
else
|
19
|
+
xdg_data_home = ENV.fetch('XDG_DATA_HOME', '')
|
20
|
+
data_home = xdg_data_home[0] ? xdg_data_home : File.expand_path('~/.local/share')
|
21
|
+
File.join(data_home, 'jupyter')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def kernelspec_dir(data_dir=nil)
|
26
|
+
data_dir ||= default_data_dir
|
27
|
+
File.join(data_dir, 'kernels')
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# returns %APPDATA%
|
33
|
+
def windows_user_appdata
|
34
|
+
require 'fiddle/import'
|
35
|
+
check_windows
|
36
|
+
path = Fiddle::Pointer.malloc(2 * 300) # uint16_t[300]
|
37
|
+
csidl_appdata = 0x001a
|
38
|
+
case call_SHGetFolderPathW(Fiddle::NULL, csidl_appdata, Fiddle::NULL, 0, path)
|
39
|
+
when 0
|
40
|
+
len = (1 ... (path.size/2)).find {|i| path[2*i, 2] == "\0\0" }
|
41
|
+
path = path.to_str(2*len).encode(Encoding::UTF_8, Encoding::UTF_16LE)
|
42
|
+
else
|
43
|
+
ENV.fetch('APPDATA', '')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def call_SHGetFolderPathW(hwnd, csidl, hToken, dwFlags, pszPath)
|
48
|
+
require 'fiddle/import'
|
49
|
+
shell32 = Fiddle::Handle.new('shell32')
|
50
|
+
func = Fiddle::Function.new(
|
51
|
+
shell32['SHGetFolderPathW'],
|
52
|
+
[
|
53
|
+
Fiddle::TYPE_VOIDP,
|
54
|
+
Fiddle::TYPE_INT,
|
55
|
+
Fiddle::TYPE_VOIDP,
|
56
|
+
Fiddle::TYPE_INT,
|
57
|
+
Fiddle::TYPE_VOIDP
|
58
|
+
],
|
59
|
+
Fiddle::TYPE_INT,
|
60
|
+
Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[:stdcall])
|
61
|
+
func.(hwnd, csidl, hToken, dwFlags, pszPath)
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_windows
|
65
|
+
raise 'the current platform is not Windows' unless windows?
|
66
|
+
end
|
67
|
+
|
68
|
+
def windows?
|
69
|
+
/mingw|mswin/ =~ RUBY_PLATFORM
|
70
|
+
end
|
71
|
+
|
72
|
+
def apple?
|
73
|
+
/darwin/ =~ RUBY_PLATFORM
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/iruby/kernel.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module IRuby
|
2
2
|
class Kernel
|
3
3
|
RED = "\e[31m"
|
4
|
-
WHITE = "\e[37m"
|
5
4
|
RESET = "\e[0m"
|
6
5
|
|
7
6
|
class<< self
|
@@ -19,6 +18,8 @@ module IRuby
|
|
19
18
|
$stdout = OStream.new(@session, :stdout)
|
20
19
|
$stderr = OStream.new(@session, :stderr)
|
21
20
|
|
21
|
+
init_parent_process_poller
|
22
|
+
|
22
23
|
@execution_count = 0
|
23
24
|
@backend = create_backend
|
24
25
|
@running = true
|
@@ -40,6 +41,7 @@ module IRuby
|
|
40
41
|
|
41
42
|
def dispatch
|
42
43
|
msg = @session.recv(:reply)
|
44
|
+
IRuby.logger.debug "Kernel#dispatch: msg = #{msg}"
|
43
45
|
type = msg[:header]['msg_type']
|
44
46
|
raise "Unknown message type: #{msg.inspect}" unless type =~ /comm_|_request\Z/ && respond_to?(type)
|
45
47
|
begin
|
@@ -50,24 +52,32 @@ module IRuby
|
|
50
52
|
end
|
51
53
|
rescue Exception => e
|
52
54
|
IRuby.logger.debug "Kernel error: #{e.message}\n#{e.backtrace.join("\n")}"
|
53
|
-
@session.send(:publish, :error,
|
55
|
+
@session.send(:publish, :error, error_content(e))
|
54
56
|
end
|
55
57
|
|
56
58
|
def kernel_info_request(msg)
|
57
59
|
@session.send(:reply, :kernel_info_reply,
|
58
60
|
protocol_version: '5.0',
|
59
61
|
implementation: 'iruby',
|
60
|
-
banner: "IRuby #{IRuby::VERSION}",
|
61
62
|
implementation_version: IRuby::VERSION,
|
62
63
|
language_info: {
|
63
64
|
name: 'ruby',
|
64
65
|
version: RUBY_VERSION,
|
65
66
|
mimetype: 'application/x-ruby',
|
66
|
-
file_extension: 'rb'
|
67
|
-
}
|
67
|
+
file_extension: '.rb'
|
68
|
+
},
|
69
|
+
banner: "IRuby #{IRuby::VERSION} (with #{@session.description})",
|
70
|
+
help_links: [
|
71
|
+
{
|
72
|
+
text: "Ruby Documentation",
|
73
|
+
url: "https://ruby-doc.org/"
|
74
|
+
}
|
75
|
+
],
|
76
|
+
status: :ok)
|
68
77
|
end
|
69
78
|
|
70
79
|
def send_status(status)
|
80
|
+
IRuby.logger.debug "Send status: #{status}"
|
71
81
|
@session.send(:publish, :status, execution_state: status)
|
72
82
|
end
|
73
83
|
|
@@ -88,8 +98,10 @@ module IRuby
|
|
88
98
|
rescue SystemExit
|
89
99
|
content[:payload] << { source: :ask_exit }
|
90
100
|
rescue Exception => e
|
91
|
-
content =
|
101
|
+
content = error_content(e)
|
92
102
|
@session.send(:publish, :error, content)
|
103
|
+
content[:status] = :error
|
104
|
+
content[:execution_count] = @execution_count
|
93
105
|
end
|
94
106
|
@session.send(:reply, :execute_reply, content)
|
95
107
|
@session.send(:publish, :execute_result,
|
@@ -98,26 +110,33 @@ module IRuby
|
|
98
110
|
execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
|
99
111
|
end
|
100
112
|
|
101
|
-
def
|
102
|
-
{
|
103
|
-
|
113
|
+
def error_content(e)
|
114
|
+
rindex = e.backtrace.rindex{|line| line.start_with?(@backend.eval_path)} || -1
|
115
|
+
backtrace = SyntaxError === e && rindex == -1 ? [] : e.backtrace[0..rindex]
|
116
|
+
{ ename: e.class.to_s,
|
104
117
|
evalue: e.message,
|
105
|
-
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *
|
106
|
-
|
118
|
+
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *backtrace] }
|
119
|
+
end
|
120
|
+
|
121
|
+
def is_complete_request(msg)
|
122
|
+
# FIXME: the code completeness should be judged by using ripper or other Ruby parser
|
123
|
+
@session.send(:reply, :is_complete_reply,
|
124
|
+
status: :unknown)
|
107
125
|
end
|
108
126
|
|
109
127
|
def complete_request(msg)
|
110
128
|
# HACK for #26, only complete last line
|
111
129
|
code = msg[:content]['code']
|
112
|
-
if start = code.rindex(
|
130
|
+
if start = code.rindex(/\s|\R/)
|
113
131
|
code = code[start+1..-1]
|
114
132
|
start += 1
|
115
133
|
end
|
116
134
|
@session.send(:reply, :complete_reply,
|
117
135
|
matches: @backend.complete(code),
|
118
|
-
status: :ok,
|
119
136
|
cursor_start: start.to_i,
|
120
|
-
cursor_end: msg[:content]['cursor_pos']
|
137
|
+
cursor_end: msg[:content]['cursor_pos'],
|
138
|
+
metadata: {},
|
139
|
+
status: :ok)
|
121
140
|
end
|
122
141
|
|
123
142
|
def connect_request(msg)
|
@@ -136,14 +155,8 @@ module IRuby
|
|
136
155
|
end
|
137
156
|
|
138
157
|
def inspect_request(msg)
|
139
|
-
|
140
|
-
@session.send(:reply, :inspect_reply,
|
141
|
-
status: :ok,
|
142
|
-
data: Display.display(result),
|
143
|
-
metadata: {})
|
144
|
-
rescue Exception => e
|
145
|
-
IRuby.logger.warn "Inspection error: #{e.message}\n#{e.backtrace.join("\n")}"
|
146
|
-
@session.send(:reply, :inspect_reply, status: :error)
|
158
|
+
# not yet implemented. See (#119).
|
159
|
+
@session.send(:reply, :inspect_reply, status: :ok, found: false, data: {}, metadata: {})
|
147
160
|
end
|
148
161
|
|
149
162
|
def comm_open(msg)
|
@@ -161,5 +174,37 @@ module IRuby
|
|
161
174
|
Comm.comm[comm_id].handle_close(msg[:content]['data'])
|
162
175
|
Comm.comm.delete(comm_id)
|
163
176
|
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def init_parent_process_poller
|
181
|
+
pid = ENV.fetch('JPY_PARENT_PID', 0).to_i
|
182
|
+
return unless pid > 1
|
183
|
+
|
184
|
+
case RUBY_PLATFORM
|
185
|
+
when /mswin/, /mingw/
|
186
|
+
# TODO
|
187
|
+
else
|
188
|
+
@parent_poller = start_parent_process_pollar_unix
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def start_parent_process_pollar_unix
|
193
|
+
Thread.start do
|
194
|
+
IRuby.logger.warn("parent process poller thread started.")
|
195
|
+
loop do
|
196
|
+
begin
|
197
|
+
current_ppid = Process.ppid
|
198
|
+
if current_ppid == 1
|
199
|
+
IRuby.logger.warn("parent process appears to exited, shutting down.")
|
200
|
+
exit!(1)
|
201
|
+
end
|
202
|
+
sleep 1
|
203
|
+
rescue Errno::EINTR
|
204
|
+
# ignored
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
164
209
|
end
|
165
210
|
end
|
data/lib/iruby/ostream.rb
CHANGED
@@ -25,22 +25,38 @@ module IRuby
|
|
25
25
|
alias_method :next, :read
|
26
26
|
alias_method :readline, :read
|
27
27
|
|
28
|
-
def write(
|
29
|
-
|
30
|
-
|
31
|
-
nil
|
28
|
+
def write(*obj)
|
29
|
+
str = build_string { |sio| sio.write(*obj) }
|
30
|
+
session_send(str)
|
32
31
|
end
|
33
32
|
alias_method :<<, :write
|
34
33
|
alias_method :print, :write
|
35
34
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
def printf(format, *obj)
|
36
|
+
str = build_string { |sio| sio.printf(format, *obj) }
|
37
|
+
session_send(str)
|
38
|
+
end
|
39
|
+
|
40
|
+
def puts(*obj)
|
41
|
+
str = build_string { |sio| sio.puts(*obj) }
|
42
|
+
session_send(str)
|
40
43
|
end
|
41
44
|
|
42
45
|
def writelines(lines)
|
43
46
|
lines.each { |s| write(s) }
|
44
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def build_string
|
52
|
+
StringIO.open { |sio| yield(sio); sio.string }
|
53
|
+
end
|
54
|
+
|
55
|
+
def session_send(str)
|
56
|
+
raise 'I/O operation on closed file' unless @session
|
57
|
+
|
58
|
+
@session.send(:publish, :stream, name: @name, text: str)
|
59
|
+
nil
|
60
|
+
end
|
45
61
|
end
|
46
62
|
end
|
data/lib/iruby/session.rb
CHANGED
@@ -1,98 +1,116 @@
|
|
1
|
+
require 'iruby/session_adapter'
|
2
|
+
require 'iruby/session/mixin'
|
3
|
+
|
4
|
+
require 'securerandom'
|
5
|
+
|
1
6
|
module IRuby
|
2
7
|
class Session
|
3
|
-
|
8
|
+
include SessionSerialize
|
9
|
+
|
10
|
+
def initialize(config, adapter_name=nil)
|
11
|
+
@config = config
|
12
|
+
@adapter = create_session_adapter(config, adapter_name)
|
4
13
|
|
5
|
-
|
6
|
-
|
14
|
+
setup
|
15
|
+
setup_sockets
|
16
|
+
setup_heartbeat
|
17
|
+
setup_security
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
reply_socket = c.socket(:ROUTER)
|
10
|
-
reply_socket.bind(connection % config['shell_port'])
|
20
|
+
attr_reader :adapter, :config
|
11
21
|
|
12
|
-
|
13
|
-
|
22
|
+
def description
|
23
|
+
"#{@adapter.name} session adapter"
|
24
|
+
end
|
14
25
|
|
15
|
-
|
26
|
+
def setup
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup_sockets
|
30
|
+
protocol, host = config.values_at('transport', 'ip')
|
31
|
+
shell_port = config['shell_port']
|
32
|
+
iopub_port = config['iopub_port']
|
33
|
+
stdin_port = config['stdin_port']
|
34
|
+
|
35
|
+
@shell_socket, @shell_port = @adapter.make_router_socket(protocol, host, shell_port)
|
36
|
+
@iopub_socket, @iopub_port = @adapter.make_pub_socket(protocol, host, iopub_port)
|
37
|
+
@stdin_socket, @stdin_port = @adapter.make_router_socket(protocol, host, stdin_port)
|
38
|
+
|
39
|
+
@sockets = {
|
40
|
+
publish: @iopub_socket,
|
41
|
+
reply: @shell_socket,
|
42
|
+
stdin: @stdin_socket
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup_heartbeat
|
47
|
+
protocol, host = config.values_at('transport', 'ip')
|
48
|
+
hb_port = config['hb_port']
|
49
|
+
@hb_socket, @hb_port = @adapter.make_rep_socket(protocol, host, hb_port)
|
50
|
+
@heartbeat_thread = Thread.start do
|
16
51
|
begin
|
17
|
-
|
18
|
-
|
19
|
-
ZMQ.proxy(hb_socket, hb_socket)
|
52
|
+
# NOTE: this loop is copied from CZTop's old session code
|
53
|
+
@adapter.heartbeat_loop(@hb_socket)
|
20
54
|
rescue Exception => e
|
21
55
|
IRuby.logger.fatal "Kernel heartbeat died: #{e.message}\n#{e.backtrace.join("\n")}"
|
22
56
|
end
|
23
57
|
end
|
58
|
+
end
|
24
59
|
|
25
|
-
|
26
|
-
@
|
27
|
-
unless config['key'].
|
28
|
-
|
29
|
-
|
60
|
+
def setup_security
|
61
|
+
@session_id = SecureRandom.uuid
|
62
|
+
unless config['key'].empty? || config['signature_scheme'].empty?
|
63
|
+
unless config['signature_scheme'] =~ /\Ahmac-/
|
64
|
+
raise "Unknown signature_scheme: #{config['signature_scheme']}"
|
65
|
+
end
|
66
|
+
digest_algorithm = config['signature_scheme'][/\Ahmac-(.*)\Z/, 1]
|
67
|
+
@hmac = OpenSSL::HMAC.new(config['key'], OpenSSL::Digest.new(digest_algorithm))
|
30
68
|
end
|
31
69
|
end
|
32
70
|
|
33
|
-
|
34
|
-
|
35
|
-
idents =
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
71
|
+
def send(socket_type, message_type, content)
|
72
|
+
sock = check_socket_type(socket_type)
|
73
|
+
idents = if socket_type == :reply && @last_recvd_msg
|
74
|
+
@last_recvd_msg[:idents]
|
75
|
+
else
|
76
|
+
message_type == :stream ? "stream.#{content[:name]}" : message_type
|
77
|
+
end
|
41
78
|
header = {
|
42
|
-
msg_type:
|
79
|
+
msg_type: message_type,
|
43
80
|
msg_id: SecureRandom.uuid,
|
44
81
|
username: 'kernel',
|
45
|
-
session: @
|
82
|
+
session: @session_id,
|
46
83
|
version: '5.0'
|
47
84
|
}
|
48
|
-
@
|
85
|
+
@adapter.send(sock, serialize(idents, header, content))
|
49
86
|
end
|
50
87
|
|
51
|
-
|
52
|
-
|
53
|
-
|
88
|
+
def recv(socket_type)
|
89
|
+
sock = check_socket_type(socket_type)
|
90
|
+
data = @adapter.recv(sock)
|
91
|
+
@last_recvd_msg = unserialize(data)
|
54
92
|
end
|
55
93
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
MultiJson.dump(@last_recvd_msg ? @last_recvd_msg[:header] : {}),
|
61
|
-
'{}',
|
62
|
-
MultiJson.dump(content || {})]
|
63
|
-
frames = ([*idents].compact.map(&:to_s) << DELIM << sign(msg)) + msg
|
64
|
-
IRuby.logger.debug "Sent #{frames.inspect}"
|
65
|
-
ZMQ::Message(*frames)
|
94
|
+
def recv_input
|
95
|
+
sock = check_socket_type(:stdin)
|
96
|
+
data = @adapter.recv(sock)
|
97
|
+
unserialize(data)[:content]["value"]
|
66
98
|
end
|
67
99
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
raise 'malformed message, must have at least #{minlen} elements' unless msg_list.length >= minlen
|
78
|
-
s, header, parent_header, metadata, content, buffers = *msg_list
|
79
|
-
raise 'Invalid signature' unless s == sign(msg_list[1..-1])
|
80
|
-
{
|
81
|
-
idents: idents,
|
82
|
-
header: MultiJson.load(header),
|
83
|
-
parent_header: MultiJson.load(parent_header),
|
84
|
-
metadata: MultiJson.load(metadata),
|
85
|
-
content: MultiJson.load(content),
|
86
|
-
buffers: buffers
|
87
|
-
}
|
100
|
+
private
|
101
|
+
|
102
|
+
def check_socket_type(socket_type)
|
103
|
+
case socket_type
|
104
|
+
when :publish, :reply, :stdin
|
105
|
+
@sockets[socket_type]
|
106
|
+
else
|
107
|
+
raise ArgumentError, "Invalid socket type #{socket_type}"
|
108
|
+
end
|
88
109
|
end
|
89
110
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
@hmac.reset
|
94
|
-
list.each {|m| @hmac.update(m) }
|
95
|
-
@hmac.hexdigest
|
111
|
+
def create_session_adapter(config, adapter_name)
|
112
|
+
adapter_class = SessionAdapter.select_adapter_class(adapter_name)
|
113
|
+
adapter_class.new(config)
|
96
114
|
end
|
97
115
|
end
|
98
116
|
end
|