iruby 0.7.3 → 0.8.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +119 -0
- data/CHANGES.md +28 -0
- data/README.md +59 -51
- data/Rakefile +2 -1
- data/exe/iruby +7 -0
- data/ext/Rakefile +19 -0
- data/iruby.gemspec +7 -1
- data/lib/iruby/application.rb +380 -0
- data/lib/iruby/backend.rb +11 -2
- data/lib/iruby/error.rb +12 -0
- data/lib/iruby/jupyter.rb +4 -1
- data/lib/iruby/kernel.rb +4 -2
- data/lib/iruby/kernel_app.rb +108 -0
- data/lib/iruby/logger.rb +12 -2
- data/lib/iruby/session/mixin.rb +1 -1
- data/lib/iruby/session.rb +2 -0
- data/lib/iruby/session_adapter.rb +3 -2
- data/lib/iruby/version.rb +1 -1
- data/test/helper.rb +22 -3
- data/test/integration_test.rb +7 -1
- data/test/iruby/application/application_test.rb +32 -0
- data/test/iruby/application/console_test.rb +97 -0
- data/test/iruby/application/helper.rb +38 -0
- data/test/iruby/application/kernel_test.rb +93 -0
- data/test/iruby/application/register_test.rb +139 -0
- data/test/iruby/application/unregister_test.rb +77 -0
- data/test/iruby/jupyter_test.rb +27 -15
- data/test/iruby/kernel_test.rb +0 -2
- data/test/iruby/multi_logger_test.rb +13 -0
- data/test/iruby/session_adapter_test.rb +0 -28
- data/test/iruby/session_test.rb +0 -4
- data/test/run-test.rb +1 -0
- metadata +57 -15
- data/.github/workflows/ubuntu.yml +0 -69
- data/bin/iruby +0 -5
- data/lib/iruby/command.rb +0 -190
- data/lib/iruby/session_adapter/pyzmq_adapter.rb +0 -77
- data/test/iruby/command_test.rb +0 -207
@@ -0,0 +1,380 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "json"
|
3
|
+
require "optparse"
|
4
|
+
require "rbconfig"
|
5
|
+
require "singleton"
|
6
|
+
|
7
|
+
require_relative "error"
|
8
|
+
require_relative "kernel_app"
|
9
|
+
|
10
|
+
module IRuby
|
11
|
+
class Application
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
# Set the application instance up.
|
15
|
+
def setup(argv=nil)
|
16
|
+
@iruby_executable = File.expand_path($PROGRAM_NAME)
|
17
|
+
parse_command_line(argv)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parse the command line arguments
|
21
|
+
#
|
22
|
+
# @param argv [Array<String>, nil] The array of arguments.
|
23
|
+
private def parse_command_line(argv)
|
24
|
+
argv = ARGV.dup if argv.nil?
|
25
|
+
@argv = argv # save the original
|
26
|
+
|
27
|
+
case argv[0]
|
28
|
+
when "help"
|
29
|
+
# turn `iruby help notebook` into `iruby notebook -h`
|
30
|
+
argv = [*argv[1..-1], "-h"]
|
31
|
+
when "version"
|
32
|
+
# turn `iruby version` into `iruby -v`
|
33
|
+
argv = ["-v", *argv[1..-1]]
|
34
|
+
else
|
35
|
+
argv = argv.dup # prevent to break @argv
|
36
|
+
end
|
37
|
+
|
38
|
+
opts = OptionParser.new
|
39
|
+
opts.program_name = "IRuby"
|
40
|
+
opts.version = ::IRuby::VERSION
|
41
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options] [subcommand] [options]"
|
42
|
+
|
43
|
+
opts.on_tail("-h", "--help") do
|
44
|
+
print_help(opts)
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on_tail("-v", "--version") do
|
49
|
+
puts opts.ver
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.order!(argv)
|
54
|
+
|
55
|
+
if argv.length == 0 || argv[0].start_with?("-")
|
56
|
+
# If no subcommand is given, we use the console
|
57
|
+
argv = ["console", *argv]
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
parse_sub_command(argv) if argv.length > 0
|
62
|
+
rescue InvalidSubcommandError => err
|
63
|
+
$stderr.puts err.message
|
64
|
+
print_help(opts, $stderr)
|
65
|
+
abort
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
SUB_COMMANDS = {
|
70
|
+
"register" => "Register IRuby kernel.",
|
71
|
+
"unregister" => "Unregister the existing IRuby kernel.",
|
72
|
+
"kernel" => "Launch IRuby kernel",
|
73
|
+
"console" => "Launch jupyter console with IRuby kernel"
|
74
|
+
}.freeze.each_value(&:freeze)
|
75
|
+
|
76
|
+
private_constant :SUB_COMMANDS
|
77
|
+
|
78
|
+
private def parse_sub_command(argv)
|
79
|
+
sub_cmd, *sub_argv = argv
|
80
|
+
case sub_cmd
|
81
|
+
when *SUB_COMMANDS.keys
|
82
|
+
@sub_cmd = sub_cmd.to_sym
|
83
|
+
@sub_argv = sub_argv
|
84
|
+
else
|
85
|
+
raise InvalidSubcommandError.new(sub_cmd, sub_argv)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private def print_help(opts, out=$stdout)
|
90
|
+
out.puts opts.help
|
91
|
+
out.puts
|
92
|
+
out.puts "Subcommands"
|
93
|
+
out.puts "==========="
|
94
|
+
SUB_COMMANDS.each do |name, description|
|
95
|
+
out.puts "#{name}"
|
96
|
+
out.puts " #{description}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def run
|
101
|
+
case @sub_cmd
|
102
|
+
when :register
|
103
|
+
register_kernel(@sub_argv)
|
104
|
+
when :unregister
|
105
|
+
unregister_kernel(@sub_argv)
|
106
|
+
when :console
|
107
|
+
exec_jupyter(@sub_cmd.to_s, @sub_argv)
|
108
|
+
when :kernel
|
109
|
+
@sub_app = KernelApplication.new(@sub_argv)
|
110
|
+
@sub_app.run
|
111
|
+
else
|
112
|
+
raise "[IRuby][BUG] Unknown subcommand: #{@sub_cmd}; this must be treated in parse_command_line."
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
ruby_version_info = RUBY_VERSION.split('.')
|
117
|
+
DEFAULT_KERNEL_NAME = "ruby#{ruby_version_info[0]}".freeze
|
118
|
+
DEFAULT_DISPLAY_NAME = "Ruby #{ruby_version_info[0]} (iruby kernel)"
|
119
|
+
|
120
|
+
RegisterParams = Struct.new(
|
121
|
+
:name,
|
122
|
+
:display_name,
|
123
|
+
:profile,
|
124
|
+
:env,
|
125
|
+
:user,
|
126
|
+
:prefix,
|
127
|
+
:sys_prefix,
|
128
|
+
:force,
|
129
|
+
:ipython_dir
|
130
|
+
) do
|
131
|
+
def initialize(*args)
|
132
|
+
super
|
133
|
+
self.name ||= DEFAULT_KERNEL_NAME
|
134
|
+
self.force = false
|
135
|
+
self.user = true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def register_kernel(argv)
|
140
|
+
params = parse_register_command_line(argv)
|
141
|
+
|
142
|
+
if params.name != DEFAULT_KERNEL_NAME
|
143
|
+
# `--name` is specified and `--display-name` is not
|
144
|
+
# default `params.display_name` to `params.name`
|
145
|
+
params.display_name ||= params.name
|
146
|
+
end
|
147
|
+
|
148
|
+
check_and_warn_kernel_in_default_ipython_directory(params)
|
149
|
+
|
150
|
+
if installed_kernel_exist?(params.name, params.ipython_dir)
|
151
|
+
unless params.force
|
152
|
+
$stderr.puts "IRuby kernel named `#{params.name}` already exists!"
|
153
|
+
$stderr.puts "Use --force to force register the new kernel."
|
154
|
+
exit 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
Dir.mktmpdir("iruby_kernel") do |tmpdir|
|
159
|
+
path = File.join(tmpdir, DEFAULT_KERNEL_NAME)
|
160
|
+
FileUtils.mkdir_p(path)
|
161
|
+
|
162
|
+
# Stage assets
|
163
|
+
assets_dir = File.expand_path("../assets", __FILE__)
|
164
|
+
FileUtils.cp_r(Dir.glob(File.join(assets_dir, "*")), path)
|
165
|
+
|
166
|
+
kernel_dict = {
|
167
|
+
"argv" => make_iruby_cmd(),
|
168
|
+
"display_name" => params.display_name || DEFAULT_DISPLAY_NAME,
|
169
|
+
"language" => "ruby",
|
170
|
+
"metadata" => {"debugger": false}
|
171
|
+
}
|
172
|
+
|
173
|
+
# TODO: Support params.profile
|
174
|
+
# TODO: Support params.env
|
175
|
+
|
176
|
+
kernel_content = JSON.pretty_generate(kernel_dict)
|
177
|
+
File.write(File.join(path, "kernel.json"), kernel_content)
|
178
|
+
|
179
|
+
args = ["--name=#{params.name}"]
|
180
|
+
args << "--user" if params.user
|
181
|
+
args << path
|
182
|
+
|
183
|
+
# TODO: Support params.prefix
|
184
|
+
# TODO: Support params.sys_prefix
|
185
|
+
|
186
|
+
system("jupyter", "kernelspec", "install", *args)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Warn the existence of the IRuby kernel in the default IPython's kernels directory
|
191
|
+
private def check_and_warn_kernel_in_default_ipython_directory(params)
|
192
|
+
default_ipython_kernels_dir = File.expand_path("~/.ipython/kernels")
|
193
|
+
[params.name, "ruby"].each do |name|
|
194
|
+
if File.exist?(File.join(default_ipython_kernels_dir, name, "kernel.json"))
|
195
|
+
warn "IRuby kernel `#{name}` already exists in the deprecated IPython's data directory."
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
alias __system__ system
|
201
|
+
|
202
|
+
private def system(*cmdline, dry_run: false)
|
203
|
+
$stderr.puts "EXECUTE: #{cmdline.map {|x| x.include?(' ') ? x.inspect : x}.join(' ')}"
|
204
|
+
__system__(*cmdline) unless dry_run
|
205
|
+
end
|
206
|
+
|
207
|
+
private def installed_kernel_exist?(name, ipython_dir)
|
208
|
+
kernels_dir = resolve_kernelspec_dir(ipython_dir)
|
209
|
+
kernel_dir = File.join(kernels_dir, name)
|
210
|
+
File.file?(File.join(kernel_dir, "kernel.json"))
|
211
|
+
end
|
212
|
+
|
213
|
+
private def resolve_kernelspec_dir(ipython_dir)
|
214
|
+
if ENV.has_key?("JUPYTER_DATA_DIR")
|
215
|
+
if ENV.has_key?("IPYTHONDIR")
|
216
|
+
warn "both JUPYTER_DATA_DIR and IPYTHONDIR are supplied; IPYTHONDIR is ignored."
|
217
|
+
end
|
218
|
+
jupyter_data_dir = ENV["JUPYTER_DATA_DIR"]
|
219
|
+
return File.join(jupyter_data_dir, "kernels")
|
220
|
+
end
|
221
|
+
|
222
|
+
if ipython_dir.nil? && ENV.key?("IPYTHONDIR")
|
223
|
+
warn 'IPYTHONDIR is deprecated. Use JUPYTER_DATA_DIR instead.'
|
224
|
+
ipython_dir = ENV["IPYTHONDIR"]
|
225
|
+
end
|
226
|
+
|
227
|
+
if ipython_dir
|
228
|
+
File.join(ipython_dir, 'kerenels')
|
229
|
+
else
|
230
|
+
Jupyter.kernelspec_dir
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
private def make_iruby_cmd(executable: nil, extra_arguments: nil)
|
235
|
+
executable ||= default_executable
|
236
|
+
extra_arguments ||= []
|
237
|
+
[*Array(executable), "kernel", "-f", "{connection_file}", *extra_arguments]
|
238
|
+
end
|
239
|
+
|
240
|
+
private def default_executable
|
241
|
+
[RbConfig.ruby, @iruby_executable]
|
242
|
+
end
|
243
|
+
|
244
|
+
private def parse_register_command_line(argv)
|
245
|
+
opts = OptionParser.new
|
246
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} register [options]"
|
247
|
+
|
248
|
+
params = RegisterParams.new
|
249
|
+
|
250
|
+
opts.on(
|
251
|
+
"--force",
|
252
|
+
"Force register a new kernel spec. The existing kernel spec will be removed."
|
253
|
+
) { params.force = true }
|
254
|
+
|
255
|
+
opts.on(
|
256
|
+
"--user",
|
257
|
+
"Register for the current user instead of system-wide."
|
258
|
+
) { params.user = true }
|
259
|
+
|
260
|
+
opts.on(
|
261
|
+
"--name=VALUE", String,
|
262
|
+
"Specify a name for the kernelspec. This is needed to have multiple IRuby kernels at the same time."
|
263
|
+
) {|v| params.name = v }
|
264
|
+
|
265
|
+
opts.on(
|
266
|
+
"--display-name=VALUE", String,
|
267
|
+
"Specify the display name for the kernelspec. This is helpful when you have multiple IRuby kernels."
|
268
|
+
) {|v| kernel_display_name = v }
|
269
|
+
|
270
|
+
# TODO: --profile
|
271
|
+
# TODO: --prefix
|
272
|
+
# TODO: --sys-prefix
|
273
|
+
# TODO: --env
|
274
|
+
|
275
|
+
define_ipython_dir_option(opts, params)
|
276
|
+
|
277
|
+
opts.order!(argv)
|
278
|
+
|
279
|
+
params
|
280
|
+
end
|
281
|
+
|
282
|
+
UnregisterParams = Struct.new(
|
283
|
+
:names,
|
284
|
+
#:profile,
|
285
|
+
#:user,
|
286
|
+
#:prefix,
|
287
|
+
#:sys_prefix,
|
288
|
+
:ipython_dir,
|
289
|
+
:force,
|
290
|
+
:yes
|
291
|
+
) do
|
292
|
+
def initialize(*args, **kw)
|
293
|
+
super
|
294
|
+
self.names = []
|
295
|
+
# self.user = true
|
296
|
+
self.force = false
|
297
|
+
self.yes = false
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def unregister_kernel(argv)
|
302
|
+
params = parse_unregister_command_line(argv)
|
303
|
+
opts = []
|
304
|
+
opts << "-y" if params.yes
|
305
|
+
opts << "-f" if params.force
|
306
|
+
system("jupyter", "kernelspec", "uninstall", *opts, *params.names)
|
307
|
+
end
|
308
|
+
|
309
|
+
private def parse_unregister_command_line(argv)
|
310
|
+
opts = OptionParser.new
|
311
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} unregister [options] NAME [NAME ...]"
|
312
|
+
|
313
|
+
params = UnregisterParams.new
|
314
|
+
|
315
|
+
opts.on(
|
316
|
+
"-f", "--force",
|
317
|
+
"Force removal, don't prompt for confirmation."
|
318
|
+
) { params.force = true}
|
319
|
+
|
320
|
+
opts.on(
|
321
|
+
"-y", "--yes",
|
322
|
+
"Answer yes to any prompts."
|
323
|
+
) { params.yes = true }
|
324
|
+
|
325
|
+
# TODO: --user
|
326
|
+
# TODO: --profile
|
327
|
+
# TODO: --prefix
|
328
|
+
# TODO: --sys-prefix
|
329
|
+
|
330
|
+
define_ipython_dir_option(opts, params)
|
331
|
+
|
332
|
+
opts.order!(argv)
|
333
|
+
|
334
|
+
params.names = argv.dup
|
335
|
+
|
336
|
+
params
|
337
|
+
end
|
338
|
+
|
339
|
+
def exec_jupyter(sub_cmd, argv)
|
340
|
+
opts = OptionParser.new
|
341
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} unregister [options]"
|
342
|
+
|
343
|
+
kernel_name = resolve_installed_kernel_name(DEFAULT_KERNEL_NAME)
|
344
|
+
opts.on(
|
345
|
+
"--kernel=NAME", String,
|
346
|
+
"The name of the default kernel to start."
|
347
|
+
) {|v| kernel_name = v }
|
348
|
+
|
349
|
+
opts.order!(argv)
|
350
|
+
|
351
|
+
opts = ["--kernel=#{kernel_name}"]
|
352
|
+
exec("jupyter", "console", *opts)
|
353
|
+
end
|
354
|
+
|
355
|
+
private def resolve_installed_kernel_name(default_name)
|
356
|
+
kernels = IO.popen(["jupyter", "kernelspec", "list", "--json"], "r", err: File::NULL) do |jupyter_out|
|
357
|
+
JSON.load(jupyter_out.read)
|
358
|
+
end
|
359
|
+
unless kernels["kernelspecs"].key?(default_name)
|
360
|
+
return "ruby" if kernels["kernelspecs"].key?("ruby")
|
361
|
+
end
|
362
|
+
default_name
|
363
|
+
end
|
364
|
+
|
365
|
+
private def define_ipython_dir_option(opts, params)
|
366
|
+
opts.on(
|
367
|
+
"--ipython-dir=DIR", String,
|
368
|
+
"Specify the IPython's data directory (DEPRECATED)."
|
369
|
+
) do |v|
|
370
|
+
if ENV.key?("JUPYTER_DATA_DIR")
|
371
|
+
warn 'Both JUPYTER_DATA_DIR and --ipython-dir are supplied; --ipython-dir is ignored.'
|
372
|
+
else
|
373
|
+
warn '--ipython-dir is deprecated. Use JUPYTER_DATA_DIR environment variable instead.'
|
374
|
+
end
|
375
|
+
|
376
|
+
params.ipython_dir = v
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
data/lib/iruby/backend.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module IRuby
|
2
2
|
In, Out = [nil], [nil]
|
3
3
|
|
4
|
+
class << self
|
5
|
+
attr_accessor :silent_assignment
|
6
|
+
end
|
7
|
+
self.silent_assignment = false
|
8
|
+
|
4
9
|
module History
|
5
10
|
def eval(code, store_history)
|
6
11
|
b = eval_binding
|
@@ -52,8 +57,8 @@ module IRuby
|
|
52
57
|
end
|
53
58
|
|
54
59
|
def eval(code, store_history)
|
55
|
-
@irb.context.evaluate(code, 0)
|
56
|
-
@irb.context.last_value
|
60
|
+
@irb.context.evaluate(@irb.build_statement(code), 0)
|
61
|
+
@irb.context.last_value unless IRuby.silent_assignment && assignment_expression?(code)
|
57
62
|
end
|
58
63
|
|
59
64
|
def complete(code)
|
@@ -69,6 +74,10 @@ module IRuby
|
|
69
74
|
wrapper_module.include(*args)
|
70
75
|
end
|
71
76
|
end
|
77
|
+
|
78
|
+
def assignment_expression?(code)
|
79
|
+
@irb.respond_to?(:assignment_expression?) && @irb.assignment_expression?(code)
|
80
|
+
end
|
72
81
|
end
|
73
82
|
|
74
83
|
class PryBackend
|
data/lib/iruby/error.rb
ADDED
data/lib/iruby/jupyter.rb
CHANGED
@@ -2,8 +2,11 @@ module IRuby
|
|
2
2
|
module Jupyter
|
3
3
|
class << self
|
4
4
|
# User's default kernelspec directory is described here:
|
5
|
-
# https://jupyter.
|
5
|
+
# https://docs.jupyter.org/en/latest/use/jupyter-directories.html
|
6
6
|
def default_data_dir
|
7
|
+
data_dir = ENV["JUPYTER_DATA_DIR"]
|
8
|
+
return data_dir if data_dir
|
9
|
+
|
7
10
|
case
|
8
11
|
when windows?
|
9
12
|
appdata = windows_user_appdata
|
data/lib/iruby/kernel.rb
CHANGED
@@ -176,8 +176,9 @@ module IRuby
|
|
176
176
|
# @private
|
177
177
|
def execute_request(msg)
|
178
178
|
code = msg[:content]['code']
|
179
|
-
store_history = msg[:content]['store_history']
|
180
179
|
silent = msg[:content]['silent']
|
180
|
+
# https://jupyter-client.readthedocs.io/en/stable/messaging.html#execute
|
181
|
+
store_history = silent ? false : msg[:content].fetch('store_history', true)
|
181
182
|
|
182
183
|
@execution_count += 1 if store_history
|
183
184
|
|
@@ -218,7 +219,8 @@ module IRuby
|
|
218
219
|
end
|
219
220
|
|
220
221
|
events.trigger(:post_execute)
|
221
|
-
|
222
|
+
# **{} is for Ruby2.7. Gnuplot#to_hash returns an Array.
|
223
|
+
events.trigger(:post_run_cell, result, **{}) unless silent
|
222
224
|
|
223
225
|
@session.send(:reply, :execute_reply, content)
|
224
226
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module IRuby
|
2
|
+
class KernelApplication
|
3
|
+
def initialize(argv)
|
4
|
+
parse_command_line(argv)
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
if @test_mode
|
9
|
+
dump_connection_file
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
run_kernel
|
14
|
+
end
|
15
|
+
|
16
|
+
DEFAULT_CONNECTION_FILE = "kernel-#{Process.pid}.json".freeze
|
17
|
+
|
18
|
+
private def parse_command_line(argv)
|
19
|
+
opts = OptionParser.new
|
20
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options] [subcommand] [options]"
|
21
|
+
|
22
|
+
@connection_file = nil
|
23
|
+
opts.on(
|
24
|
+
"-f CONNECTION_FILE", String,
|
25
|
+
"JSON file in which to store connection info (default: kernel-<pid>.json)"
|
26
|
+
) {|v| @connection_file = v }
|
27
|
+
|
28
|
+
@test_mode = false
|
29
|
+
opts.on(
|
30
|
+
"--test",
|
31
|
+
"Run as test mode; dump the connection file and exit."
|
32
|
+
) { @test_mode = true }
|
33
|
+
|
34
|
+
@log_file = nil
|
35
|
+
opts.on(
|
36
|
+
"--log=FILE", String,
|
37
|
+
"Specify the log file."
|
38
|
+
) {|v| @log_file = v }
|
39
|
+
|
40
|
+
@log_level = Logger::INFO
|
41
|
+
opts.on(
|
42
|
+
"--debug",
|
43
|
+
"Set log-level debug"
|
44
|
+
) { @log_level = Logger::DEBUG }
|
45
|
+
|
46
|
+
opts.order!(argv)
|
47
|
+
|
48
|
+
if @connection_file.nil?
|
49
|
+
# Without -f option, the connection file is at the beginning of the rest arguments
|
50
|
+
if argv.length <= 3
|
51
|
+
@connection_file, @boot_file, @work_dir = argv
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Too many commandline arguments"
|
54
|
+
end
|
55
|
+
else
|
56
|
+
if argv.length <= 2
|
57
|
+
@boot_file, @work_dir = argv
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Too many commandline arguments"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@connection_file ||= DEFAULT_CONNECTION_FILE
|
64
|
+
end
|
65
|
+
|
66
|
+
private def dump_connection_file
|
67
|
+
puts File.read(@connection_file)
|
68
|
+
end
|
69
|
+
|
70
|
+
private def run_kernel
|
71
|
+
IRuby.logger = MultiLogger.new(*Logger.new(STDOUT))
|
72
|
+
STDOUT.sync = true # FIXME: This can make the integration test.
|
73
|
+
|
74
|
+
IRuby.logger.loggers << Logger.new(@log_file) unless @log_file.nil?
|
75
|
+
IRuby.logger.level = @log_level
|
76
|
+
|
77
|
+
if @work_dir
|
78
|
+
IRuby.logger.debug("iruby kernel") { "Change the working directory: #{@work_dir}" }
|
79
|
+
Dir.chdir(@work_dir)
|
80
|
+
end
|
81
|
+
|
82
|
+
if @boot_file
|
83
|
+
IRuby.logger.debug("iruby kernel") { "Load the boot file: #{@boot_file}" }
|
84
|
+
require @boot_file
|
85
|
+
end
|
86
|
+
|
87
|
+
check_bundler {|e| IRuby.logger.warn "Could not load bundler: #{e.message}" }
|
88
|
+
|
89
|
+
require "iruby"
|
90
|
+
Kernel.new(@connection_file).run
|
91
|
+
rescue Exception => e
|
92
|
+
IRuby.logger.fatal "Kernel died: #{e.message}\n#{e.backtrace.join("\n")}"
|
93
|
+
exit 1
|
94
|
+
end
|
95
|
+
|
96
|
+
private def check_bundler
|
97
|
+
require "bundler"
|
98
|
+
unless Bundler.definition.specs.any? {|s| s.name == "iruby" }
|
99
|
+
raise %{IRuby is missing from Gemfile. This might not work. Add `gem "iruby"` in your Gemfile to fix it.}
|
100
|
+
end
|
101
|
+
Bundler.setup
|
102
|
+
rescue LoadError
|
103
|
+
# do nothing
|
104
|
+
rescue Exception => e
|
105
|
+
yield e
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/iruby/logger.rb
CHANGED
@@ -6,10 +6,20 @@ module IRuby
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class MultiLogger < BasicObject
|
9
|
+
def initialize(*loggers, level: ::Logger::DEBUG)
|
10
|
+
@loggers = loggers
|
11
|
+
@level = level
|
12
|
+
end
|
13
|
+
|
9
14
|
attr_reader :loggers
|
10
15
|
|
11
|
-
|
12
|
-
|
16
|
+
attr_reader :level
|
17
|
+
|
18
|
+
def level=(new_level)
|
19
|
+
@loggers.each do |l|
|
20
|
+
l.level = new_level
|
21
|
+
end
|
22
|
+
@level = new_level
|
13
23
|
end
|
14
24
|
|
15
25
|
def method_missing(name, *args, &b)
|
data/lib/iruby/session/mixin.rb
CHANGED
@@ -23,7 +23,7 @@ module IRuby
|
|
23
23
|
idents, msg_list = frames[0..i-1], frames[i+1..-1]
|
24
24
|
|
25
25
|
minlen = 5
|
26
|
-
raise
|
26
|
+
raise "malformed message, must have at least #{minlen} elements" unless msg_list.length >= minlen
|
27
27
|
s, header, parent_header, metadata, content, buffers = *msg_list
|
28
28
|
raise 'Invalid signature' unless s == sign(msg_list[1..-1])
|
29
29
|
{
|
data/lib/iruby/session.rb
CHANGED
@@ -2,6 +2,7 @@ require 'iruby/session_adapter'
|
|
2
2
|
require 'iruby/session/mixin'
|
3
3
|
|
4
4
|
require 'securerandom'
|
5
|
+
require 'time'
|
5
6
|
|
6
7
|
module IRuby
|
7
8
|
class Session
|
@@ -79,6 +80,7 @@ module IRuby
|
|
79
80
|
header = {
|
80
81
|
msg_type: message_type,
|
81
82
|
msg_id: SecureRandom.uuid,
|
83
|
+
date: Time.now.utc.iso8601,
|
82
84
|
username: 'kernel',
|
83
85
|
session: @session_id,
|
84
86
|
version: '5.0'
|
@@ -40,14 +40,12 @@ module IRuby
|
|
40
40
|
|
41
41
|
require_relative 'session_adapter/ffirzmq_adapter'
|
42
42
|
require_relative 'session_adapter/cztop_adapter'
|
43
|
-
require_relative 'session_adapter/pyzmq_adapter'
|
44
43
|
require_relative 'session_adapter/test_adapter'
|
45
44
|
|
46
45
|
def self.select_adapter_class(name=nil)
|
47
46
|
classes = {
|
48
47
|
'ffi-rzmq' => SessionAdapter::FfirzmqAdapter,
|
49
48
|
'cztop' => SessionAdapter::CztopAdapter,
|
50
|
-
# 'pyzmq' => SessionAdapter::PyzmqAdapter
|
51
49
|
'test' => SessionAdapter::TestAdapter,
|
52
50
|
}
|
53
51
|
if (name ||= ENV.fetch('IRUBY_SESSION_ADAPTER', nil))
|
@@ -61,6 +59,9 @@ module IRuby
|
|
61
59
|
"Session adapter `#{name}` is unavailable"
|
62
60
|
end
|
63
61
|
end
|
62
|
+
if name == 'cztop'
|
63
|
+
warn "WARNING: cztop was deprecated and will be removed; Use ffi-rzmq, instead."
|
64
|
+
end
|
64
65
|
return cls
|
65
66
|
end
|
66
67
|
classes.each_value do |cls|
|
data/lib/iruby/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -2,18 +2,36 @@ require "iruby"
|
|
2
2
|
require "json"
|
3
3
|
require 'multi_json'
|
4
4
|
require "pathname"
|
5
|
+
require "rbconfig"
|
5
6
|
require "test/unit"
|
6
7
|
require "test/unit/rr"
|
7
8
|
require "tmpdir"
|
8
9
|
|
9
|
-
|
10
10
|
IRuby.logger = IRuby::MultiLogger.new(*Logger.new(STDERR, level: Logger::Severity::INFO))
|
11
11
|
|
12
12
|
module IRubyTest
|
13
13
|
class TestBase < Test::Unit::TestCase
|
14
|
+
TEST_DIR = File.expand_path("..", __FILE__).freeze
|
15
|
+
EXE_DIR = File.expand_path("../exe", TEST_DIR).freeze
|
16
|
+
LIB_DIR = File.expand_path("../lib", TEST_DIR).freeze
|
17
|
+
|
18
|
+
RUBY = RbConfig.ruby.freeze
|
19
|
+
IRUBY_PATH = File.join(EXE_DIR, "iruby").freeze
|
20
|
+
|
21
|
+
def iruby_command(*args)
|
22
|
+
[RUBY, "-I#{LIB_DIR}", IRUBY_PATH, *args]
|
23
|
+
end
|
24
|
+
|
14
25
|
def self.startup
|
15
|
-
@
|
26
|
+
@__work_dir = Dir.mktmpdir("iruby-test-data")
|
27
|
+
|
28
|
+
@__jupyter_data_dir = File.join(@__work_dir, "jupyter")
|
29
|
+
@__save_jupyter_data_dir = ENV["JUPYTER_DATA_DIR"]
|
30
|
+
ENV["JUPYTER_DATA_DIR"] = @__jupyter_data_dir
|
31
|
+
|
32
|
+
@__config_dir = File.join(@__work_dir, "config")
|
16
33
|
@__config_path = Pathname.new(@__config_dir) + "config.json"
|
34
|
+
@__config_path.dirname.mkpath
|
17
35
|
File.write(@__config_path, {
|
18
36
|
control_port: 50160,
|
19
37
|
shell_port: 57503,
|
@@ -30,7 +48,8 @@ module IRubyTest
|
|
30
48
|
end
|
31
49
|
|
32
50
|
def self.shutdown
|
33
|
-
FileUtils.remove_entry_secure(@
|
51
|
+
FileUtils.remove_entry_secure(@__work_dir)
|
52
|
+
ENV["JUPYTER_DATA_DIR"] = @__save_jupyter_data_dir
|
34
53
|
end
|
35
54
|
|
36
55
|
def self.test_config_filename
|