bubbles 0.0.4.1 → 0.1.0

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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +21 -0
  3. data/README.md +524 -79
  4. data/bubbles.gemspec +29 -21
  5. data/lib/bubbles/cursor.rb +169 -0
  6. data/lib/bubbles/file_picker.rb +397 -0
  7. data/lib/bubbles/help.rb +170 -0
  8. data/lib/bubbles/key.rb +96 -0
  9. data/lib/bubbles/list.rb +365 -0
  10. data/lib/bubbles/paginator.rb +158 -0
  11. data/lib/bubbles/progress.rb +276 -0
  12. data/lib/bubbles/spinner/spinners.rb +77 -0
  13. data/lib/bubbles/spinner.rb +122 -0
  14. data/lib/bubbles/stopwatch.rb +189 -0
  15. data/lib/bubbles/table.rb +248 -0
  16. data/lib/bubbles/text_area.rb +503 -0
  17. data/lib/bubbles/text_input.rb +543 -0
  18. data/lib/bubbles/timer.rb +196 -0
  19. data/lib/bubbles/version.rb +4 -1
  20. data/lib/bubbles/viewport.rb +296 -0
  21. data/lib/bubbles.rb +18 -34
  22. data/sig/bubbles/cursor.rbs +87 -0
  23. data/sig/bubbles/file_picker.rbs +138 -0
  24. data/sig/bubbles/help.rbs +88 -0
  25. data/sig/bubbles/key.rbs +63 -0
  26. data/sig/bubbles/list.rbs +138 -0
  27. data/sig/bubbles/paginator.rbs +90 -0
  28. data/sig/bubbles/progress.rbs +123 -0
  29. data/sig/bubbles/spinner/spinners.rbs +32 -0
  30. data/sig/bubbles/spinner.rbs +74 -0
  31. data/sig/bubbles/stopwatch.rbs +97 -0
  32. data/sig/bubbles/table.rbs +119 -0
  33. data/sig/bubbles/text_area.rbs +161 -0
  34. data/sig/bubbles/text_input.rbs +183 -0
  35. data/sig/bubbles/timer.rbs +107 -0
  36. data/sig/bubbles/version.rbs +5 -0
  37. data/sig/bubbles/viewport.rbs +125 -0
  38. data/sig/bubbles.rbs +4 -0
  39. metadata +66 -66
  40. data/.gitignore +0 -14
  41. data/.rspec +0 -2
  42. data/.travis.yml +0 -10
  43. data/Gemfile +0 -4
  44. data/LICENSE +0 -20
  45. data/Rakefile +0 -6
  46. data/bin/console +0 -14
  47. data/bin/setup +0 -8
  48. data/exe/bubbles +0 -5
  49. data/lib/bubbles/bubblicious_file.rb +0 -40
  50. data/lib/bubbles/command_queue.rb +0 -43
  51. data/lib/bubbles/common_uploader_interface.rb +0 -13
  52. data/lib/bubbles/config.rb +0 -149
  53. data/lib/bubbles/dir_watcher.rb +0 -53
  54. data/lib/bubbles/uploaders/local_dir.rb +0 -39
  55. data/lib/bubbles/uploaders/s3.rb +0 -36
  56. data/tmp/dummy_local_dir_uploader_dir/.gitkeep +0 -0
  57. data/tmp/dummy_processing_dir/.gitkeep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ea23e1f12dc2c015ac20730ce463b99d29374038
4
- data.tar.gz: c16242cd3bc2ce23e3fc3de294ebb9650dc52780
2
+ SHA256:
3
+ metadata.gz: d64710274a0d2e55de7efb197eee82f4e8506cd47b345baffecda3f45c08958d
4
+ data.tar.gz: bacbc6bbbe38cf7db3bcd7c2492bccfb2965f472e1f4c2d1e27c80d492a3834a
5
5
  SHA512:
6
- metadata.gz: 6872861fa07912bc5edb06f391f223c385d2784344d6b7917f92b4baee9517b990b467fadeb8493d04197e1fba81d79d33599425867cdebb7866949b2c5bb4ce
7
- data.tar.gz: 7e574f69ed3a9a936e178505cee402ecfc2509a4b1f242c7caf3349f720019424aa616e5d0490acd80994371e92c47731dcda7de5b9d90e7b687cba8069dd77f
6
+ metadata.gz: 9c8e0205d20ca9ebc1a7c3e18945b9e7d652200e27927d9292bca08a94716b85438fd8681b0efdbb5e099799457c56eb3cc19506b2922511000b6c228c0a5b11
7
+ data.tar.gz: 41ee83520438881adb482b5386b7cc6908d82bb4164829ca1a09f089a8ec420f3115264cefe73d4ac3f8e692d2fa4d5b4613d9d371a68b3496700e0973aa4bc1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Marco Roth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,137 +1,582 @@
1
- # Bubbles
1
+ <div align="center">
2
+ <h1>Bubbles for Ruby</h1>
3
+ <h4>TUI components for Bubble Tea</h4>
2
4
 
3
- [![Build Status](https://travis-ci.org/equivalent/bubbles.svg?branch=master)](https://travis-ci.org/equivalent/bubbles)
5
+ <p>
6
+ <a href="https://rubygems.org/gems/bubbles"><img alt="Gem Version" src="https://img.shields.io/gem/v/bubbles"></a>
7
+ <a href="https://github.com/marcoroth/bubbles-ruby/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/github/license/marcoroth/bubbles-ruby"></a>
8
+ </p>
4
9
 
5
- Daemonized file uploader that watch a folder and uploads any files files
6
- to AWS S3 or Local directory (e.g. mounted NAS volume). Designed for Raspberry pi zero
10
+ <p>Ruby implementation of <a href="https://github.com/charmbracelet/bubbles">charmbracelet/bubbles</a>.<br/>Common UI components for building terminal applications with <a href="https://github.com/marcoroth/bubbletea-ruby">Bubble Tea</a>.</p>
11
+ </div>
7
12
 
13
+ ## Installation
8
14
 
9
- Notes:
15
+ **Add to your Gemfile:**
10
16
 
11
- * for AWS S3 upload we use AWS-SDK [s3 put_object](http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#put_object-instance_method) the single file upload should not exceed 5GB. I'm not planing to introduce S3 multipart uploads but Pull Requests are welcome.
17
+ ```ruby
18
+ gem "bubbles"
19
+ ```
12
20
 
21
+ **Or install directly:**
13
22
 
14
- ## Installation
23
+ ```bash
24
+ gem install bubbles
25
+ ```
26
+
27
+ ## Components
28
+
29
+ | Component | Description |
30
+ |-----------|-------------|
31
+ | [Spinner](#spinner) | Loading spinners with multiple styles |
32
+ | [Progress](#progress) | Animated progress bars |
33
+ | [Timer](#timer) | Countdown timer |
34
+ | [Stopwatch](#stopwatch) | Elapsed time counter |
35
+ | [TextInput](#textinput) | Single-line text input with cursor |
36
+ | [TextArea](#textarea) | Multi-line text input |
37
+ | [Viewport](#viewport) | Scrollable content pane |
38
+ | [List](#list) | Interactive list with filtering |
39
+ | [Table](#table) | Data table with columns |
40
+ | [FilePicker](#filepicker) | File and directory browser |
41
+ | [Paginator](#paginator) | Pagination controls |
42
+ | [Help](#help) | Help text generator |
43
+ | [Key](#key) | Key binding definitions |
44
+ | [Cursor](#cursor) | Blinking cursor for inputs |
45
+
46
+ ## Usage
47
+
48
+ ### Spinner
49
+
50
+ **Animated loading indicator:**
51
+
52
+ ```ruby
53
+ require "bubbles"
54
+
55
+ spinner = Bubbles::Spinner.new
56
+ spinner.spinner = Bubbles::Spinners::DOTS
57
+ ```
15
58
 
16
- Add this line to your application's Gemfile:
59
+ **In your update method:**
17
60
 
18
61
  ```ruby
19
- gem 'bubbles'
62
+ spinner, command = spinner.update(message)
20
63
  ```
21
64
 
22
- And then execute:
65
+ **In your view method:**
23
66
 
24
- $ bundle
67
+ ```ruby
68
+ spinner.view
69
+ ```
25
70
 
26
- Or install it yourself as:
71
+ **Available spinner styles:**
27
72
 
28
- $ gem install bubbles
73
+ ```ruby
74
+ Bubbles::Spinners::LINE
75
+ Bubbles::Spinners::DOT
76
+ Bubbles::Spinners::MINI_DOT
77
+ Bubbles::Spinners::JUMP
78
+ Bubbles::Spinners::PULSE
79
+ Bubbles::Spinners::POINTS
80
+ Bubbles::Spinners::GLOBE
81
+ Bubbles::Spinners::MOON
82
+ Bubbles::Spinners::MONKEY
83
+ Bubbles::Spinners::METER
84
+ Bubbles::Spinners::HAMBURGER
85
+ Bubbles::Spinners::ELLIPSIS
86
+ ```
29
87
 
30
- ## Usage
88
+ ### Progress
89
+
90
+ **Animated progress bar:**
91
+
92
+ ```ruby
93
+ progress = Bubbles::Progress.new(width: 40)
94
+ progress.set_percent(0.5)
95
+ ```
96
+
97
+ **In your view:**
98
+
99
+ ```ruby
100
+ progress.view
101
+ ```
102
+
103
+ **Customization:**
31
104
 
32
- ##### The easy way
105
+ ```ruby
106
+ progress = Bubbles::Progress.new(
107
+ width: 40,
108
+ full: "█",
109
+ empty: "░",
110
+ show_percentage: true
111
+ )
112
+ progress.full_color = "212"
113
+ progress.empty_color = "238"
114
+ ```
33
115
 
34
- Gem by default looks into to two locations for configuration file. So
35
- either create `~/.bubbles/config` or `/var/lib/bubbles/config.yml`
116
+ ### Timer
36
117
 
37
- > note: make sure you have correct read access permission so that ruby
38
- > can read that file
118
+ **Countdown timer (60 seconds):**
39
119
 
40
- ...with content similar to this:
120
+ ```ruby
121
+ timer = Bubbles::Timer.new(60)
122
+ ```
123
+
124
+ **Start the timer:**
125
+
126
+ ```ruby
127
+ command = timer.start
128
+ ```
41
129
 
42
- ```yml
43
- s3_access_key_id: xxxxxxxxxxxxxxxxxxxx
44
- s3_secret_access_key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
45
- s3_bucket: bucket_name
46
- s3_region: eu-west-1
130
+ **In update:**
47
131
 
48
- source_dir: /home/myuser/source_folder
49
- processing_dir: /home/myuser/processing_folder
132
+ ```ruby
133
+ timer, command = timer.update(message)
50
134
  ```
51
135
 
52
- Now all is left is to lunch bubbles with `bubbles` or `bundle exec bubbles`
136
+ **Check if done:**
53
137
 
54
- > note: files in `source_folder` will get removed after successful upload
138
+ ```ruby
139
+ timer.timed_out?
140
+ ```
55
141
 
56
- ##### More advanced config
142
+ **In view:**
143
+
144
+ ```ruby
145
+ timer.view
146
+ ```
57
147
 
148
+ ### Stopwatch
58
149
 
59
- ```yml
60
- source_dir: /var/myuser/source_folder # source from where to pick up files
61
- processing_dir: /home/myuser/processing_folder
150
+ **Elapsed time counter:**
62
151
 
63
- log_level: 0 # debug log level
64
- log_path: /var/log/bubbles.log # default is STDOOT
152
+ ```ruby
153
+ stopwatch = Bubbles::Stopwatch.new
154
+ ```
65
155
 
66
- sleep_interval: 1 # sleep between next command
67
- num_of_files_to_schedule: 1 # how many files schedule for processing at the same time
156
+ **Start/stop/toggle:**
68
157
 
69
- uploaders:
70
- - 'Bubbles::Uploaders::S3' # by default only S3 uploader is used
71
- - 'Bubbles::Uploaders::LocalDir'
158
+ ```ruby
159
+ command = stopwatch.start
160
+ stopwatch.stop
161
+ command = stopwatch.toggle
162
+ ```
72
163
 
73
- s3_access_key_id: xxxxxxxxxxxxxxxxxxxx
74
- s3_secret_access_key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
75
- s3_bucket: bucket_name
76
- s3_region: eu-west-1
77
- s3_path: foo_folder/bar_folder/car_folder # will upload to s3://bucket/foo_folder/bar_folder/car_folder
78
- s3_acl: private # # accepts private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control
164
+ **In view:**
79
165
 
80
- local_dir_uploader_path: /mnt/network_smb_storage/
81
- local_dir_metadata_file_path: /var/log/uploads_metadata.yaml
166
+ ```ruby
167
+ stopwatch.view
82
168
  ```
83
169
 
84
- > Look into [lib/bubbles/config.rb](https://github.com/equivalent/bubbles/blob/master/lib/bubbles/config.rb) for more details.
170
+ ### TextInput
171
+
172
+ **Single-line text input:**
85
173
 
86
- ##### Full Ruby way
174
+ ```ruby
175
+ input = Bubbles::TextInput.new
176
+ input.placeholder = "Enter your name..."
177
+ input.prompt = "> "
178
+ input.focus
179
+ ```
87
180
 
88
- You can create custom ruby file and you initialize custom
89
- `Bubbles::Config.new` instance and set all the options you want there:
181
+ **In update:**
90
182
 
91
183
  ```ruby
92
- # ~/my_bubbles_runner.rb
93
- require 'bubbles'
184
+ input, command = input.update(message)
185
+ ```
94
186
 
95
- c = Bubbles::Config.new
187
+ **Get value:**
96
188
 
97
- c.log_level = 0
98
- c.source_dir = /var/myuser/source_folder
99
- c.processing_dir = /var/myuser/processing_folder
100
- c.uploader_classes = [Bubbles::Uploaders::S3, Bubbles::Uploaders::LocalDir]
101
- c.local_dir_uploader_path = /mnt/network_smb_storage
102
- c.s3_region = 'eu-west-1'
103
- c.s3_bucket = 'mybckt'
104
- c.s3_access_key_id = 'xxxxxxxxxxx'
105
- c.s3_secret_access_key = 'yyyyyyyyyyy'
189
+ ```ruby
190
+ input.value
191
+ ```
106
192
 
107
- # ...
193
+ **In view:**
194
+
195
+ ```ruby
196
+ input.view
197
+ ```
108
198
 
109
- command_queue = Bubbles::CommandQueue.new(config: c)
110
- command_queue << Bubbles::DirWatcher.new(config: c, command_queue: command_queue)
199
+ **Password mode:**
200
+
201
+ ```ruby
202
+ input.echo_mode = :password
203
+ input.echo_character = "*"
204
+ ```
205
+
206
+ **With suggestions:**
207
+
208
+ ```ruby
209
+ input.suggestions = ["apple", "apricot", "avocado"]
210
+ input.show_suggestions = true
211
+ ```
212
+
213
+ ### TextArea
214
+
215
+ **Multi-line text input:**
216
+
217
+ ```ruby
218
+ textarea = Bubbles::TextArea.new(width: 60, height: 10)
219
+ textarea.placeholder = "Type your message..."
220
+ textarea.show_line_numbers = true
221
+ textarea.focus
222
+ ```
223
+
224
+ **In update:**
225
+
226
+ ```ruby
227
+ textarea, command = textarea.update(message)
228
+ ```
229
+
230
+ **Get value:**
231
+
232
+ ```ruby
233
+ textarea.value
234
+ ```
235
+
236
+ **Position info:**
237
+
238
+ ```ruby
239
+ textarea.row
240
+ textarea.col
241
+ textarea.line_count
242
+ ```
243
+
244
+ ### Viewport
245
+
246
+ **Scrollable content pane:**
247
+
248
+ ```ruby
249
+ viewport = Bubbles::Viewport.new(width: 80, height: 20)
250
+ viewport.content = long_text
251
+ ```
252
+
253
+ **In update (handles scroll keys):**
254
+
255
+ ```ruby
256
+ viewport, command = viewport.update(message)
257
+ ```
258
+
259
+ **Scroll info:**
260
+
261
+ ```ruby
262
+ viewport.scroll_percent
263
+ viewport.at_top?
264
+ viewport.at_bottom?
265
+ ```
266
+
267
+ **In view:**
268
+
269
+ ```ruby
270
+ viewport.view
271
+ ```
272
+
273
+ **Programmatic scrolling:**
274
+
275
+ ```ruby
276
+ viewport.scroll_down(5)
277
+ viewport.scroll_up(5)
278
+ viewport.page_down
279
+ viewport.page_up
280
+ viewport.goto_top
281
+ viewport.goto_bottom
282
+ ```
283
+
284
+ ### List
285
+
286
+ **Interactive list with filtering:**
287
+
288
+ ```ruby
289
+ items = [
290
+ { title: "Item 1", description: "First item" },
291
+ { title: "Item 2", description: "Second item" }
292
+ ]
293
+
294
+ list = Bubbles::List.new(items, width: 40, height: 10)
295
+ list.title = "My List"
296
+ ```
297
+
298
+ **In update:**
299
+
300
+ ```ruby
301
+ list, command = list.update(message)
302
+ ```
111
303
 
112
- loop do
113
- command_queue.call_next
114
- sleep c.sleep_interval
304
+ **Get selection:**
305
+
306
+ ```ruby
307
+ list.selected_item
308
+ ```
309
+
310
+ **Filter state:**
311
+
312
+ ```ruby
313
+ list.filter_state
314
+ ```
315
+
316
+ **Styling:**
317
+
318
+ ```ruby
319
+ list.title_style = Lipgloss::Style.new.bold(true).foreground("212")
320
+ list.selected_item_style = Lipgloss::Style.new.foreground("212")
321
+ list.item_style = Lipgloss::Style.new.foreground("252")
322
+ ```
323
+
324
+ ### Table
325
+
326
+ **Data table with columns:**
327
+
328
+ ```ruby
329
+ columns = [
330
+ { title: "Name", width: 20 },
331
+ { title: "Age", width: 5 },
332
+ { title: "City", width: 15 }
333
+ ]
334
+
335
+ rows = [
336
+ ["Alice", "30", "New York"],
337
+ ["Bob", "25", "London"]
338
+ ]
339
+
340
+ table = Bubbles::Table.new(columns: columns, rows: rows, height: 10)
341
+ ```
342
+
343
+ **In update:**
344
+
345
+ ```ruby
346
+ table, command = table.update(message)
347
+ ```
348
+
349
+ **Get selection:**
350
+
351
+ ```ruby
352
+ table.selected_row
353
+ table.selected_row_data
354
+ ```
355
+
356
+ **Styling:**
357
+
358
+ ```ruby
359
+ table.header_style = Lipgloss::Style.new.bold(true).foreground("212")
360
+ table.cell_style = Lipgloss::Style.new.padding_left(1)
361
+ table.selected_style = Lipgloss::Style.new.bold(true).background("57")
362
+ ```
363
+
364
+ ### FilePicker
365
+
366
+ **File and directory browser:**
367
+
368
+ ```ruby
369
+ picker = Bubbles::FilePicker.new(directory: ".")
370
+ picker.height = 15
371
+ picker.show_hidden = false
372
+ picker.allowed_types = ["rb", "txt"]
373
+ ```
374
+
375
+ **In update:**
376
+
377
+ ```ruby
378
+ picker, command = picker.update(message)
379
+ ```
380
+
381
+ **Check for selection:**
382
+
383
+ ```ruby
384
+ if picker.did_select_file?
385
+ selected_path = picker.path
115
386
  end
116
387
  ```
117
388
 
118
- ..and execute:
389
+ **Options:**
390
+
391
+ ```ruby
392
+ picker.show_permissions = true
393
+ picker.show_size = true
394
+ picker.dir_allowed = false
395
+ picker.file_allowed = true
396
+ ```
397
+
398
+ ### Paginator
399
+
400
+ **Pagination controls:**
401
+
402
+ ```ruby
403
+ paginator = Bubbles::Paginator.new(type: Bubbles::Paginator::DOTS)
404
+ paginator.per_page = 10
405
+ paginator.update_total_pages(100)
406
+ ```
407
+
408
+ **Navigation:**
409
+
410
+ ```ruby
411
+ paginator.next_page
412
+ paginator.prev_page
413
+ ```
414
+
415
+ **Get slice bounds for your data:**
416
+
417
+ ```ruby
418
+ start_index, end_index = paginator.slice_bounds(items.length)
419
+ visible_items = items[start_index...end_index]
420
+ ```
421
+
422
+ **In view:**
423
+
424
+ ```ruby
425
+ paginator.view
426
+ ```
427
+
428
+ **Types:**
429
+
430
+ ```ruby
431
+ Bubbles::Paginator::ARABIC
432
+ Bubbles::Paginator::DOTS
433
+ ```
434
+
435
+ ### Help
436
+
437
+ **Help text generator:**
438
+
439
+ ```ruby
440
+ help = Bubbles::Help.new
441
+
442
+ bindings = [
443
+ Bubbles::Key.binding(keys: ["up", "k"], help: ["↑/k", "up"]),
444
+ Bubbles::Key.binding(keys: ["down", "j"], help: ["↓/j", "down"]),
445
+ Bubbles::Key.binding(keys: ["q"], help: ["q", "quit"])
446
+ ]
447
+
448
+ help.short_help_view(bindings)
449
+ ```
450
+
451
+ ### Key
452
+
453
+ **Key binding definitions:**
454
+
455
+ ```ruby
456
+ quit_binding = Bubbles::Key.binding(
457
+ keys: ["q", "ctrl+c"],
458
+ help: ["q", "quit"]
459
+ )
460
+ ```
461
+
462
+ **Check if a key matches:**
463
+
464
+ ```ruby
465
+ Bubbles::Key.matches?(message, quit_binding)
466
+ ```
119
467
 
120
- ```sh
121
- ruby my_bubbles_runner.rb
122
- # or
123
- bundle exec ruby my_bubbles_runner.rb
468
+ ### Cursor
469
+
470
+ **Blinking cursor for inputs:**
471
+
472
+ ```ruby
473
+ cursor = Bubbles::Cursor.new
474
+ cursor.char = "_"
475
+ cursor.focus
124
476
  ```
125
477
 
126
- > Look into [lib/bubbles/config.rb](https://github.com/equivalent/bubbles/blob/master/lib/bubbles/config.rb) for more details.
478
+ **Set cursor mode:**
479
+
480
+ ```ruby
481
+ cursor.set_mode(:blink)
482
+ cursor.set_mode(:static)
483
+ cursor.set_mode(:hide)
484
+ ```
485
+
486
+ **In update:**
487
+
488
+ ```ruby
489
+ cursor, command = cursor.update(message)
490
+ ```
491
+
492
+ **In view:**
493
+
494
+ ```ruby
495
+ cursor.view
496
+ ```
497
+
498
+ ## Complete Example
499
+
500
+ ```ruby
501
+ require "bubbletea"
502
+ require "lipgloss"
503
+ require "bubbles"
504
+
505
+ class MyApp
506
+ include Bubbletea::Model
507
+
508
+ def initialize
509
+ @spinner = Bubbles::Spinner.new
510
+ @spinner.spinner = Bubbles::Spinners::DOTS
511
+ end
512
+
513
+ def init
514
+ [self, @spinner.tick]
515
+ end
516
+
517
+ def update(message)
518
+ case message
519
+ when Bubbletea::KeyMessage
520
+ return [self, Bubbletea.quit] if message.to_s == "q"
521
+ end
522
+
523
+ @spinner, command = @spinner.update(message)
524
+ [self, command]
525
+ end
526
+
527
+ def view
528
+ "#{@spinner.view} Loading...\n\nPress q to quit"
529
+ end
530
+ end
531
+
532
+ Bubbletea.run(MyApp.new)
533
+ ```
127
534
 
128
535
  ## Development
129
536
 
130
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
131
- To install this gem onto your local machine, run `bundle exec rake install`.
537
+ **Requirements:**
538
+ - Ruby 3.2+
539
+ - [bubbletea-ruby](https://github.com/marcoroth/bubbletea-ruby)
540
+ - [lipgloss-ruby](https://github.com/marcoroth/lipgloss-ruby) (optional, for styling)
541
+
542
+ **Install dependencies:**
543
+
544
+ ```bash
545
+ bundle install
546
+ ```
547
+
548
+ **Run tests:**
549
+
550
+ ```bash
551
+ bundle exec rake test
552
+ ```
553
+
554
+ **Run demos:**
555
+
556
+ ```bash
557
+ ./demo/spinner
558
+ ./demo/progress
559
+ ./demo/textinput
560
+ ./demo/textarea
561
+ ./demo/viewport
562
+ ./demo/list
563
+ ./demo/table
564
+ ./demo/filepicker
565
+ ./demo/timer
566
+ ./demo/stopwatch
567
+ ./demo/paginator
568
+ ./demo/help
569
+ ./demo/cursor
570
+ ```
132
571
 
133
572
  ## Contributing
134
573
 
135
- Bug reports and pull requests are welcome on GitHub at https://github.com/equivalent/bubbles.
574
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/bubbles-ruby.
575
+
576
+ ## License
577
+
578
+ The gem is available as open source under the terms of the MIT License.
136
579
 
580
+ ## Acknowledgments
137
581
 
582
+ This gem is a Ruby implementation of [charmbracelet/bubbles](https://github.com/charmbracelet/bubbles), part of the excellent [Charm](https://charm.sh) ecosystem.