iruby 0.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +35 -10
  3. data/Gemfile +4 -0
  4. data/README.md +115 -84
  5. data/Rakefile +26 -0
  6. data/ci/Dockerfile.base.erb +41 -0
  7. data/ci/Dockerfile.main.erb +9 -0
  8. data/ci/requirements.txt +1 -0
  9. data/docker/setup.sh +15 -0
  10. data/docker/test.sh +7 -0
  11. data/iruby.gemspec +4 -2
  12. data/lib/iruby.rb +13 -7
  13. data/lib/iruby/command.rb +67 -11
  14. data/lib/iruby/input/README.md +299 -0
  15. data/lib/iruby/jupyter.rb +76 -0
  16. data/lib/iruby/kernel.rb +49 -9
  17. data/lib/iruby/ostream.rb +4 -0
  18. data/lib/iruby/session.rb +116 -0
  19. data/lib/iruby/session/cztop.rb +4 -0
  20. data/lib/iruby/session/rbczmq.rb +5 -1
  21. data/lib/iruby/session_adapter.rb +68 -0
  22. data/lib/iruby/session_adapter/cztop_adapter.rb +45 -0
  23. data/lib/iruby/session_adapter/ffirzmq_adapter.rb +55 -0
  24. data/lib/iruby/session_adapter/pyzmq_adapter.rb +76 -0
  25. data/lib/iruby/session_adapter/rbczmq_adapter.rb +33 -0
  26. data/lib/iruby/utils.rb +1 -2
  27. data/lib/iruby/version.rb +1 -1
  28. data/run-test.sh +12 -0
  29. data/tasks/ci.rake +65 -0
  30. data/test/integration_test.rb +22 -10
  31. data/test/iruby/command_test.rb +208 -0
  32. data/test/iruby/jupyter_test.rb +28 -0
  33. data/test/iruby/multi_logger_test.rb +1 -1
  34. data/test/iruby/session_adapter/cztop_adapter_test.rb +20 -0
  35. data/test/iruby/session_adapter/ffirzmq_adapter_test.rb +20 -0
  36. data/test/iruby/session_adapter/rbczmq_adapter_test.rb +37 -0
  37. data/test/iruby/session_adapter/session_adapter_test_base.rb +29 -0
  38. data/test/iruby/session_adapter_test.rb +116 -0
  39. data/test/iruby/session_test.rb +53 -0
  40. data/test/test_helper.rb +44 -1
  41. metadata +72 -12
@@ -4,7 +4,9 @@ require 'securerandom'
4
4
  require 'openssl'
5
5
  require 'tempfile'
6
6
  require 'set'
7
+
7
8
  require 'iruby/version'
9
+ require 'iruby/jupyter'
8
10
  require 'iruby/kernel'
9
11
  require 'iruby/backend'
10
12
  require 'iruby/ostream'
@@ -13,18 +15,22 @@ require 'iruby/formatter'
13
15
  require 'iruby/utils'
14
16
  require 'iruby/display'
15
17
  require 'iruby/comm'
16
- require 'iruby/session/mixin'
17
18
 
18
- begin
19
- require 'iruby/session/cztop'
20
- rescue LoadError
19
+ if ENV.fetch('IRUBY_OLD_SESSION', false)
20
+ require 'iruby/session/mixin'
21
21
  begin
22
- require 'iruby/session/rbczmq'
22
+ require 'iruby/session/ffi_rzmq'
23
23
  rescue LoadError
24
24
  begin
25
- require 'iruby/session/ffi_rzmq'
25
+ require 'iruby/session/cztop'
26
26
  rescue LoadError
27
- STDERR.puts "You should install cztop, rbczmq or ffi_rzmq before running iruby notebook. See README."
27
+ begin
28
+ require 'iruby/session/rbczmq'
29
+ rescue LoadError
30
+ STDERR.puts "You should install ffi-rzmq, cztop, or rbczmq before running iruby notebook. See README."
31
+ end
28
32
  end
29
33
  end
34
+ else
35
+ require 'iruby/session'
30
36
  end
@@ -1,3 +1,5 @@
1
+ require 'iruby/jupyter'
2
+
1
3
  require 'fileutils'
2
4
  require 'multi_json'
3
5
 
@@ -6,15 +8,18 @@ module IRuby
6
8
  def initialize(args)
7
9
  @args = args
8
10
 
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')
11
+ @ipython_dir = File.expand_path("~/.ipython")
12
+ @kernel_dir = resolve_kernelspec_dir.freeze
13
+ @kernel_file = File.join(@kernel_dir, 'kernel.json').freeze
15
14
  @iruby_path = File.expand_path $0
16
15
  end
17
16
 
17
+ attr_reader :ipython_dir, :kernel_dir, :kernel_file
18
+
19
+ def ipython_kernel_dir
20
+ File.join(File.expand_path(@ipython_dir), 'kernels', 'ruby')
21
+ end
22
+
18
23
  def run
19
24
  case @args.first
20
25
  when 'version', '-v', '--version'
@@ -23,11 +28,12 @@ module IRuby
23
28
  when 'help', '-h', '--help'
24
29
  print_help
25
30
  when 'register'
26
- if registered_iruby_path && !@args.include?('--force')
31
+ force_p = @args.include?('--force')
32
+ if registered_iruby_path && !force_p
27
33
  STDERR.puts "#{@kernel_file} already exists!\nUse --force to force a register."
28
34
  exit 1
29
35
  end
30
- register_kernel
36
+ register_kernel(force_p)
31
37
  when 'unregister'
32
38
  unregister_kernel
33
39
  when 'kernel'
@@ -39,6 +45,38 @@ module IRuby
39
45
 
40
46
  private
41
47
 
48
+ def resolve_kernelspec_dir
49
+ if ENV.has_key?('JUPYTER_DATA_DIR')
50
+ if ENV.has_key?('IPYTHONDIR')
51
+ warn 'both JUPYTER_DATA_DIR and IPYTHONDIR are supplied; IPYTHONDIR is ignored.'
52
+ end
53
+ if @args.find {|x| /\A--ipython-dir=/ =~ x }
54
+ warn 'both JUPYTER_DATA_DIR and --ipython-dir are supplied; --ipython-dir is ignored.'
55
+ end
56
+ jupyter_data_dir = ENV['JUPYTER_DATA_DIR']
57
+ return File.join(jupyter_data_dir, 'kernels', 'ruby')
58
+ end
59
+
60
+ if ENV.has_key?('IPYTHONDIR')
61
+ warn 'IPYTHONDIR is deprecated. Use JUPYTER_DATA_DIR instead.'
62
+ ipython_dir = ENV['IPYTHONDIR']
63
+ end
64
+
65
+ @args.each do |arg|
66
+ next unless /\A--ipython-dir=(.*)\Z/ =~ arg
67
+ ipython_dir = Regexp.last_match[1]
68
+ warn '--ipython-dir is deprecated. Use JUPYTER_DATA_DIR environment variable instead.'
69
+ break
70
+ end
71
+
72
+ if ipython_dir
73
+ @ipython_dir = ipython_dir
74
+ ipython_kernel_dir
75
+ else
76
+ File.join(Jupyter.kernelspec_dir, 'ruby')
77
+ end
78
+ end
79
+
42
80
  def print_help
43
81
  puts %{
44
82
  Usage:
@@ -56,6 +94,8 @@ Try `ipython help` for more information.
56
94
  def run_kernel
57
95
  require 'iruby/logger'
58
96
  IRuby.logger = MultiLogger.new(*Logger.new(STDOUT))
97
+ STDOUT.sync = true # FIXME: This can make the integration test.
98
+
59
99
  @args.reject! {|arg| arg =~ /\A--log=(.*)\Z/ && IRuby.logger.loggers << Logger.new($1) }
60
100
  IRuby.logger.level = @args.delete('--debug') ? Logger::DEBUG : Logger::INFO
61
101
 
@@ -64,7 +104,7 @@ Try `ipython help` for more information.
64
104
  Dir.chdir(working_dir) if working_dir
65
105
 
66
106
  require boot_file if boot_file
67
- check_bundler {|e| IRuby.logger.warn "Could not load bundler: #{e.message}\n#{e.backtrace.join("\n")}" }
107
+ check_bundler {|e| IRuby.logger.warn "Could not load bundler: #{e.message}" }
68
108
 
69
109
  require 'iruby'
70
110
  Kernel.new(config_file).run
@@ -95,7 +135,7 @@ Try `ipython help` for more information.
95
135
  end
96
136
 
97
137
  def check_registered_kernel
98
- if kernel = registered_iruby_path
138
+ if (kernel = registered_iruby_path)
99
139
  STDERR.puts "#{@iruby_path} differs from registered path #{registered_iruby_path}.
100
140
  This might not work. Run 'iruby register --force' to fix it." if @iruby_path != kernel
101
141
  else
@@ -113,7 +153,12 @@ Add `gem 'iruby'` to your Gemfile to fix it.} unless Bundler.definition.specs.an
113
153
  yield(e)
114
154
  end
115
155
 
116
- def register_kernel
156
+ def register_kernel(force_p=false)
157
+ if force_p
158
+ unregister_kernel_in_ipython_dir
159
+ else
160
+ return unless check_existing_kernel_in_ipython_dir
161
+ end
117
162
  FileUtils.mkpath(@kernel_dir)
118
163
  unless RUBY_PLATFORM =~ /mswin(?!ce)|mingw|cygwin/
119
164
  File.write(@kernel_file, MultiJson.dump(argv: [ @iruby_path, 'kernel', '{connection_file}' ],
@@ -127,6 +172,13 @@ Add `gem 'iruby'` to your Gemfile to fix it.} unless Bundler.definition.specs.an
127
172
  FileUtils.copy(Dir[File.join(__dir__, 'assets', '*')], @kernel_dir) rescue nil
128
173
  end
129
174
 
175
+ def check_existing_kernel_in_ipython_dir
176
+ return true unless File.file?(File.join(ipython_kernel_dir, 'kernel.json'))
177
+ warn "IRuby kernel file already exists in the deprecated IPython's data directory."
178
+ warn "Using --force, you can replace the old kernel file with the new one in Jupyter's data directory."
179
+ false
180
+ end
181
+
130
182
  def registered_iruby_path
131
183
  File.exist?(@kernel_file) && MultiJson.load(File.read(@kernel_file))['argv'].first
132
184
  end
@@ -134,5 +186,9 @@ Add `gem 'iruby'` to your Gemfile to fix it.} unless Bundler.definition.specs.an
134
186
  def unregister_kernel
135
187
  FileUtils.rm_rf(@kernel_dir)
136
188
  end
189
+
190
+ def unregister_kernel_in_ipython_dir
191
+ FileUtils.rm_rf(ipython_kernel_dir)
192
+ end
137
193
  end
138
194
  end
@@ -0,0 +1,299 @@
1
+
2
+ # IRuby Input
3
+
4
+ This README is generated from README.ipynb. Please do not edit this file directly.
5
+
6
+ The `IRuby::Input` class makes it easier for IRuby users to get input from users. For example:
7
+
8
+
9
+ ```ruby
10
+ name = IRuby.input 'Enter your name'
11
+ ```
12
+
13
+ The following input methods are supported on the `IRuby` module:
14
+
15
+ | method | description |
16
+ | -------- | -------- |
17
+ | `input(prompt)` | Prompts the user for a line of input |
18
+ | `password(prompt)` | Prompts the user for a password |
19
+ | `form(&block)` | Presents a form to the user |
20
+ | `popup(title,&block)` | Displays a form to the user as a popup |
21
+
22
+ ## Forms
23
+
24
+ Forms are groups of inputs to be collected from the user. For example:
25
+
26
+
27
+ ```ruby
28
+ result = IRuby.form do
29
+ input :username
30
+ password :password
31
+ button
32
+ end
33
+ ```
34
+
35
+ The following methods are available to build forms:
36
+
37
+ | method | description |
38
+ | -------- | -------- |
39
+ | `input(key=:input)` | Prompts the user for a line of input |
40
+ | `textarea(key=:textarea),` | Adds a textarea to the form |
41
+ | `password(key=:password)` | Prompts the user for a password |
42
+ | `button(key=:done, color: :blue)` | Adds a submit button to the form |
43
+ | `cancel(prompt='Cancel')` | Adds a cancel button to the form |
44
+ | `text(string)` | Adds text to the form |
45
+ | `html(&block)` | Inserts HTML from the given [erector block](https://github.com/erector/erector) |
46
+ | `file(key=:file)` | Adds a file input to the form |
47
+ | `date(key=:date)` | Adds a date picker to the form |
48
+ | `select(*options)` | Adds a dropdown select input to the form |
49
+ | `radio(*options)` | Adds a radio select input to the form |
50
+ | `checkbox(*options)` | Adds checkbox inputs to the form |
51
+
52
+ ## Popups
53
+
54
+ Popups are just forms in a bootstrap modal. They are useful when users **Run All** in a notebook with a lot of inputs. The popups always appear in the same spot, so users don't have to scroll down to find the next input.
55
+
56
+ Popups accept a `title` argument, for example:
57
+
58
+
59
+ ```ruby
60
+ result = IRuby.popup 'Please enter your name' do
61
+ input
62
+ button
63
+ end
64
+ ```
65
+
66
+ ## Submit and cancel
67
+
68
+ The enter key will submit an input or form and the escape key will cancel it. Canceled inputs are returned as `nil`. Inputs are automatically canceled if destroyed. An input can be destroyed by clearing its cell's output. The `cancel` button will cancel a form and all other buttons will submit it.
69
+
70
+ After a form destroyed, the cell's output is cleared. Be careful not to prompt for input in a block that has previous output you would like to keep. Output is cleared to prevent forms from interferring with one another and to ensure that inputs are not inadvertently saved to the notebook.
71
+
72
+
73
+ ```ruby
74
+ result = IRuby.popup 'Confirm' do
75
+ text 'Are you sure you want to continue?'
76
+ cancel 'No'
77
+ button 'Yes'
78
+ end
79
+ ```
80
+
81
+ ## Custom keys
82
+
83
+ Every widget has an entry in the final results hash. A custom key can be passed as the first parameter to the hash. If no key is provided, the widget name is used as the key. The `cancel` widget has no key; it's first parameter is its label.
84
+
85
+
86
+ ```ruby
87
+ result = IRuby.form do
88
+ input :username
89
+ password :password
90
+ end
91
+ ```
92
+
93
+ ## Custom labels
94
+
95
+ Field labels appear to the left of the field. Button labels appear as the text on the button. `cancel` labels are passed as the first argument. All other widgets' labels are set using the `label` parameter.
96
+
97
+
98
+ ```ruby
99
+ result = IRuby.form do
100
+ input :name, label: 'Please enter your name'
101
+ cancel 'None of your business!'
102
+ button :submit, label: 'All done'
103
+ end
104
+ ```
105
+
106
+ ## Defaults
107
+
108
+ Most inputs will accept a `default` parameter. If no default is given, the deault is `nil`. Since checkboxes can have multiple values selected, you can pass an array of values. To check everything, pass `true` as the default.
109
+
110
+
111
+ ```ruby
112
+ result = IRuby.form do
113
+ checkbox :one, 'Fish', 'Cat', 'Dog', default: 'Fish'
114
+ checkbox :many, 'Fish', 'Cat', 'Dog', default: ['Cat', 'Dog']
115
+ checkbox :all, 'Fish', 'Cat', 'Dog', default: true
116
+ button :submit, label: 'All done'
117
+ end
118
+ ```
119
+
120
+ ## Dates
121
+
122
+ The `date` widget provides a calendar popup and returns a `Time` object. It's default should also be a `Time` object.
123
+
124
+
125
+ ```ruby
126
+ result = IRuby.form do
127
+ date :birthday
128
+ date :today, default: Time.now
129
+ button
130
+ end
131
+ ```
132
+
133
+ ## Buttons
134
+
135
+ Buttons do not appear in the final hash unless they are clicked. If clicked, their value is `true`. Here are the various colors a button can be:
136
+
137
+
138
+ ```ruby
139
+ result = IRuby.form do
140
+ IRuby::Input::Button::COLORS.each_key do |color|
141
+ button color, color: color
142
+ end
143
+ end
144
+ ```
145
+
146
+ ## Textareas
147
+
148
+ Textareas are multiline inputs that are convenient for larger inputs. If you need a line return when typing in a textarea, use shift+enter since enter will submit the form.
149
+
150
+
151
+ ```ruby
152
+ result = IRuby.form do
153
+ text 'Enter email addresses, one per line (use shift+enter for newlines)'
154
+ textarea :emails
155
+ end
156
+ ```
157
+
158
+ ## Text and HTML
159
+
160
+ You can insert lines of text or custom html using their respective helpers:
161
+
162
+
163
+ ```ruby
164
+ result = IRuby.form do
165
+ html { h1 'Choose a Stooge' }
166
+ text 'Choose your favorite stooge'
167
+ select :stooge, 'Moe', 'Larry', 'Curly'
168
+ button
169
+ end
170
+ ```
171
+
172
+ ## Dropdowns
173
+
174
+ A `select` is a dropdown of options. Use a `multiple` to allow multiple selections. `multiple` widgets accept an additional `size` parameters that determines the number of rows. The default is 4.
175
+
176
+
177
+ ```ruby
178
+ result = IRuby.form do
179
+ select :stooge, 'Moe', 'Larry', 'Curly'
180
+ select :stooge, 'Moe', 'Larry', 'Curly', default: 'Moe'
181
+ multiple :stooges, 'Moe', 'Larry', 'Curly', default: true, size: 3
182
+ multiple :stooges, 'Moe', 'Larry', 'Curly', default: ['Moe','Curly']
183
+ button
184
+ end
185
+ ```
186
+
187
+ ## Radio selects and checkboxes
188
+
189
+ Like selects, radio selects and checkboxes take multiple arguments, each one an option. If the first argument is a symbol, it is used as the key.
190
+
191
+ Note that the `checkbox` widget will always return `nil` or an array.
192
+
193
+
194
+ ```ruby
195
+ result = IRuby.form do
196
+ radio :children, *(0..12), label: 'How many children do you have?'
197
+ checkbox :gender, 'Male', 'Female', 'Intersex', label: 'Select the genders of your children'
198
+ button
199
+ end
200
+ ```
201
+
202
+ ## Files
203
+
204
+ Since file widgets capture the enter key, you should include a button when creating forms that contain only a file input:
205
+
206
+
207
+ ```ruby
208
+ IRuby.form do
209
+ file :avatar, label: 'Choose an Avatar'
210
+ button :submit
211
+ end
212
+ ```
213
+
214
+ File widgets return a hash with three keys:
215
+
216
+ * `data`: The contents of the file as a string
217
+ * `content_type`: The type of file, such as `text/plain` or `image/jpeg`
218
+ * `name`: The name of the uploaded file
219
+
220
+ ## Example
221
+
222
+ Here is an example form that uses every built-in widget.
223
+
224
+
225
+ ```ruby
226
+ result = IRuby.form do
227
+ html { h1 'The Everything Form' }
228
+ text 'Marvel at the strange and varied inputs!'
229
+ date
230
+ file
231
+ input :username
232
+ password
233
+ textarea
234
+ radio *(1..10)
235
+ checkbox 'Fish', 'Cat', 'Dog', label: 'Animals'
236
+ select :color, *IRuby::Input::Button::COLORS.keys
237
+ cancel
238
+ button
239
+ end
240
+ ```
241
+
242
+ ## Writing your own widget
243
+
244
+ Most form methods are `IRuby::Input::Widget` instances. A `Widget` is an [`Erector::Widget`](https://github.com/erector/erector) with some additional helpers. Here is the `cancel` widget:
245
+
246
+ ```ruby
247
+ module IRuby
248
+ module Input
249
+ class Cancel < Widget
250
+ needs :label
251
+
252
+ builder :cancel do |label='Cancel'|
253
+ add_button Cancel.new(label: label)
254
+ end
255
+
256
+ def widget_css
257
+ ".iruby-cancel { margin-left: 5px; }"
258
+ end
259
+
260
+ def widget_js
261
+ <<-JS
262
+ $('.iruby-cancel').click(function(){
263
+ $('#iruby-form').remove();
264
+ });
265
+ JS
266
+ end
267
+
268
+ def widget_html
269
+ button(
270
+ @label,
271
+ type: 'button',
272
+ :'data-dismiss' => 'modal',
273
+ class: "btn btn-danger pull-right iruby-cancel"
274
+ )
275
+ end
276
+ end
277
+ end
278
+ end
279
+ ```
280
+
281
+ The following methods are available for widgets to use or override:
282
+
283
+ | method | description |
284
+ | -------- | -------- |
285
+ | `widget_js` | Returns the widget's Javascript |
286
+ | `widget_css` | Returns the widget's CSS |
287
+ | `widget_html` | Returns the widget's |
288
+ | `builder(method,&block)` | Class method to add form building helpers. |
289
+
290
+ The following methods are available in the `builder` block:
291
+
292
+ | method | description |
293
+ | -------- | -------- |
294
+ | `add_field(field)` | Adds a widget to the form's field area |
295
+ | `add_button(button)` | Adds a button to the form's button area |
296
+ | `process(key,&block)` | Register a custom processing block for the given key in the results hash |
297
+ | `unique_key(key)` | Returns a unique key for the given key. Use this to make sure that there are no key collisions in the final results hash. |
298
+
299
+ A canceled form always returns `nil`. Otherwise, the form collects any element with a `data-iruby-key` and non-falsey `data-iruby-value` and passes those to the processor proc registered for the key. See the `File` widget for a more involved example of processing results.