iruby 0.7.4 → 0.8.1
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 +102 -86
- data/CHANGES.md +31 -1
- data/Gemfile +5 -0
- 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 +24 -3
- data/lib/iruby/comm.rb +6 -6
- data/lib/iruby/display.rb +2 -3
- data/lib/iruby/error.rb +12 -0
- data/lib/iruby/formatter.rb +1 -1
- 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/ostream.rb +1 -1
- data/lib/iruby/session/mixin.rb +2 -2
- data/lib/iruby/session.rb +4 -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/run-test.rb +1 -0
- metadata +34 -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| params.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
|
@@ -45,6 +50,7 @@ module IRuby
|
|
45
50
|
@irb = IRB::Irb.new(@workspace)
|
46
51
|
@eval_path = @irb.context.irb_path
|
47
52
|
IRB.conf[:MAIN_CONTEXT] = @irb.context
|
53
|
+
@completor = IRB::RegexpCompletor.new if defined? IRB::RegexpCompletor # IRB::VERSION >= 1.8.2
|
48
54
|
end
|
49
55
|
|
50
56
|
def eval_binding
|
@@ -52,12 +58,23 @@ module IRuby
|
|
52
58
|
end
|
53
59
|
|
54
60
|
def eval(code, store_history)
|
55
|
-
@irb.context.evaluate(code, 0)
|
56
|
-
@irb.context.last_value
|
61
|
+
@irb.context.evaluate(parse_code(code), 0)
|
62
|
+
@irb.context.last_value unless IRuby.silent_assignment && assignment_expression?(code)
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_code(code)
|
66
|
+
return code if Gem::Version.new(IRB::VERSION) < Gem::Version.new('1.13.0')
|
67
|
+
return @irb.parse_input(code) if @irb.respond_to?(:parse_input)
|
68
|
+
return @irb.build_statement(code) if @irb.respond_to?(:build_statement)
|
57
69
|
end
|
58
70
|
|
59
71
|
def complete(code)
|
60
|
-
|
72
|
+
if @completor
|
73
|
+
# preposing and postposing never used, so they are empty, pass only target as code
|
74
|
+
@completor.completion_candidates('', code, '', bind: @workspace.binding)
|
75
|
+
else
|
76
|
+
IRB::InputCompletor::CompletionProc.call(code)
|
77
|
+
end
|
61
78
|
end
|
62
79
|
|
63
80
|
private
|
@@ -69,6 +86,10 @@ module IRuby
|
|
69
86
|
wrapper_module.include(*args)
|
70
87
|
end
|
71
88
|
end
|
89
|
+
|
90
|
+
def assignment_expression?(code)
|
91
|
+
@irb.respond_to?(:assignment_expression?) && @irb.assignment_expression?(code)
|
92
|
+
end
|
72
93
|
end
|
73
94
|
|
74
95
|
class PryBackend
|
data/lib/iruby/comm.rb
CHANGED
@@ -13,17 +13,17 @@ module IRuby
|
|
13
13
|
@target_name, @comm_id = target_name, comm_id
|
14
14
|
end
|
15
15
|
|
16
|
-
def open(**data)
|
17
|
-
Kernel.instance.session.send(:publish, :comm_open, comm_id: @comm_id, data: data, target_name: @target_name)
|
16
|
+
def open(metadata = nil, **data)
|
17
|
+
Kernel.instance.session.send(:publish, :comm_open, metadata, comm_id: @comm_id, data: data, target_name: @target_name)
|
18
18
|
Comm.comm[@comm_id] = self
|
19
19
|
end
|
20
20
|
|
21
|
-
def send(**data)
|
22
|
-
Kernel.instance.session.send(:publish, :comm_msg, comm_id: @comm_id, data: data)
|
21
|
+
def send(metadata = nil, **data)
|
22
|
+
Kernel.instance.session.send(:publish, :comm_msg, metadata, comm_id: @comm_id, data: data)
|
23
23
|
end
|
24
24
|
|
25
|
-
def close(**data)
|
26
|
-
Kernel.instance.session.send(:publish, :comm_close, comm_id: @comm_id, data: data)
|
25
|
+
def close(metadata = nil, **data)
|
26
|
+
Kernel.instance.session.send(:publish, :comm_close, metadata, comm_id: @comm_id, data: data)
|
27
27
|
Comm.comm.delete(@comm_id)
|
28
28
|
end
|
29
29
|
|
data/lib/iruby/display.rb
CHANGED
@@ -113,14 +113,14 @@ module IRuby
|
|
113
113
|
if FORCE_TEXT_TYPES.include?(mime)
|
114
114
|
true
|
115
115
|
else
|
116
|
-
MIME::Type.new(mime).ascii?
|
116
|
+
MIME::Type.new("content-type" => mime).ascii?
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
120
|
private def render_mimebundle(obj, exact_mime, fuzzy_mime)
|
121
121
|
data = {}
|
122
122
|
include_mime = [exact_mime].compact
|
123
|
-
formats,
|
123
|
+
formats, _metadata = obj.to_iruby_mimebundle(include: include_mime)
|
124
124
|
formats.each do |mime, value|
|
125
125
|
if fuzzy_mime.nil? || mime.include?(fuzzy_mime)
|
126
126
|
data[mime] = value
|
@@ -407,7 +407,6 @@ module IRuby
|
|
407
407
|
|
408
408
|
type { Gruff::Base }
|
409
409
|
format 'image' do |obj|
|
410
|
-
image = obj.to_image
|
411
410
|
format_magick_image.(obj.to_image)
|
412
411
|
end
|
413
412
|
|
data/lib/iruby/error.rb
ADDED
data/lib/iruby/formatter.rb
CHANGED
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/ostream.rb
CHANGED
data/lib/iruby/session/mixin.rb
CHANGED
@@ -4,10 +4,10 @@ module IRuby
|
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def serialize(idents, header, content)
|
7
|
+
def serialize(idents, header, metadata = nil, content)
|
8
8
|
msg = [MultiJson.dump(header),
|
9
9
|
MultiJson.dump(@last_recvd_msg ? @last_recvd_msg[:header] : {}),
|
10
|
-
|
10
|
+
MultiJson.dump(metadata || {}),
|
11
11
|
MultiJson.dump(content || {})]
|
12
12
|
frames = ([*idents].compact.map(&:to_s) << DELIM << sign(msg)) + msg
|
13
13
|
IRuby.logger.debug "Sent #{frames.inspect}"
|