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/lib/iruby/display.rb CHANGED
@@ -1,17 +1,24 @@
1
+ require "set"
2
+
1
3
  module IRuby
2
4
  module Display
3
5
  class << self
6
+ # @private
4
7
  def convert(obj, options)
5
8
  Representation.new(obj, options)
6
9
  end
7
10
 
11
+ # @private
8
12
  def display(obj, options = {})
9
13
  obj = convert(obj, options)
10
14
  options = obj.options
11
15
  obj = obj.object
12
16
 
13
17
  fuzzy_mime = options[:format] # Treated like a fuzzy mime type
14
- raise 'Invalid argument :format' unless !fuzzy_mime || String === fuzzy_mime
18
+ unless !fuzzy_mime || String === fuzzy_mime
19
+ raise 'Invalid argument :format'
20
+ end
21
+
15
22
  if exact_mime = options[:mime]
16
23
  raise 'Invalid argument :mime' unless String === exact_mime
17
24
  raise 'Invalid mime type' unless exact_mime.include?('/')
@@ -27,32 +34,54 @@ module IRuby
27
34
 
28
35
  # As a last resort, interpret string representation of the object
29
36
  # as the given mime type.
30
- data[exact_mime] = protect(exact_mime, obj) if exact_mime && !data.any? {|m,_| exact_mime == m }
37
+ if exact_mime && data.none? { |m, _| exact_mime == m }
38
+ data[exact_mime] = protect(exact_mime, obj)
39
+ end
31
40
 
32
41
  data
33
42
  end
34
43
 
35
- def clear_output(wait=false)
36
- IRuby::Kernel.instance.session.send(:publish, :clear_output, {wait: wait})
44
+ # @private
45
+ def clear_output(wait = false)
46
+ IRuby::Kernel.instance.session.send(:publish, :clear_output, wait: wait)
37
47
  end
38
48
 
39
49
  private
40
50
 
41
51
  def protect(mime, data)
42
- MimeMagic.new(mime).text? ? data.to_s : [data.to_s].pack('m0')
52
+ ascii?(mime) ? data.to_s : [data.to_s].pack('m0')
53
+ end
54
+
55
+ # Each of the following mime types must be a text type,
56
+ # but mime-types library tells us it is a non-text type.
57
+ FORCE_TEXT_TYPES = Set[
58
+ "application/javascript",
59
+ "image/svg+xml"
60
+ ].freeze
61
+
62
+ def ascii?(mime)
63
+ if FORCE_TEXT_TYPES.include?(mime)
64
+ true
65
+ else
66
+ MIME::Type.new(mime).ascii?
67
+ end
43
68
  end
44
69
 
45
70
  def render(data, obj, exact_mime, fuzzy_mime)
46
71
  # Filter matching renderer by object type
47
- renderer = Registry.renderer.select {|r| r.match?(obj) }
72
+ renderer = Registry.renderer.select { |r| r.match?(obj) }
48
73
 
49
74
  matching_renderer = nil
50
75
 
51
76
  # Find exactly matching display by exact_mime
52
- matching_renderer = renderer.find {|r| exact_mime == r.mime } if exact_mime
77
+ if exact_mime
78
+ matching_renderer = renderer.find { |r| exact_mime == r.mime }
79
+ end
53
80
 
54
81
  # Find fuzzy matching display by fuzzy_mime
55
- matching_renderer ||= renderer.find {|r| r.mime && r.mime.include?(fuzzy_mime) } if fuzzy_mime
82
+ if fuzzy_mime
83
+ matching_renderer ||= renderer.find { |r| r.mime&.include?(fuzzy_mime) }
84
+ end
56
85
 
57
86
  renderer.unshift matching_renderer if matching_renderer
58
87
 
@@ -73,7 +102,8 @@ module IRuby
73
102
  attr_reader :object, :options
74
103
 
75
104
  def initialize(object, options)
76
- @object, @options = object, options
105
+ @object = object
106
+ @options = options
77
107
  end
78
108
 
79
109
  class << self
@@ -91,10 +121,13 @@ module IRuby
91
121
  end
92
122
 
93
123
  class Renderer
94
- attr_reader :match, :mime, :render, :priority
124
+ attr_reader :match, :mime, :priority
95
125
 
96
126
  def initialize(match, mime, render, priority)
97
- @match, @mime, @render, @priority = match, mime, render, priority
127
+ @match = match
128
+ @mime = mime
129
+ @render = render
130
+ @priority = priority
98
131
  end
99
132
 
100
133
  def match?(obj)
@@ -114,7 +147,7 @@ module IRuby
114
147
  @renderer ||= []
115
148
  end
116
149
 
117
- SUPPORTED_MIMES = %w(
150
+ SUPPORTED_MIMES = %w[
118
151
  text/plain
119
152
  text/html
120
153
  text/latex
@@ -122,7 +155,8 @@ module IRuby
122
155
  application/javascript
123
156
  image/png
124
157
  image/jpeg
125
- image/svg+xml)
158
+ image/svg+xml
159
+ ]
126
160
 
127
161
  def match(&block)
128
162
  @match = block
@@ -131,7 +165,7 @@ module IRuby
131
165
  end
132
166
 
133
167
  def respond_to(name)
134
- match {|obj| obj.respond_to?(name) }
168
+ match { |obj| obj.respond_to?(name) }
135
169
  end
136
170
 
137
171
  def type(&block)
@@ -153,7 +187,7 @@ module IRuby
153
187
 
154
188
  def format(mime = nil, &block)
155
189
  renderer << Renderer.new(@match, mime, block, @priority)
156
- renderer.sort_by! {|r| -r.priority }
190
+ renderer.sort_by! { |r| -r.priority }
157
191
 
158
192
  # Decrease priority implicitly for all formats
159
193
  # which are added later for a type.
@@ -170,6 +204,7 @@ module IRuby
170
204
  end
171
205
 
172
206
  type { Numo::NArray }
207
+ format 'text/plain', &:inspect
173
208
  format 'text/latex' do |obj|
174
209
  obj.ndim == 2 ?
175
210
  LaTeX.matrix(obj, obj.shape[0], obj.shape[1]) :
@@ -180,6 +215,7 @@ module IRuby
180
215
  end
181
216
 
182
217
  type { NArray }
218
+ format 'text/plain', &:inspect
183
219
  format 'text/latex' do |obj|
184
220
  obj.dim == 2 ?
185
221
  LaTeX.matrix(obj.transpose(1, 0), obj.shape[1], obj.shape[0]) :
@@ -241,37 +277,41 @@ module IRuby
241
277
 
242
278
  match do |obj|
243
279
  defined?(Magick::Image) && Magick::Image === obj ||
244
- defined?(MiniMagick::Image) && MiniMagick::Image === obj
280
+ defined?(MiniMagick::Image) && MiniMagick::Image === obj
245
281
  end
246
282
  format 'image' do |obj|
247
283
  format = obj.format || 'PNG'
248
- [format == 'PNG' ? 'image/png' : 'image/jpeg', obj.to_blob {|i| i.format = format }]
284
+ [format == 'PNG' ? 'image/png' : 'image/jpeg', obj.to_blob { |i| i.format = format }]
249
285
  end
250
286
 
251
- type { Gruff::Base }
252
- format 'image/png' do |obj|
253
- obj.to_blob
287
+ match do |obj|
288
+ defined?(Vips::Image) && Vips::Image === obj
254
289
  end
290
+ format do |obj|
291
+ # handles Vips::Error, vips_image_get: field "vips-loader" not found
292
+ loader = obj.get('vips-loader') rescue nil
293
+ if loader == 'jpegload'
294
+ ['image/jpeg', obj.write_to_buffer('.jpg')]
295
+ else
296
+ # falls back to png for other/unknown types
297
+ ['image/png', obj.write_to_buffer('.png')]
298
+ end
299
+ end
300
+
301
+ type { Gruff::Base }
302
+ format 'image/png', &:to_blob
255
303
 
256
304
  respond_to :to_html
257
- format 'text/html' do |obj|
258
- obj.to_html
259
- end
305
+ format 'text/html', &:to_html
260
306
 
261
307
  respond_to :to_latex
262
- format 'text/latex' do |obj|
263
- obj.to_latex
264
- end
308
+ format 'text/latex', &:to_latex
265
309
 
266
310
  respond_to :to_tex
267
- format 'text/latex' do |obj|
268
- obj.to_tex
269
- end
311
+ format 'text/latex', &:to_tex
270
312
 
271
313
  respond_to :to_javascript
272
- format 'text/javascript' do |obj|
273
- obj.to_javascript
274
- end
314
+ format 'text/javascript', &:to_javascript
275
315
 
276
316
  respond_to :to_svg
277
317
  format 'image/svg+xml' do |obj|
@@ -280,21 +320,17 @@ module IRuby
280
320
  end
281
321
 
282
322
  respond_to :to_iruby
283
- format do |obj|
284
- obj.to_iruby
285
- end
323
+ format(&:to_iruby)
286
324
 
287
- match {|obj| obj.respond_to?(:path) && File.readable?(obj.path) }
325
+ match { |obj| obj.respond_to?(:path) && obj.method(:path).arity == 0 && File.readable?(obj.path) }
288
326
  format do |obj|
289
- mime = MimeMagic.by_path(obj.path).to_s
327
+ mime = MIME::Types.of(obj.path).first.to_s
290
328
  [mime, File.read(obj.path)] if SUPPORTED_MIMES.include?(mime)
291
329
  end
292
330
 
293
331
  type { Object }
294
- priority -1000
295
- format 'text/plain' do |obj|
296
- obj.inspect
297
- end
332
+ priority(-1000)
333
+ format 'text/plain', &:inspect
298
334
  end
299
335
  end
300
336
  end
@@ -0,0 +1,40 @@
1
+ module IRuby
2
+ class EventManager
3
+ def initialize(available_events)
4
+ @available_events = available_events.dup.freeze
5
+ @callbacks = available_events.map {|n| [n, []] }.to_h
6
+ end
7
+
8
+ attr_reader :available_events
9
+
10
+ def register(event, &block)
11
+ check_available_event(event)
12
+ @callbacks[event] << block unless block.nil?
13
+ block
14
+ end
15
+
16
+ def unregister(event, callback)
17
+ check_available_event(event)
18
+ val = @callbacks[event].delete(callback)
19
+ unless val
20
+ raise ArgumentError,
21
+ "Given callable object #{callback} is not registered as a #{event} callback"
22
+ end
23
+ val
24
+ end
25
+
26
+ def trigger(event, *args, **kwargs)
27
+ check_available_event(event)
28
+ @callbacks[event].each do |fn|
29
+ fn.call(*args, **kwargs)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def check_available_event(event)
36
+ return if @callbacks.key?(event)
37
+ raise ArgumentError, "Unknown event name: #{event}", caller
38
+ end
39
+ end
40
+ end
@@ -78,16 +78,16 @@ module IRuby
78
78
 
79
79
  if maxcols && keys.size > maxcols
80
80
  keys1 = keys[0...maxcols / 2]
81
- keys2 = keys[-maxcols / 2...-1]
81
+ keys2 = keys[-maxcols / 2 + 1..-1]
82
82
  if header
83
83
  header1 = header[0...maxcols / 2]
84
- header2 = header[-maxcols / 2...-1]
84
+ header2 = header[-maxcols / 2 + 1..-1]
85
85
  end
86
86
  end
87
87
 
88
88
  if maxrows && rows.size > maxrows
89
89
  rows1 = rows[0...maxrows / 2]
90
- rows2 = rows[-maxrows / 2...-1]
90
+ rows2 = rows[-maxrows / 2 + 1..-1]
91
91
  end
92
92
 
93
93
  table = '<table>'
data/lib/iruby/input.rb CHANGED
@@ -15,21 +15,21 @@ module IRuby
15
15
  end
16
16
 
17
17
  def form &block
18
- builder = Builder.new &block
18
+ builder = Builder.new(&block)
19
19
  form = InputForm.new(
20
- fields: builder.fields,
20
+ fields: builder.fields,
21
21
  buttons: builder.buttons
22
22
  )
23
23
  form.widget_display
24
24
  builder.process_result form.submit
25
25
  end
26
-
26
+
27
27
  def popup title='Input', &block
28
- builder = Builder.new &block
28
+ builder = Builder.new(&block)
29
29
  form = InputForm.new fields: builder.fields
30
30
  popup = Popup.new(
31
- title: title,
32
- form: form,
31
+ title: title,
32
+ form: form,
33
33
  buttons: builder.buttons
34
34
  )
35
35
  popup.widget_display
@@ -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.