iruby 0.7.4 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +80 -85
- data/CHANGES.md +21 -1
- data/README.md +19 -9
- data/Rakefile +2 -1
- data/exe/iruby +7 -0
- data/iruby.gemspec +3 -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.rb +2 -0
- 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/run-test.rb +1 -0
- metadata +37 -11
- data/bin/iruby +0 -5
- data/lib/iruby/command.rb +0 -190
- 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.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'
|
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
|
data/test/integration_test.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
+
require 'bundler'
|
1
2
|
require 'pty'
|
2
3
|
require 'expect'
|
3
4
|
|
4
5
|
class IRubyTest::IntegrationTest < IRubyTest::TestBase
|
5
6
|
def setup
|
7
|
+
system(*iruby_command("register", "--name=iruby-test"), out: File::NULL, err: File::NULL)
|
8
|
+
kernel_json = File.join(ENV["JUPYTER_DATA_DIR"], "kernels", "iruby-test", "kernel.json")
|
9
|
+
assert_path_exist kernel_json
|
10
|
+
|
6
11
|
$expect_verbose = false # make true if you want to dump the output of iruby console
|
7
12
|
|
8
|
-
|
13
|
+
command = iruby_command("console", "--kernel=iruby-test").map {|x| %Q["#{x}"] }
|
14
|
+
@in, @out, pid = PTY.spawn(command.join(" "))
|
9
15
|
@waiter = Thread.start { Process.waitpid(pid) }
|
10
16
|
expect 'In [', 30
|
11
17
|
expect '1'
|