glimmer-dsl-swt 4.22.2.1 → 4.22.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +26 -12
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_COMMAND.md +3 -1
  6. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +145 -0
  7. data/docs/reference/GLIMMER_SAMPLES.md +17 -9
  8. data/glimmer-dsl-swt.gemspec +0 -0
  9. data/lib/glimmer/dsl/swt/exec_expression.rb +1 -2
  10. data/lib/glimmer/launcher.rb +7 -4
  11. data/lib/glimmer/rake_task/scaffold.rb +28 -15
  12. data/lib/glimmer/swt/image_proxy.rb +1 -0
  13. data/lib/glimmer/swt/layout_data_proxy.rb +4 -0
  14. data/samples/elaborate/calculator.rb +12 -12
  15. data/samples/elaborate/contact_manager.rb +26 -14
  16. data/samples/elaborate/game_of_life.rb +8 -8
  17. data/samples/elaborate/klondike_solitaire/view/action_panel.rb +2 -2
  18. data/samples/elaborate/klondike_solitaire/view/klondike_solitaire_menu_bar.rb +15 -10
  19. data/samples/elaborate/login.rb +16 -10
  20. data/samples/elaborate/mandelbrot_fractal.rb +26 -20
  21. data/samples/elaborate/meta_sample.rb +6 -6
  22. data/samples/elaborate/metronome.rb +4 -4
  23. data/samples/elaborate/snake/model/apple.rb +33 -0
  24. data/samples/elaborate/snake/model/game.rb +68 -0
  25. data/samples/elaborate/snake/model/snake.rb +95 -0
  26. data/samples/elaborate/snake/model/vertebra.rb +22 -0
  27. data/samples/elaborate/snake/presenter/cell.rb +27 -0
  28. data/samples/elaborate/snake/presenter/grid.rb +49 -0
  29. data/samples/elaborate/snake.rb +103 -0
  30. data/samples/elaborate/stock_ticker.rb +12 -12
  31. data/samples/elaborate/tetris.rb +10 -10
  32. data/samples/elaborate/timer.rb +36 -26
  33. data/samples/elaborate/user_profile.rb +53 -14
  34. data/samples/elaborate/weather.rb +2 -2
  35. data/samples/hello/hello_button.rb +3 -3
  36. data/samples/hello/hello_canvas_animation.rb +2 -2
  37. data/samples/hello/hello_color_dialog.rb +4 -4
  38. data/samples/hello/hello_custom_shell.rb +2 -2
  39. data/samples/hello/hello_dialog.rb +4 -4
  40. data/samples/hello/hello_directory_dialog.rb +2 -2
  41. data/samples/hello/hello_expand_bar.rb +4 -5
  42. data/samples/hello/hello_file_dialog.rb +2 -2
  43. data/samples/hello/hello_font_dialog.rb +2 -2
  44. data/samples/hello/hello_link.rb +5 -4
  45. data/samples/hello/hello_list_multi_selection.rb +3 -1
  46. data/samples/hello/hello_list_single_selection.rb +3 -1
  47. data/samples/hello/hello_menu_bar.rb +28 -28
  48. data/samples/hello/hello_message_box.rb +2 -2
  49. data/samples/hello/hello_pop_up_context_menu.rb +18 -8
  50. data/samples/hello/hello_shell.rb +16 -16
  51. data/samples/hello/hello_styled_text.rb +2 -2
  52. data/samples/hello/hello_table.rb +4 -4
  53. data/samples/hello/hello_tray_item.rb +2 -2
  54. metadata +9 -2
@@ -40,6 +40,7 @@ class ContactManager
40
40
  margin_width 0
41
41
  margin_height 0
42
42
  }
43
+
43
44
  layout_data :fill, :center, true, false
44
45
  text 'Lookup Contacts'
45
46
  font height: 24
@@ -52,9 +53,10 @@ class ContactManager
52
53
  text {
53
54
  layout_data :fill, :center, true, false
54
55
  text <=> [@contact_manager_presenter, :first_name]
55
- on_key_pressed {|key_event|
56
+
57
+ on_key_pressed do |key_event|
56
58
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
57
- }
59
+ end
58
60
  }
59
61
 
60
62
  label {
@@ -65,9 +67,10 @@ class ContactManager
65
67
  text {
66
68
  layout_data :fill, :center, true, false
67
69
  text <=> [@contact_manager_presenter, :last_name]
68
- on_key_pressed {|key_event|
70
+
71
+ on_key_pressed do |key_event|
69
72
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
70
- }
73
+ end
71
74
  }
72
75
 
73
76
  label {
@@ -78,9 +81,10 @@ class ContactManager
78
81
  text {
79
82
  layout_data :fill, :center, true, false
80
83
  text <=> [@contact_manager_presenter, :email]
81
- on_key_pressed {|key_event|
84
+
85
+ on_key_pressed do |key_event|
82
86
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
83
- }
87
+ end
84
88
  }
85
89
 
86
90
  composite {
@@ -94,18 +98,26 @@ class ContactManager
94
98
 
95
99
  button {
96
100
  text "&Find"
97
- on_widget_selected { @contact_manager_presenter.find }
98
- on_key_pressed {|key_event|
101
+
102
+ on_widget_selected do
103
+ @contact_manager_presenter.find
104
+ end
105
+
106
+ on_key_pressed do |key_event|
99
107
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
100
- }
108
+ end
101
109
  }
102
110
 
103
111
  button {
104
112
  text "&List All"
105
- on_widget_selected { @contact_manager_presenter.list }
106
- on_key_pressed {|key_event|
113
+
114
+ on_widget_selected do
115
+ @contact_manager_presenter.list
116
+ end
117
+
118
+ on_key_pressed do |key_event|
107
119
  @contact_manager_presenter.list if key_event.keyCode == swt(:cr)
108
- }
120
+ end
109
121
  }
110
122
  }
111
123
  }
@@ -134,9 +146,9 @@ class ContactManager
134
146
 
135
147
  items <=> [@contact_manager_presenter, :results, column_properties: [:first_name, :last_name, :email]]
136
148
 
137
- on_mouse_up { |event|
149
+ on_mouse_up do |event|
138
150
  table_proxy.edit_table_item(event.table_item, event.column_index)
139
- }
151
+ end
140
152
  }
141
153
  }
142
154
  }
@@ -50,9 +50,9 @@ class GameOfLife
50
50
  rectangle(column_index*CELL_WIDTH, row_index*CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT) {
51
51
  background <= [@grid.cell_rows[row_index][column_index], "alive", on_read: ->(a) {a ? :black : :white}]
52
52
 
53
- on_mouse_down {
53
+ on_mouse_down do
54
54
  @grid.cell_rows[row_index][column_index].toggle_aliveness!
55
- }
55
+ end
56
56
  }
57
57
  end
58
58
  end
@@ -67,26 +67,26 @@ class GameOfLife
67
67
  text 'Step'
68
68
  enabled <= [@grid, :playing, on_read: :! ]
69
69
 
70
- on_widget_selected {
70
+ on_widget_selected do
71
71
  @grid.step!
72
- }
72
+ end
73
73
  }
74
74
 
75
75
  button {
76
76
  text 'Clear'
77
77
  enabled <= [@grid, :playing, on_read: :! ]
78
78
 
79
- on_widget_selected {
79
+ on_widget_selected do
80
80
  @grid.clear!
81
- }
81
+ end
82
82
  }
83
83
 
84
84
  button {
85
85
  text <= [@grid, :playing, on_read: ->(p) { p ? 'Stop' : 'Play' }]
86
86
 
87
- on_widget_selected {
87
+ on_widget_selected do
88
88
  @grid.toggle_playback!
89
- }
89
+ end
90
90
  }
91
91
 
92
92
  label {
@@ -19,9 +19,9 @@ class KlondikeSolitaire
19
19
 
20
20
  text 'Restart Game'
21
21
 
22
- on_widget_selected {
22
+ on_widget_selected do
23
23
  game.restart!
24
- }
24
+ end
25
25
  }
26
26
  }
27
27
  }
@@ -7,12 +7,13 @@ class KlondikeSolitaire
7
7
 
8
8
  before_body do
9
9
  @display = display {
10
- on_about {
10
+ on_about do
11
11
  display_about_dialog
12
- }
13
- on_preferences {
12
+ end
13
+
14
+ on_preferences do
14
15
  display_about_dialog
15
- }
16
+ end
16
17
  }
17
18
  end
18
19
 
@@ -20,30 +21,34 @@ class KlondikeSolitaire
20
21
  menu_bar {
21
22
  menu {
22
23
  text '&Game'
24
+
23
25
  menu_item {
24
26
  text '&Restart'
25
27
  accelerator (OS.mac? ? :command : :ctrl), :r
26
28
 
27
- on_widget_selected {
29
+ on_widget_selected do
28
30
  game.restart!
29
- }
31
+ end
30
32
  }
33
+
31
34
  menu_item {
32
35
  text 'E&xit'
33
36
  accelerator :alt, :f4
34
37
 
35
- on_widget_selected {
38
+ on_widget_selected do
36
39
  exit(0)
37
- }
40
+ end
38
41
  }
39
42
  }
40
43
  menu {
41
44
  text '&Help'
45
+
42
46
  menu_item {
43
47
  text '&About...'
44
- on_widget_selected {
48
+
49
+ on_widget_selected do
45
50
  display_about_dialog
46
- }
51
+ end
47
52
  }
48
53
  }
49
54
  }
@@ -81,9 +81,9 @@ class Login
81
81
  text <=> [@presenter, :user_name]
82
82
  enabled <= [@presenter, :logged_out?, computed_by: :status]
83
83
 
84
- on_key_pressed { |event|
84
+ on_key_pressed do |event|
85
85
  @password_text.set_focus if event.keyCode == swt(:cr)
86
- }
86
+ end
87
87
  }
88
88
 
89
89
  label { text "Password:" }
@@ -91,9 +91,9 @@ class Login
91
91
  text <=> [@presenter, :password]
92
92
  enabled <= [@presenter, :logged_out?, computed_by: :status]
93
93
 
94
- on_key_pressed { |event|
94
+ on_key_pressed do |event|
95
95
  @presenter.login! if event.keyCode == swt(:cr)
96
- }
96
+ end
97
97
  }
98
98
 
99
99
  label { text "Status:" }
@@ -103,25 +103,31 @@ class Login
103
103
  text "Login"
104
104
  enabled <= [@presenter, :logged_out?, computed_by: :status]
105
105
 
106
- on_widget_selected { @presenter.login! }
107
- on_key_pressed { |event|
106
+ on_widget_selected do
107
+ @presenter.login!
108
+ end
109
+
110
+ on_key_pressed do |event|
108
111
  if event.keyCode == swt(:cr)
109
112
  @presenter.login!
110
113
  end
111
- }
114
+ end
112
115
  }
113
116
 
114
117
  button {
115
118
  text "Logout"
116
119
  enabled <= [@presenter, :logged_in?, computed_by: :status]
117
120
 
118
- on_widget_selected { @presenter.logout! }
119
- on_key_pressed { |event|
121
+ on_widget_selected do
122
+ @presenter.logout!
123
+ end
124
+
125
+ on_key_pressed do |event|
120
126
  if event.keyCode == swt(:cr)
121
127
  @presenter.logout!
122
128
  @user_name_text.set_focus
123
129
  end
124
- }
130
+ end
125
131
  }
126
132
  }
127
133
  }
@@ -186,10 +186,10 @@ class MandelbrotFractal
186
186
  minimum_size mandelbrot.width + 29, mandelbrot.height + 77
187
187
  image @mandelbrot_image
188
188
 
189
- on_shell_closed {
189
+ on_shell_closed do
190
190
  @thread.kill # should not be dangerous in this case
191
191
  puts "Mandelbrot background calculation stopped!"
192
- }
192
+ end
193
193
 
194
194
  progress_bar {
195
195
  layout_data :fill, :center, true, false
@@ -205,27 +205,27 @@ class MandelbrotFractal
205
205
  image @mandelbrot_image
206
206
  cursor :no
207
207
 
208
- on_mouse_down {
208
+ on_mouse_down do
209
209
  @drag_detected = false
210
210
  @canvas.cursor = :hand
211
- }
211
+ end
212
212
 
213
- on_drag_detected { |drag_detect_event|
213
+ on_drag_detected do |drag_detect_event|
214
214
  @drag_detected = true
215
215
  @drag_start_x = drag_detect_event.x
216
216
  @drag_start_y = drag_detect_event.y
217
- }
217
+ end
218
218
 
219
- on_mouse_move { |mouse_event|
219
+ on_mouse_move do |mouse_event|
220
220
  if @drag_detected
221
221
  origin = @scrolled_composite.origin
222
222
  new_x = origin.x - (mouse_event.x - @drag_start_x)
223
223
  new_y = origin.y - (mouse_event.y - @drag_start_y)
224
224
  @scrolled_composite.set_origin(new_x, new_y)
225
225
  end
226
- }
226
+ end
227
227
 
228
- on_mouse_up { |mouse_event|
228
+ on_mouse_up do |mouse_event|
229
229
  if !@drag_detected
230
230
  origin = @scrolled_composite.origin
231
231
  @location_x = mouse_event.x
@@ -238,7 +238,7 @@ class MandelbrotFractal
238
238
  end
239
239
  @canvas.cursor = can_zoom_in? ? :cross : :no
240
240
  @drag_detected = false
241
- }
241
+ end
242
242
 
243
243
  }
244
244
  }
@@ -251,27 +251,33 @@ class MandelbrotFractal
251
251
  text 'Zoom &In'
252
252
  accelerator COMMAND, '+'
253
253
 
254
- on_widget_selected { zoom_in }
254
+ on_widget_selected do
255
+ zoom_in
256
+ end
255
257
  }
256
258
 
257
259
  menu_item {
258
260
  text 'Zoom &Out'
259
261
  accelerator COMMAND, '-'
260
262
 
261
- on_widget_selected { zoom_out }
263
+ on_widget_selected do
264
+ zoom_out
265
+ end
262
266
  }
263
267
 
264
268
  menu_item {
265
269
  text '&Reset Zoom'
266
270
  accelerator COMMAND, '0'
267
271
 
268
- on_widget_selected { perform_zoom(mandelbrot_zoom: 1.0) }
272
+ on_widget_selected do
273
+ perform_zoom(mandelbrot_zoom: 1.0)
274
+ end
269
275
  }
270
276
  }
271
277
  menu {
272
278
  text '&Cores'
273
279
 
274
- Concurrent.physical_processor_count.times {|n|
280
+ Concurrent.physical_processor_count.times do |n|
275
281
  processor_number = n + 1
276
282
  menu_item(:radio) {
277
283
  text "&#{processor_number}"
@@ -287,11 +293,11 @@ class MandelbrotFractal
287
293
 
288
294
  selection true if processor_number == Concurrent.physical_processor_count
289
295
 
290
- on_widget_selected {
296
+ on_widget_selected do
291
297
  Mandelbrot.processor_count = processor_number
292
- }
298
+ end
293
299
  }
294
- }
300
+ end
295
301
  }
296
302
  menu {
297
303
  text '&Help'
@@ -300,9 +306,9 @@ class MandelbrotFractal
300
306
  text '&Instructions'
301
307
  accelerator COMMAND, :shift, :i
302
308
 
303
- on_widget_selected {
309
+ on_widget_selected do
304
310
  display_help_instructions
305
- }
311
+ end
306
312
  }
307
313
  }
308
314
  }
@@ -352,7 +358,7 @@ class MandelbrotFractal
352
358
  def color_palette
353
359
  if @color_palette.nil?
354
360
  @color_palette = [[0, 0, 0]] + 40.times.map { |i| [255 - i*5, 255 - i*5, 55 + i*5] }
355
- @color_palette = @color_palette.map {|color_data| rgb(*color_data).swt_color}
361
+ @color_palette = @color_palette.map { |color_data| rgb(*color_data).swt_color }
356
362
  end
357
363
  @color_palette
358
364
  end
@@ -253,22 +253,22 @@ class MetaSampleApplication
253
253
  font height: 25
254
254
  enabled <= [SampleDirectory, 'selected_sample.launchable']
255
255
 
256
- on_widget_selected {
256
+ on_widget_selected do
257
257
  begin
258
258
  SampleDirectory.selected_sample.launch(@code_text.text)
259
259
  rescue LoadError, StandardError, SyntaxError => launch_error
260
260
  error_dialog(message: launch_error.full_message).open
261
261
  end
262
- }
262
+ end
263
263
  }
264
264
  button {
265
265
  text 'Reset'
266
266
  font height: 25
267
267
  enabled <= [SampleDirectory, 'selected_sample.editable']
268
268
 
269
- on_widget_selected {
269
+ on_widget_selected do
270
270
  SampleDirectory.selected_sample.reset_code!
271
- }
271
+ end
272
272
  }
273
273
  }
274
274
  }
@@ -319,9 +319,9 @@ class MetaSampleApplication
319
319
  button {
320
320
  text 'Close'
321
321
 
322
- on_widget_selected {
322
+ on_widget_selected do
323
323
  dialog_proxy.close
324
- }
324
+ end
325
325
  }
326
326
  }
327
327
  end
@@ -101,13 +101,13 @@ class Metronome
101
101
 
102
102
  @beat_container = beat_container
103
103
 
104
- on_swt_show {
104
+ on_swt_show do
105
105
  start_metronome
106
- }
106
+ end
107
107
 
108
- on_widget_disposed {
108
+ on_widget_disposed do
109
109
  stop_metronome
110
- }
110
+ end
111
111
  }
112
112
  }
113
113
 
@@ -0,0 +1,33 @@
1
+ class Snake
2
+ module Model
3
+ class Apple
4
+ attr_reader :game
5
+ attr_accessor :row, :column
6
+
7
+ def initialize(game)
8
+ @game = game
9
+ end
10
+
11
+ # generates a new location from scratch or via dependency injection of what cell is (for testing purposes)
12
+ def generate(initial_row: nil, initial_column: nil)
13
+ if initial_row && initial_column
14
+ self.row, self.column = initial_row, initial_column
15
+ else
16
+ self.row, self.column = @game.height.times.zip(@game.width.times).reject do |row, column|
17
+ @game.snake.vertebrae.map {|v| [v.row, v.column]}.include?([row, column])
18
+ end.sample
19
+ end
20
+ end
21
+
22
+ def remove
23
+ self.row = nil
24
+ self.column = nil
25
+ end
26
+
27
+ # inspect is overridden to prevent printing very long stack traces
28
+ def inspect
29
+ "#{super[0, 120]}... >"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+
3
+ require_relative 'snake'
4
+ require_relative 'apple'
5
+
6
+ class Snake
7
+ module Model
8
+ class Game
9
+ WIDTH_DEFAULT = 20
10
+ HEIGHT_DEFAULT = 20
11
+ FILE_HIGH_SCORE = File.expand_path(File.join(Dir.home, '.glimmer-snake'))
12
+
13
+ attr_reader :width, :height
14
+ attr_accessor :snake, :apple, :over, :score, :high_score, :paused
15
+ alias over? over
16
+ alias paused? paused
17
+
18
+ def initialize(width = WIDTH_DEFAULT, height = HEIGHT_DEFAULT)
19
+ @width = width
20
+ @height = height
21
+ @snake = Snake.new(self)
22
+ @apple = Apple.new(self)
23
+ FileUtils.touch(FILE_HIGH_SCORE)
24
+ @high_score = File.read(FILE_HIGH_SCORE).to_i rescue 0
25
+ end
26
+
27
+ def score=(new_score)
28
+ @score = new_score
29
+ self.high_score = @score if @score > @high_score
30
+ end
31
+
32
+ def high_score=(new_high_score)
33
+ @high_score = new_high_score
34
+ File.write(FILE_HIGH_SCORE, @high_score.to_s)
35
+ rescue => e
36
+ puts e.full_message
37
+ end
38
+
39
+ def start
40
+ self.over = false
41
+ self.score = 0
42
+ self.snake.generate
43
+ self.apple.generate
44
+ end
45
+
46
+ def pause
47
+ self.paused = true
48
+ end
49
+
50
+ def resume
51
+ self.paused = false
52
+ end
53
+
54
+ def toggle_pause
55
+ unless paused?
56
+ pause
57
+ else
58
+ resume
59
+ end
60
+ end
61
+
62
+ # inspect is overridden to prevent printing very long stack traces
63
+ def inspect
64
+ "#{super[0, 75]}... >"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,95 @@
1
+ require_relative 'vertebra'
2
+
3
+ class Snake
4
+ module Model
5
+ class Snake
6
+ SCORE_EAT_APPLE = 50
7
+ RIGHT_TURN_MAP = {
8
+ north: :east,
9
+ east: :south,
10
+ south: :west,
11
+ west: :north
12
+ }
13
+ LEFT_TURN_MAP = RIGHT_TURN_MAP.invert
14
+
15
+ attr_accessor :collided
16
+ alias collided? collided
17
+
18
+ attr_reader :game
19
+ # vertebrae and joins are ordered from tail to head
20
+ attr_accessor :vertebrae
21
+
22
+ def initialize(game)
23
+ @game = game
24
+ end
25
+
26
+ # generates a new snake location and orientation from scratch or via dependency injection of what head_cell and orientation are (for testing purposes)
27
+ def generate(initial_row: nil, initial_column: nil, initial_orientation: nil)
28
+ self.collided = false
29
+ initial_vertebra = Vertebra.new(snake: self, row: initial_row, column: initial_column, orientation: initial_orientation)
30
+ self.vertebrae = [initial_vertebra]
31
+ end
32
+
33
+ def length
34
+ @vertebrae.length
35
+ end
36
+
37
+ def head
38
+ @vertebrae.last
39
+ end
40
+
41
+ def tail
42
+ @vertebrae.first
43
+ end
44
+
45
+ def remove
46
+ self.vertebrae.clear
47
+ self.joins.clear
48
+ end
49
+
50
+ def move
51
+ @old_tail = tail.dup
52
+ @new_head = head.dup
53
+ case @new_head.orientation
54
+ when :east
55
+ @new_head.column = (@new_head.column + 1) % @game.width
56
+ when :west
57
+ @new_head.column = (@new_head.column - 1) % @game.width
58
+ when :south
59
+ @new_head.row = (@new_head.row + 1) % @game.height
60
+ when :north
61
+ @new_head.row = (@new_head.row - 1) % @game.height
62
+ end
63
+ if @vertebrae.map {|v| [v.row, v.column]}.include?([@new_head.row, @new_head.column])
64
+ self.collided = true
65
+ @game.over = true
66
+ else
67
+ @vertebrae.append(@new_head)
68
+ @vertebrae.delete(tail)
69
+ if head.row == @game.apple.row && head.column == @game.apple.column
70
+ grow
71
+ @game.apple.generate
72
+ end
73
+ end
74
+ end
75
+
76
+ def turn_right
77
+ head.orientation = RIGHT_TURN_MAP[head.orientation]
78
+ end
79
+
80
+ def turn_left
81
+ head.orientation = LEFT_TURN_MAP[head.orientation]
82
+ end
83
+
84
+ def grow
85
+ @game.score += SCORE_EAT_APPLE
86
+ @vertebrae.prepend(@old_tail)
87
+ end
88
+
89
+ # inspect is overridden to prevent printing very long stack traces
90
+ def inspect
91
+ "#{super[0, 150]}... >"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ class Snake
2
+ module Model
3
+ class Vertebra
4
+ ORIENTATIONS = %i[north east south west]
5
+ # orientation is needed for snake occuppied cells (but not apple cells)
6
+ attr_reader :snake
7
+ attr_accessor :row, :column, :orientation
8
+
9
+ def initialize(snake: , row: , column: , orientation: )
10
+ @row = row || rand(snake.game.height)
11
+ @column = column || rand(snake.game.width)
12
+ @orientation = orientation || ORIENTATIONS.sample
13
+ @snake = snake
14
+ end
15
+
16
+ # inspect is overridden to prevent printing very long stack traces
17
+ def inspect
18
+ "#{super[0, 150]}... >"
19
+ end
20
+ end
21
+ end
22
+ end