iruby 0.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ubuntu.yml +62 -0
  3. data/CHANGES.md +203 -0
  4. data/Gemfile +3 -1
  5. data/LICENSE +1 -1
  6. data/README.md +137 -87
  7. data/Rakefile +36 -10
  8. data/ci/Dockerfile.base.erb +41 -0
  9. data/ci/Dockerfile.main.erb +7 -0
  10. data/ci/requirements.txt +1 -0
  11. data/docker/setup.sh +15 -0
  12. data/docker/test.sh +7 -0
  13. data/iruby.gemspec +14 -18
  14. data/lib/iruby.rb +14 -8
  15. data/lib/iruby/backend.rb +38 -10
  16. data/lib/iruby/command.rb +67 -15
  17. data/lib/iruby/display.rb +77 -41
  18. data/lib/iruby/event_manager.rb +40 -0
  19. data/lib/iruby/formatter.rb +3 -3
  20. data/lib/iruby/input.rb +6 -6
  21. data/lib/iruby/input/README.md +299 -0
  22. data/lib/iruby/input/autoload.rb +1 -1
  23. data/lib/iruby/input/builder.rb +4 -4
  24. data/lib/iruby/input/button.rb +2 -2
  25. data/lib/iruby/input/cancel.rb +1 -1
  26. data/lib/iruby/input/checkbox.rb +3 -3
  27. data/lib/iruby/input/date.rb +3 -3
  28. data/lib/iruby/input/field.rb +2 -2
  29. data/lib/iruby/input/file.rb +3 -3
  30. data/lib/iruby/input/form.rb +6 -6
  31. data/lib/iruby/input/label.rb +4 -4
  32. data/lib/iruby/input/multiple.rb +10 -10
  33. data/lib/iruby/input/popup.rb +2 -2
  34. data/lib/iruby/input/radio.rb +6 -6
  35. data/lib/iruby/input/select.rb +8 -8
  36. data/lib/iruby/input/textarea.rb +1 -1
  37. data/lib/iruby/input/widget.rb +2 -2
  38. data/lib/iruby/jupyter.rb +77 -0
  39. data/lib/iruby/kernel.rb +204 -36
  40. data/lib/iruby/ostream.rb +29 -8
  41. data/lib/iruby/session.rb +117 -0
  42. data/lib/iruby/session/cztop.rb +4 -0
  43. data/lib/iruby/session_adapter.rb +72 -0
  44. data/lib/iruby/session_adapter/cztop_adapter.rb +45 -0
  45. data/lib/iruby/session_adapter/ffirzmq_adapter.rb +55 -0
  46. data/lib/iruby/session_adapter/pyzmq_adapter.rb +77 -0
  47. data/lib/iruby/session_adapter/test_adapter.rb +49 -0
  48. data/lib/iruby/utils.rb +13 -2
  49. data/lib/iruby/version.rb +1 -1
  50. data/run-test.sh +12 -0
  51. data/tasks/ci.rake +65 -0
  52. data/test/helper.rb +136 -0
  53. data/test/integration_test.rb +22 -11
  54. data/test/iruby/backend_test.rb +37 -0
  55. data/test/iruby/command_test.rb +207 -0
  56. data/test/iruby/event_manager_test.rb +92 -0
  57. data/test/iruby/jupyter_test.rb +27 -0
  58. data/test/iruby/kernel_test.rb +185 -0
  59. data/test/iruby/mime_test.rb +50 -0
  60. data/test/iruby/multi_logger_test.rb +1 -5
  61. data/test/iruby/session_adapter/cztop_adapter_test.rb +20 -0
  62. data/test/iruby/session_adapter/ffirzmq_adapter_test.rb +20 -0
  63. data/test/iruby/session_adapter/session_adapter_test_base.rb +27 -0
  64. data/test/iruby/session_adapter_test.rb +91 -0
  65. data/test/iruby/session_test.rb +48 -0
  66. data/test/run-test.rb +19 -0
  67. metadata +120 -50
  68. data/.travis.yml +0 -16
  69. data/CHANGES +0 -143
  70. data/CONTRIBUTORS +0 -19
  71. data/lib/iruby/session/rbczmq.rb +0 -68
  72. data/test/test_helper.rb +0 -5
data/Rakefile CHANGED
@@ -1,15 +1,41 @@
1
- require 'rake/testtask'
1
+ require "bundler/gem_helper"
2
2
 
3
- begin
4
- require 'bundler/gem_tasks'
5
- rescue Exception
6
- end
3
+ base_dir = File.join(File.dirname(__FILE__))
4
+
5
+ helper = Bundler::GemHelper.new(base_dir)
6
+ helper.install
7
7
 
8
- Rake::TestTask.new('test') do |t|
9
- t.libs << 'lib'
10
- t.libs << 'test'
11
- t.test_files = FileList['test/**/*_test.rb']
12
- t.verbose = true
8
+ FileList['tasks/**.rake'].each {|f| load f }
9
+
10
+ desc "Run tests"
11
+ task :test do
12
+ cd(base_dir) do
13
+ ruby("test/run-test.rb")
14
+ end
13
15
  end
14
16
 
15
17
  task default: 'test'
18
+
19
+ namespace :docker do
20
+ def root_dir
21
+ @root_dir ||= File.expand_path("..", __FILE__)
22
+ end
23
+
24
+ task :build do
25
+ container_name = "iruby_build"
26
+ image_name = "mrkn/iruby"
27
+ sh "docker", "run",
28
+ "--name", container_name,
29
+ "-v", "#{root_dir}:/tmp/iruby",
30
+ "rubylang/ruby", "/bin/bash", "/tmp/iruby/docker/setup.sh"
31
+ sh "docker", "commit", container_name, image_name
32
+ sh "docker", "rm", container_name
33
+ end
34
+
35
+ task :test do
36
+ root_dir = File.expand_path("..", __FILE__)
37
+ sh "docker", "run", "-it", "--rm",
38
+ "-v", "#{root_dir}:/tmp/iruby",
39
+ "mrkn/iruby", "/bin/bash", "/tmp/iruby/docker/test.sh"
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ FROM rubylang/ruby:<%= ruby_version %>-bionic
2
+
3
+ ADD ci/requirements.txt /tmp
4
+
5
+ RUN apt-get update \
6
+ && apt-get install -y --no-install-recommends \
7
+ libczmq-dev \
8
+ python3 \
9
+ python3-pip \
10
+ python3-setuptools \
11
+ libpython3.6 \
12
+ && pip3 install wheel \
13
+ && pip3 install -r /tmp/requirements.txt \
14
+ && rm -f /tmp/requirements.txt
15
+
16
+ # ZeroMQ version 4.1.6 and CZMQ version 3.0.2 for rbczmq
17
+ RUN apt-get update \
18
+ && apt-get install -y --no-install-recommends \
19
+ build-essential \
20
+ file \
21
+ wget \
22
+ && cd /tmp \
23
+ && wget https://github.com/zeromq/zeromq4-1/releases/download/v4.1.6/zeromq-4.1.6.tar.gz \
24
+ && wget https://archive.org/download/zeromq_czmq_3.0.2/czmq-3.0.2.tar.gz \
25
+ && tar xf zeromq-4.1.6.tar.gz \
26
+ && tar xf czmq-3.0.2.tar.gz \
27
+ && \
28
+ ( \
29
+ cd zeromq-4.1.6 \
30
+ && ./configure \
31
+ && make install \
32
+ ) \
33
+ && \
34
+ ( \
35
+ cd czmq-3.0.2 \
36
+ && wget -O 1.patch https://github.com/zeromq/czmq/commit/2594d406d8ec6f54e54d7570d7febba10a6906b2.diff \
37
+ && wget -O 2.patch https://github.com/zeromq/czmq/commit/b651cb479235751b22b8f9a822a2fc6bc1be01ab.diff \
38
+ && cat *.patch | patch -p1 \
39
+ && ./configure \
40
+ && make install \
41
+ )
@@ -0,0 +1,7 @@
1
+ FROM iruby-test-base:ruby-<%= ruby_version %>
2
+
3
+ RUN gem install cztop
4
+ RUN mkdir -p /iruby
5
+ ADD . /iruby
6
+ WORKDIR /iruby
7
+ RUN bundle install
@@ -0,0 +1 @@
1
+ jupyter-console>=6.0.0
data/docker/setup.sh ADDED
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ set -ex
4
+
5
+ apt-get update
6
+ apt-get install -y --no-install-recommends \
7
+ libczmq-dev \
8
+ python3 \
9
+ python3-pip \
10
+ python3-setuptools \
11
+ python3-wheel
12
+
13
+ cd /tmp/iruby
14
+ bundle install --with test --without plot
15
+ pip3 install jupyter
data/docker/test.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ set -ex
4
+
5
+ cd /tmp/iruby
6
+ bundle install --with test --without plot
7
+ bundle exec rake test
data/iruby.gemspec CHANGED
@@ -1,34 +1,30 @@
1
- # coding: utf-8
2
1
  require_relative 'lib/iruby/version'
3
- require 'date'
4
2
 
5
3
  Gem::Specification.new do |s|
6
4
  s.name = 'iruby'
7
- s.date = Date.today.to_s
8
5
  s.version = IRuby::VERSION
9
6
  s.authors = ['Daniel Mendler', 'The SciRuby developers']
10
7
  s.email = ['mail@daniel-mendler.de']
11
- s.summary = 'Ruby Kernel for Jupyter/IPython'
12
- s.description = 'A Ruby kernel for Jupyter/IPython frontends (e.g. notebook). Try it at try.jupyter.org.'
8
+ s.summary = 'Ruby Kernel for Jupyter'
9
+ s.description = 'A Ruby kernel for Jupyter environment. Try it at try.jupyter.org.'
13
10
  s.homepage = 'https://github.com/SciRuby/iruby'
14
11
  s.license = 'MIT'
15
12
 
16
- s.files = `git ls-files`.split($/)
13
+ s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
14
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
15
  s.test_files = s.files.grep(%r{^test/})
19
- s.require_paths = %w(lib)
16
+ s.require_paths = %w[lib]
20
17
 
21
- m = "Consider installing the optional dependencies to get additional functionality:\n"
22
- File.read('Gemfile').scan(/gem\s+'(.*?)'/) { m << " * #{$1}\n" }
23
- s.post_install_message = m << "\n"
18
+ s.required_ruby_version = '>= 2.3.0'
24
19
 
25
- s.required_ruby_version = '>= 2.1.0'
20
+ s.add_dependency 'data_uri', '~> 0.1'
21
+ s.add_dependency 'ffi-rzmq'
22
+ s.add_dependency 'irb'
23
+ s.add_dependency 'mime-types', '>= 3.3.1'
24
+ s.add_dependency 'multi_json', '~> 1.11'
26
25
 
27
- s.add_development_dependency 'rake', '~> 10.4'
28
- s.add_development_dependency 'minitest', '~> 5.6'
29
-
30
- s.add_runtime_dependency 'bond', '~> 0.5'
31
- s.add_runtime_dependency 'multi_json', '~> 1.11'
32
- s.add_runtime_dependency 'mimemagic', '~> 0.3'
33
- s.add_runtime_dependency 'data_uri', '~> 0.1'
26
+ s.add_development_dependency 'pycall', '>= 1.2.1'
27
+ s.add_development_dependency 'rake'
28
+ s.add_development_dependency 'test-unit'
29
+ s.add_development_dependency 'test-unit-rr'
34
30
  end
data/lib/iruby.rb CHANGED
@@ -1,10 +1,16 @@
1
- require 'mimemagic'
1
+ require 'fileutils'
2
+ require 'mime/types'
2
3
  require 'multi_json'
3
4
  require 'securerandom'
4
5
  require 'openssl'
5
6
  require 'tempfile'
6
7
  require 'set'
8
+ require 'stringio'
9
+
7
10
  require 'iruby/version'
11
+ require 'iruby/jupyter'
12
+ require 'iruby/event_manager'
13
+ require 'iruby/logger'
8
14
  require 'iruby/kernel'
9
15
  require 'iruby/backend'
10
16
  require 'iruby/ostream'
@@ -13,18 +19,18 @@ require 'iruby/formatter'
13
19
  require 'iruby/utils'
14
20
  require 'iruby/display'
15
21
  require 'iruby/comm'
16
- require 'iruby/session/mixin'
17
22
 
18
- begin
19
- require 'iruby/session/cztop'
20
- rescue LoadError
23
+ if ENV.fetch('IRUBY_OLD_SESSION', false)
24
+ require 'iruby/session/mixin'
21
25
  begin
22
- require 'iruby/session/rbczmq'
26
+ require 'iruby/session/ffi_rzmq'
23
27
  rescue LoadError
24
28
  begin
25
- require 'iruby/session/ffi_rzmq'
29
+ require 'iruby/session/cztop'
26
30
  rescue LoadError
27
- STDERR.puts "You should install cztop, rbczmq or ffi_rzmq before running iruby notebook. See README."
31
+ STDERR.puts "Please install ffi-rzmq or cztop before running iruby. See README."
28
32
  end
29
33
  end
34
+ else
35
+ require 'iruby/session'
30
36
  end
data/lib/iruby/backend.rb CHANGED
@@ -1,10 +1,9 @@
1
1
  module IRuby
2
2
  In, Out = [nil], [nil]
3
- ::In, ::Out = In, Out
4
3
 
5
4
  module History
6
5
  def eval(code, store_history)
7
- b = TOPLEVEL_BINDING
6
+ b = eval_binding
8
7
 
9
8
  b.local_variable_set(:_ih, In) unless b.local_variable_defined?(:_ih)
10
9
  b.local_variable_set(:_oh, Out) unless b.local_variable_defined?(:_oh)
@@ -33,45 +32,74 @@ module IRuby
33
32
  end
34
33
 
35
34
  class PlainBackend
35
+ attr_reader :eval_path
36
36
  prepend History
37
37
 
38
38
  def initialize
39
- require 'bond'
40
- Bond.start(debug: true)
39
+ require 'irb'
40
+ require 'irb/completion'
41
+ IRB.setup(nil)
42
+ @main = TOPLEVEL_BINDING.eval("self").dup
43
+ @workspace = IRB::WorkSpace.new(@main)
44
+ @irb = IRB::Irb.new(@workspace)
45
+ @eval_path = @irb.context.irb_path
46
+ IRB.conf[:MAIN_CONTEXT] = @irb.context
47
+ end
48
+
49
+ def eval_binding
50
+ @workspace.binding
41
51
  end
42
52
 
43
53
  def eval(code, store_history)
44
- TOPLEVEL_BINDING.eval(code)
54
+ @irb.context.evaluate(code, 0)
55
+ @irb.context.last_value
45
56
  end
46
57
 
47
58
  def complete(code)
48
- Bond.agent.call(code, code)
59
+ IRB::InputCompletor::CompletionProc.call(code)
49
60
  end
50
61
  end
51
62
 
52
63
  class PryBackend
64
+ attr_reader :eval_path
53
65
  prepend History
54
66
 
55
67
  def initialize
56
68
  require 'pry'
57
- Pry.memory_size = 3
69
+ Pry.memory_size = 3
58
70
  Pry.pager = false # Don't use the pager
59
71
  Pry.print = proc {|output, value|} # No result printing
60
72
  Pry.exception_handler = proc {|output, exception, _| }
73
+ @eval_path = Pry.eval_path
61
74
  reset
62
75
  end
63
76
 
77
+ def eval_binding
78
+ TOPLEVEL_BINDING
79
+ end
80
+
64
81
  def eval(code, store_history)
82
+ Pry.current_line = 1
65
83
  @pry.last_result = nil
66
84
  unless @pry.eval(code)
67
85
  reset
68
86
  raise SystemExit
69
87
  end
70
- unless @pry.eval_string.empty?
88
+
89
+ # Pry::Code.complete_expression? return false
90
+ if !@pry.eval_string.empty?
71
91
  syntax_error = @pry.eval_string
72
92
  @pry.reset_eval_string
73
- @pry.evaluate_ruby syntax_error
93
+ @pry.evaluate_ruby(syntax_error)
94
+
95
+ # Pry::Code.complete_expression? raise SyntaxError
96
+ # evaluate again for current line number
97
+ elsif @pry.last_result_is_exception? &&
98
+ @pry.last_exception.is_a?(SyntaxError) &&
99
+ @pry.last_exception.is_a?(Pry::UserError)
100
+ @pry.evaluate_ruby(code)
74
101
  end
102
+
75
103
  raise @pry.last_exception if @pry.last_result_is_exception?
76
104
  @pry.push_initial_binding unless @pry.current_binding # ensure that we have a binding
77
105
  @pry.last_result
@@ -82,7 +110,7 @@ module IRuby
82
110
  end
83
111
 
84
112
  def reset
85
- @pry = Pry.new(output: $stdout, target: TOPLEVEL_BINDING)
113
+ @pry = Pry.new(output: $stdout, target: eval_binding)
86
114
  end
87
115
  end
88
116
  end
data/lib/iruby/command.rb CHANGED
@@ -1,20 +1,22 @@
1
- require 'fileutils'
2
- require 'multi_json'
1
+ require 'iruby'
3
2
 
4
3
  module IRuby
5
4
  class Command
6
5
  def initialize(args)
7
6
  @args = args
8
7
 
9
- ipython_dir = ENV['IPYTHONDIR'] || '~/.ipython'
10
- @args.each do |arg|
11
- ipython_dir = $1 if arg =~ /\A--ipython-dir=(.*)\Z/
12
- end
13
- @kernel_dir = File.join(File.expand_path(ipython_dir), 'kernels', 'ruby')
14
- @kernel_file = File.join(@kernel_dir, 'kernel.json')
8
+ @ipython_dir = File.expand_path("~/.ipython")
9
+ @kernel_dir = resolve_kernelspec_dir.freeze
10
+ @kernel_file = File.join(@kernel_dir, 'kernel.json').freeze
15
11
  @iruby_path = File.expand_path $0
16
12
  end
17
13
 
14
+ attr_reader :ipython_dir, :kernel_dir, :kernel_file
15
+
16
+ def ipython_kernel_dir
17
+ File.join(File.expand_path(@ipython_dir), 'kernels', 'ruby')
18
+ end
19
+
18
20
  def run
19
21
  case @args.first
20
22
  when 'version', '-v', '--version'
@@ -23,11 +25,12 @@ module IRuby
23
25
  when 'help', '-h', '--help'
24
26
  print_help
25
27
  when 'register'
26
- if registered_iruby_path && !@args.include?('--force')
28
+ force_p = @args.include?('--force')
29
+ if registered_iruby_path && !force_p
27
30
  STDERR.puts "#{@kernel_file} already exists!\nUse --force to force a register."
28
31
  exit 1
29
32
  end
30
- register_kernel
33
+ register_kernel(force_p)
31
34
  when 'unregister'
32
35
  unregister_kernel
33
36
  when 'kernel'
@@ -39,6 +42,38 @@ module IRuby
39
42
 
40
43
  private
41
44
 
45
+ def resolve_kernelspec_dir
46
+ if ENV.has_key?('JUPYTER_DATA_DIR')
47
+ if ENV.has_key?('IPYTHONDIR')
48
+ warn 'both JUPYTER_DATA_DIR and IPYTHONDIR are supplied; IPYTHONDIR is ignored.'
49
+ end
50
+ if @args.find {|x| /\A--ipython-dir=/ =~ x }
51
+ warn 'both JUPYTER_DATA_DIR and --ipython-dir are supplied; --ipython-dir is ignored.'
52
+ end
53
+ jupyter_data_dir = ENV['JUPYTER_DATA_DIR']
54
+ return File.join(jupyter_data_dir, 'kernels', 'ruby')
55
+ end
56
+
57
+ if ENV.has_key?('IPYTHONDIR')
58
+ warn 'IPYTHONDIR is deprecated. Use JUPYTER_DATA_DIR instead.'
59
+ ipython_dir = ENV['IPYTHONDIR']
60
+ end
61
+
62
+ @args.each do |arg|
63
+ next unless /\A--ipython-dir=(.*)\Z/ =~ arg
64
+ ipython_dir = Regexp.last_match[1]
65
+ warn '--ipython-dir is deprecated. Use JUPYTER_DATA_DIR environment variable instead.'
66
+ break
67
+ end
68
+
69
+ if ipython_dir
70
+ @ipython_dir = ipython_dir
71
+ ipython_kernel_dir
72
+ else
73
+ File.join(Jupyter.kernelspec_dir, 'ruby')
74
+ end
75
+ end
76
+
42
77
  def print_help
43
78
  puts %{
44
79
  Usage:
@@ -54,8 +89,9 @@ Try `ipython help` for more information.
54
89
  end
55
90
 
56
91
  def run_kernel
57
- require 'iruby/logger'
58
92
  IRuby.logger = MultiLogger.new(*Logger.new(STDOUT))
93
+ STDOUT.sync = true # FIXME: This can make the integration test.
94
+
59
95
  @args.reject! {|arg| arg =~ /\A--log=(.*)\Z/ && IRuby.logger.loggers << Logger.new($1) }
60
96
  IRuby.logger.level = @args.delete('--debug') ? Logger::DEBUG : Logger::INFO
61
97
 
@@ -64,7 +100,7 @@ Try `ipython help` for more information.
64
100
  Dir.chdir(working_dir) if working_dir
65
101
 
66
102
  require boot_file if boot_file
67
- check_bundler {|e| IRuby.logger.warn "Could not load bundler: #{e.message}\n#{e.backtrace.join("\n")}" }
103
+ check_bundler {|e| IRuby.logger.warn "Could not load bundler: #{e.message}" }
68
104
 
69
105
  require 'iruby'
70
106
  Kernel.new(config_file).run
@@ -91,11 +127,11 @@ Try `ipython help` for more information.
91
127
  check_registered_kernel
92
128
  check_bundler {|e| STDERR.puts "Could not load bundler: #{e.message}" }
93
129
 
94
- Kernel.exec('ipython', *@args)
130
+ Process.exec('ipython', *@args)
95
131
  end
96
132
 
97
133
  def check_registered_kernel
98
- if kernel = registered_iruby_path
134
+ if (kernel = registered_iruby_path)
99
135
  STDERR.puts "#{@iruby_path} differs from registered path #{registered_iruby_path}.
100
136
  This might not work. Run 'iruby register --force' to fix it." if @iruby_path != kernel
101
137
  else
@@ -113,7 +149,12 @@ Add `gem 'iruby'` to your Gemfile to fix it.} unless Bundler.definition.specs.an
113
149
  yield(e)
114
150
  end
115
151
 
116
- def register_kernel
152
+ def register_kernel(force_p=false)
153
+ if force_p
154
+ unregister_kernel_in_ipython_dir
155
+ else
156
+ return unless check_existing_kernel_in_ipython_dir
157
+ end
117
158
  FileUtils.mkpath(@kernel_dir)
118
159
  unless RUBY_PLATFORM =~ /mswin(?!ce)|mingw|cygwin/
119
160
  File.write(@kernel_file, MultiJson.dump(argv: [ @iruby_path, 'kernel', '{connection_file}' ],
@@ -127,6 +168,13 @@ Add `gem 'iruby'` to your Gemfile to fix it.} unless Bundler.definition.specs.an
127
168
  FileUtils.copy(Dir[File.join(__dir__, 'assets', '*')], @kernel_dir) rescue nil
128
169
  end
129
170
 
171
+ def check_existing_kernel_in_ipython_dir
172
+ return true unless File.file?(File.join(ipython_kernel_dir, 'kernel.json'))
173
+ warn "IRuby kernel file already exists in the deprecated IPython's data directory."
174
+ warn "Using --force, you can replace the old kernel file with the new one in Jupyter's data directory."
175
+ false
176
+ end
177
+
130
178
  def registered_iruby_path
131
179
  File.exist?(@kernel_file) && MultiJson.load(File.read(@kernel_file))['argv'].first
132
180
  end
@@ -134,5 +182,9 @@ Add `gem 'iruby'` to your Gemfile to fix it.} unless Bundler.definition.specs.an
134
182
  def unregister_kernel
135
183
  FileUtils.rm_rf(@kernel_dir)
136
184
  end
185
+
186
+ def unregister_kernel_in_ipython_dir
187
+ FileUtils.rm_rf(ipython_kernel_dir)
188
+ end
137
189
  end
138
190
  end