rooibos 0.6.2 → 0.7.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 (217) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSES/BSD-2-Clause.txt +9 -0
  3. data/REUSE.toml +5 -0
  4. data/exe/.gitkeep +0 -0
  5. data/lib/rooibos/cli/commands/new.rb +24 -0
  6. data/lib/rooibos/command/batch.rb +10 -0
  7. data/lib/rooibos/command/bubble.rb +34 -0
  8. data/lib/rooibos/command/custom.rb +3 -2
  9. data/lib/rooibos/command/deliver.rb +50 -0
  10. data/lib/rooibos/command/http.rb +1 -1
  11. data/lib/rooibos/command/lifecycle.rb +3 -1
  12. data/lib/rooibos/command/outlet.rb +19 -9
  13. data/lib/rooibos/command.rb +107 -3
  14. data/lib/rooibos/configuration.rb +29 -0
  15. data/lib/rooibos/message/bubbled.rb +29 -0
  16. data/lib/rooibos/message.rb +24 -6
  17. data/lib/rooibos/router/action.rb +36 -0
  18. data/lib/rooibos/router/flow/dispatch.rb +39 -0
  19. data/lib/rooibos/router/flow/inward.rb +41 -0
  20. data/lib/rooibos/router/flow/outward.rb +44 -0
  21. data/lib/rooibos/router/guard.rb +56 -0
  22. data/lib/rooibos/router/predicate.rb +65 -0
  23. data/lib/rooibos/router/registry/actions.rb +41 -0
  24. data/lib/rooibos/router/registry/forwards.rb +58 -0
  25. data/lib/rooibos/router/registry/observes.rb +57 -0
  26. data/lib/rooibos/router/registry/otherwises.rb +29 -0
  27. data/lib/rooibos/router/registry/receives.rb +57 -0
  28. data/lib/rooibos/router/registry/routes.rb +59 -0
  29. data/lib/rooibos/router/registry.rb +26 -0
  30. data/lib/rooibos/router/route.rb +42 -0
  31. data/lib/rooibos/router/router_update.rb +53 -0
  32. data/lib/rooibos/router/rule/forward.rb +39 -0
  33. data/lib/rooibos/router/rule/observe.rb +22 -0
  34. data/lib/rooibos/router/rule/otherwise.rb +26 -0
  35. data/lib/rooibos/router/rule/receive.rb +22 -0
  36. data/lib/rooibos/router/rule.rb +40 -0
  37. data/lib/rooibos/router.rb +424 -438
  38. data/lib/rooibos/runtime.rb +37 -52
  39. data/lib/rooibos/test_helper.rb +22 -0
  40. data/lib/rooibos/transition.rb +92 -0
  41. data/lib/rooibos/version.rb +1 -1
  42. data/lib/rooibos.rb +2 -57
  43. data/sig/rooibos/cli.rbs +1 -0
  44. data/sig/rooibos/command.rbs +44 -0
  45. data/sig/rooibos/configuration.rbs +20 -0
  46. data/sig/rooibos/message.rbs +12 -0
  47. data/sig/rooibos/router/action.rbs +33 -0
  48. data/sig/rooibos/router/actions.rbs +27 -0
  49. data/sig/rooibos/router/flow/dispatch.rbs +29 -0
  50. data/sig/rooibos/router/flow/inward.rbs +37 -0
  51. data/sig/rooibos/router/flow/outward.rbs +36 -0
  52. data/sig/rooibos/router/forward.rbs +35 -0
  53. data/sig/rooibos/router/forwards.rbs +34 -0
  54. data/sig/rooibos/router/guard.rbs +21 -0
  55. data/sig/rooibos/router/observe.rbs +20 -0
  56. data/sig/rooibos/router/observes.rbs +38 -0
  57. data/sig/rooibos/router/otherwise.rbs +22 -0
  58. data/sig/rooibos/router/otherwises.rbs +20 -0
  59. data/sig/rooibos/router/predicate.rbs +51 -0
  60. data/sig/rooibos/router/receive.rbs +20 -0
  61. data/sig/rooibos/router/receives.rbs +38 -0
  62. data/sig/rooibos/router/registry.rbs +24 -0
  63. data/sig/rooibos/router/route.rbs +46 -0
  64. data/sig/rooibos/router/router_update.rbs +33 -0
  65. data/sig/rooibos/router/routes.rbs +41 -0
  66. data/sig/rooibos/router/rule.rbs +36 -0
  67. data/sig/rooibos/router.rbs +216 -161
  68. data/sig/rooibos/runtime.rbs +0 -1
  69. data/sig/rooibos/test_helper.rbs +6 -0
  70. data/sig/rooibos/transition.rbs +33 -0
  71. data/sig/rooibos.rbs +0 -10
  72. metadata +144 -198
  73. data/.builds/ruby-3.2.yml +0 -55
  74. data/.builds/ruby-3.3.yml +0 -55
  75. data/.builds/ruby-3.4.yml +0 -55
  76. data/.builds/ruby-4.0.0.yml +0 -55
  77. data/.pre-commit-config.yaml +0 -16
  78. data/.rubocop.yml +0 -8
  79. data/AGENTS.md +0 -108
  80. data/CHANGELOG.md +0 -308
  81. data/README.md +0 -183
  82. data/README.rdoc +0 -374
  83. data/Rakefile +0 -16
  84. data/Steepfile +0 -13
  85. data/doc/best_practices/forms_and_validation.md +0 -20
  86. data/doc/best_practices/http_workflows.md +0 -20
  87. data/doc/best_practices/index.md +0 -26
  88. data/doc/best_practices/lists_and_tables.md +0 -20
  89. data/doc/best_practices/modal_dialogs.md +0 -20
  90. data/doc/best_practices/no_stateful_widgets.md +0 -184
  91. data/doc/best_practices/orchestration.md +0 -20
  92. data/doc/best_practices/streaming_data.md +0 -20
  93. data/doc/contributors/design/commands_and_outlets.md +0 -214
  94. data/doc/contributors/design/mvu_tea_implementations_research.md +0 -373
  95. data/doc/contributors/documentation_plan.md +0 -616
  96. data/doc/contributors/documentation_stub_audit.md +0 -112
  97. data/doc/contributors/documentation_style.md +0 -275
  98. data/doc/contributors/e2e_pty.md +0 -168
  99. data/doc/contributors/maybe_stateful_router.md +0 -56
  100. data/doc/contributors/specs/earliest_tutorial_steps_per_story.md +0 -70
  101. data/doc/contributors/specs/file_browser.md +0 -789
  102. data/doc/contributors/specs/file_browser_stories.md +0 -784
  103. data/doc/contributors/specs/tutorials_to_stories.rb +0 -167
  104. data/doc/contributors/todo/scrollbar.md +0 -118
  105. data/doc/contributors/tutorial_old/01_project_setup.md +0 -20
  106. data/doc/contributors/tutorial_old/02_hello_world.md +0 -24
  107. data/doc/contributors/tutorial_old/03_adding_state.md +0 -26
  108. data/doc/contributors/tutorial_old/06_organizing_your_code.md +0 -20
  109. data/doc/contributors/tutorial_old/07_your_first_command.md +0 -21
  110. data/doc/contributors/tutorial_old/08_the_preview_pane.md +0 -20
  111. data/doc/contributors/tutorial_old/09_loading_states.md +0 -20
  112. data/doc/contributors/tutorial_old/10_testing_your_app.md +0 -20
  113. data/doc/contributors/tutorial_old/11_polish_and_refine.md +0 -20
  114. data/doc/contributors/tutorial_old/12_going_further.md +0 -20
  115. data/doc/contributors/tutorial_old/index.md +0 -20
  116. data/doc/custom.css +0 -22
  117. data/doc/essentials/commands.md +0 -20
  118. data/doc/essentials/index.md +0 -31
  119. data/doc/essentials/messages.md +0 -21
  120. data/doc/essentials/models.md +0 -21
  121. data/doc/essentials/shortcuts.md +0 -19
  122. data/doc/essentials/the_elm_architecture.md +0 -24
  123. data/doc/essentials/the_runtime.md +0 -21
  124. data/doc/essentials/update_functions.md +0 -20
  125. data/doc/essentials/views.md +0 -22
  126. data/doc/getting_started/for_go_developers.md +0 -16
  127. data/doc/getting_started/for_python_developers.md +0 -16
  128. data/doc/getting_started/for_rails_developers.md +0 -17
  129. data/doc/getting_started/for_ratatui_ruby_developers.md +0 -17
  130. data/doc/getting_started/for_react_developers.md +0 -17
  131. data/doc/getting_started/index.md +0 -52
  132. data/doc/getting_started/install.md +0 -20
  133. data/doc/getting_started/quickstart.md +0 -20
  134. data/doc/getting_started/ruby_primer.md +0 -19
  135. data/doc/getting_started/why_rooibos.md +0 -20
  136. data/doc/images/verify_readme_usage.png +0 -0
  137. data/doc/images/widget_cmd_exec.png +0 -0
  138. data/doc/index.md +0 -93
  139. data/doc/scaling_up/async_patterns.md +0 -20
  140. data/doc/scaling_up/command_composition.md +0 -20
  141. data/doc/scaling_up/custom_commands.md +0 -21
  142. data/doc/scaling_up/fractal_architecture.md +0 -20
  143. data/doc/scaling_up/index.md +0 -30
  144. data/doc/scaling_up/message_routing.md +0 -20
  145. data/doc/scaling_up/ractor_safety.md +0 -20
  146. data/doc/scaling_up/testing.md +0 -21
  147. data/doc/troubleshooting/common_errors.md +0 -20
  148. data/doc/troubleshooting/debugging.md +0 -21
  149. data/doc/troubleshooting/index.md +0 -23
  150. data/doc/troubleshooting/performance.md +0 -20
  151. data/doc/tutorial/01_project_setup.md +0 -44
  152. data/doc/tutorial/02_hello_world.md +0 -45
  153. data/doc/tutorial/03_static_file_list.md +0 -44
  154. data/doc/tutorial/04_arrow_navigation.md +0 -47
  155. data/doc/tutorial/05_real_files.md +0 -45
  156. data/doc/tutorial/06_safe_refactoring.md +0 -21
  157. data/doc/tutorial/07_red_first_tdd.md +0 -26
  158. data/doc/tutorial/08_file_metadata.md +0 -42
  159. data/doc/tutorial/09_text_preview.md +0 -44
  160. data/doc/tutorial/10_directory_tree.md +0 -42
  161. data/doc/tutorial/11_pane_focus.md +0 -40
  162. data/doc/tutorial/12_sorting.md +0 -41
  163. data/doc/tutorial/13_filtering.md +0 -43
  164. data/doc/tutorial/14_toggle_hidden.md +0 -41
  165. data/doc/tutorial/15_text_input_widget.md +0 -43
  166. data/doc/tutorial/16_rename_files.md +0 -42
  167. data/doc/tutorial/17_confirmation_dialogs.md +0 -43
  168. data/doc/tutorial/18_progress_indicators.md +0 -43
  169. data/doc/tutorial/19_atomic_operations.md +0 -42
  170. data/doc/tutorial/20_external_editor.md +0 -42
  171. data/doc/tutorial/21_modal_overlays.md +0 -41
  172. data/doc/tutorial/22_error_handling.md +0 -43
  173. data/doc/tutorial/23_terminal_capabilities.md +0 -53
  174. data/doc/tutorial/24_mouse_events.md +0 -43
  175. data/doc/tutorial/25_resize_events.md +0 -43
  176. data/doc/tutorial/26_loading_states.md +0 -42
  177. data/doc/tutorial/27_performance.md +0 -43
  178. data/doc/tutorial/28_color_schemes.md +0 -47
  179. data/doc/tutorial/29_configuration.md +0 -124
  180. data/doc/tutorial/30_going_further.md +0 -17
  181. data/doc/tutorial/index.md +0 -17
  182. data/examples/app_fractal_dashboard/README.md +0 -60
  183. data/examples/app_fractal_dashboard/app.rb +0 -63
  184. data/examples/app_fractal_dashboard/dashboard/base.rb +0 -73
  185. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +0 -86
  186. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +0 -87
  187. data/examples/app_fractal_dashboard/dashboard/update_router.rb +0 -43
  188. data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +0 -81
  189. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +0 -82
  190. data/examples/app_fractal_dashboard/fragments/custom_shell_output.rb +0 -90
  191. data/examples/app_fractal_dashboard/fragments/disk_usage.rb +0 -47
  192. data/examples/app_fractal_dashboard/fragments/network_panel.rb +0 -45
  193. data/examples/app_fractal_dashboard/fragments/ping.rb +0 -47
  194. data/examples/app_fractal_dashboard/fragments/stats_panel.rb +0 -45
  195. data/examples/app_fractal_dashboard/fragments/system_info.rb +0 -47
  196. data/examples/app_fractal_dashboard/fragments/uptime.rb +0 -47
  197. data/examples/tutorial/01/app.rb +0 -50
  198. data/examples/tutorial/02/app.rb +0 -64
  199. data/examples/tutorial/03/app.rb +0 -91
  200. data/examples/tutorial/06_safe_refactoring/app.rb +0 -124
  201. data/examples/verify_readme_usage/README.md +0 -54
  202. data/examples/verify_readme_usage/app.rb +0 -47
  203. data/examples/verify_website_first_app/app.rb +0 -85
  204. data/examples/verify_website_hello_mvu/app.rb +0 -31
  205. data/examples/widget_command_system/README.md +0 -70
  206. data/examples/widget_command_system/app.rb +0 -134
  207. data/generate_tutorial_stubs.rb +0 -126
  208. data/mise.toml +0 -8
  209. data/rbs_collection.lock.yaml +0 -108
  210. data/rbs_collection.yaml +0 -15
  211. data/tasks/example_viewer.html.erb +0 -172
  212. data/tasks/install.rake +0 -29
  213. data/tasks/resources/build.yml.erb +0 -55
  214. data/tasks/resources/index.html.erb +0 -44
  215. data/tasks/resources/rubies.yml +0 -7
  216. data/tasks/steep.rake +0 -11
  217. /data/{vendor/goodcop/base.yml → lib/rooibos/rubocop.yml} +0 -0
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
- # Fetches and displays disk usage via +df -h+.
10
- # A fragment for fetching and displaying disk usage.
11
- module DiskUsage
12
- Command = Rooibos::Command
13
-
14
- Model = Data.define(:output, :loading)
15
-
16
- Init = -> do
17
- Model.new(output: "Press 'd' for disk usage", loading: false)
18
- end
19
-
20
- View = -> (model, tui, disabled: false) do
21
- text_style = if disabled && model.output == Init.().output
22
- tui.style(fg: :dark_gray)
23
- else
24
- nil
25
- end
26
-
27
- tui.paragraph(
28
- text: tui.text_span(content: model.output, style: text_style),
29
- block: tui.block(title: "Disk Usage", borders: [:all], border_style: { fg: :cyan })
30
- )
31
- end
32
-
33
- Update = -> (message, model) do
34
- case message
35
- in { type: :system, envelope: :disk_usage, status: 0, stdout: }
36
- [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
37
- in { type: :system, envelope: :disk_usage, stderr: }
38
- [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
39
- else
40
- [model, nil]
41
- end
42
- end
43
-
44
- def self.fetch_command
45
- Command.system("df -h /", :disk_usage)
46
- end
47
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
- require_relative "ping"
10
- require_relative "uptime"
11
-
12
- # Composes Ping and Uptime in a horizontal layout.
13
- module NetworkPanel
14
- Model = Data.define(:ping, :uptime)
15
-
16
- Init = -> do
17
- ping, = Rooibos.normalize_init(Ping::Init.())
18
- uptime, = Rooibos.normalize_init(Uptime::Init.())
19
- Model.new(ping:, uptime:)
20
- end
21
-
22
- View = -> (model, tui, disabled: false) do
23
- tui.layout(
24
- direction: :horizontal,
25
- constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)],
26
- children: [
27
- Ping::View.call(model.ping, tui, disabled:),
28
- Uptime::View.call(model.uptime, tui, disabled:),
29
- ]
30
- )
31
- end
32
-
33
- Update = -> (message, model) do
34
- case message
35
- in { envelope: :ping, ** }
36
- new_child, command = Ping::Update.call(message, model.ping)
37
- [model.with(ping: new_child), command]
38
- in { envelope: :uptime, ** }
39
- new_child, command = Uptime::Update.call(message, model.uptime)
40
- [model.with(uptime: new_child), command]
41
- else
42
- [model, nil]
43
- end
44
- end
45
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
- # Pings localhost to check network connectivity.
10
- # A fragment for pinging localhost.
11
- module Ping
12
- Command = Rooibos::Command
13
-
14
- Model = Data.define(:output, :loading)
15
-
16
- Init = -> do
17
- Model.new(output: "Press 'p' for ping", loading: false)
18
- end
19
-
20
- View = -> (model, tui, disabled: false) do
21
- text_style = if disabled && model.output == Init.().output
22
- tui.style(fg: :dark_gray)
23
- else
24
- nil
25
- end
26
-
27
- tui.paragraph(
28
- text: tui.text_span(content: model.output, style: text_style),
29
- block: tui.block(title: "Ping", borders: [:all], border_style: { fg: :magenta })
30
- )
31
- end
32
-
33
- Update = -> (message, model) do
34
- case message
35
- in { type: :system, envelope: :ping, status: 0, stdout: }
36
- [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
37
- in { type: :system, envelope: :ping, stderr: }
38
- [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
39
- else
40
- [model, nil]
41
- end
42
- end
43
-
44
- def self.fetch_command
45
- Command.system("ping -c 3 8.8.8.8", :ping)
46
- end
47
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
- require_relative "system_info"
10
- require_relative "disk_usage"
11
-
12
- # Composes SystemInfo and DiskUsage in a horizontal layout.
13
- module StatsPanel
14
- Model = Data.define(:system_info, :disk_usage)
15
-
16
- Init = -> do
17
- system_info, = Rooibos.normalize_init(SystemInfo::Init.())
18
- disk_usage, = Rooibos.normalize_init(DiskUsage::Init.())
19
- Model.new(system_info:, disk_usage:)
20
- end
21
-
22
- View = -> (model, tui, disabled: false) do
23
- tui.layout(
24
- direction: :horizontal,
25
- constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)],
26
- children: [
27
- SystemInfo::View.call(model.system_info, tui, disabled:),
28
- DiskUsage::View.call(model.disk_usage, tui, disabled:),
29
- ]
30
- )
31
- end
32
-
33
- Update = -> (message, model) do
34
- case message
35
- in { envelope: :system_info, ** }
36
- new_child, command = SystemInfo::Update.call(message, model.system_info)
37
- [model.with(system_info: new_child), command]
38
- in { envelope: :disk_usage, ** }
39
- new_child, command = DiskUsage::Update.call(message, model.disk_usage)
40
- [model.with(disk_usage: new_child), command]
41
- else
42
- [model, nil]
43
- end
44
- end
45
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
- # Fetches and displays system information via +uname -a+.
10
- # A fragment for fetching and displaying system information.
11
- module SystemInfo
12
- Command = Rooibos::Command
13
-
14
- Model = Data.define(:output, :loading)
15
-
16
- Init = -> do
17
- Model.new(output: "Press 's' for system info", loading: false)
18
- end
19
-
20
- View = -> (model, tui, disabled: false) do
21
- text_style = if disabled && model.output == Init.().output
22
- tui.style(fg: :dark_gray)
23
- else
24
- nil
25
- end
26
-
27
- tui.paragraph(
28
- text: tui.text_span(content: model.output, style: text_style),
29
- block: tui.block(title: "System Info", borders: [:all], border_style: { fg: :cyan })
30
- )
31
- end
32
-
33
- Update = -> (message, model) do
34
- case message
35
- in { type: :system, envelope: :system_info, status: 0, stdout: }
36
- [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
37
- in { type: :system, envelope: :system_info, stderr: }
38
- [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
39
- else
40
- [model, nil]
41
- end
42
- end
43
-
44
- def self.fetch_command
45
- Command.system("uname -a", :system_info)
46
- end
47
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
- # Displays system uptime.
10
- # A fragment for displaying system uptime.
11
- module Uptime
12
- Command = Rooibos::Command
13
-
14
- Model = Data.define(:output, :loading)
15
-
16
- Init = -> do
17
- Model.new(output: "Press 'u' for uptime", loading: false)
18
- end
19
-
20
- View = -> (model, tui, disabled: false) do
21
- text_style = if disabled && model.output == Init.().output
22
- tui.style(fg: :dark_gray)
23
- else
24
- nil
25
- end
26
-
27
- tui.paragraph(
28
- text: tui.text_span(content: model.output, style: text_style),
29
- block: tui.block(title: "Uptime", borders: [:all], border_style: { fg: :magenta })
30
- )
31
- end
32
-
33
- Update = -> (message, model) do
34
- case message
35
- in { type: :system, envelope: :uptime, status: 0, stdout: }
36
- [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
37
- in { type: :system, envelope: :uptime, stderr: }
38
- [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
39
- else
40
- [model, nil]
41
- end
42
- end
43
-
44
- def self.fetch_command
45
- Command.system("uptime", :uptime)
46
- end
47
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- #
6
- # SPDX-License-Identifier: AGPL-3.0-or-later
7
- #++
8
-
9
- require "rooibos"
10
-
11
- module Tutorial01
12
- module FileBrowser
13
- Entry = Data.define(:name, :directory?)
14
-
15
- Model = Data.define(:current_directory, :entries)
16
-
17
- View = -> (model, tui) {
18
- items = model.entries.map { |e| e.directory? ? "#{e.name}/" : e.name }
19
- tui.layout(children: [
20
- tui.paragraph(text: model.current_directory),
21
- tui.list(items:),
22
- ])
23
- }
24
-
25
- Update = -> (message, model) {
26
- if message.ctrl_c? or message.q?
27
- Rooibos::Command.exit
28
- else
29
- model
30
- end
31
- }
32
-
33
- ReadEntries = -> (path) {
34
- Dir.children(path).map { |name|
35
- full_path = File.join(path, name)
36
- Entry.new(name:, directory?: File.directory?(full_path))
37
- }.sort_by { |e| [e.directory? ? 0 : 1, e.name.downcase] }
38
- }
39
-
40
- Init = -> {
41
- current_directory = Dir.pwd
42
- entries = ReadEntries.call(current_directory)
43
- Ractor.make_shareable Model.new(current_directory, entries)
44
- }
45
- end
46
- end
47
-
48
- if __FILE__ == $0
49
- Rooibos.run(Tutorial01::FileBrowser)
50
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- #
6
- # SPDX-License-Identifier: AGPL-3.0-or-later
7
- #++
8
-
9
- require "rooibos"
10
-
11
- module Tutorial02
12
- module FileBrowser
13
- Entry = Data.define(:name, :directory?)
14
-
15
- Model = Data.define(:current_directory, :entries, :selected_index)
16
-
17
- View = -> (model, tui) {
18
- items = model.entries.map { |e| e.directory? ? "#{e.name}/" : e.name }
19
- tui.layout(children: [
20
- tui.paragraph(text: model.current_directory),
21
- tui.list(
22
- items:,
23
- selected_index: model.selected_index,
24
- highlight_style: tui.style(modifiers: [:reversed])
25
- ),
26
- ])
27
- }
28
-
29
- Update = -> (message, model) {
30
- if message.ctrl_c? or message.q?
31
- Rooibos::Command.exit
32
- elsif message.down_arrow? or message.j?
33
- new_index = (model.selected_index + 1) % model.entries.length
34
- model.with(selected_index: new_index)
35
- elsif message.up_arrow? or message.k?
36
- new_index = (model.selected_index - 1) % model.entries.length
37
- model.with(selected_index: new_index)
38
- elsif message.home? or message.g?
39
- model.with(selected_index: 0)
40
- elsif message.end? or message.G?
41
- model.with(selected_index: model.entries.length - 1)
42
- else
43
- model
44
- end
45
- }
46
-
47
- ReadEntries = -> (path) {
48
- Dir.children(path).map { |name|
49
- full_path = File.join(path, name)
50
- Entry.new(name:, directory?: File.directory?(full_path))
51
- }.sort_by { |e| [e.directory? ? 0 : 1, e.name.downcase] }
52
- }
53
-
54
- Init = -> {
55
- current_directory = Dir.pwd
56
- entries = ReadEntries.call(current_directory)
57
- Ractor.make_shareable Model.new(current_directory, entries, 0)
58
- }
59
- end
60
- end
61
-
62
- if __FILE__ == $0
63
- Rooibos.run(Tutorial02::FileBrowser)
64
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- #
6
- # SPDX-License-Identifier: AGPL-3.0-or-later
7
- #++
8
-
9
- require "rooibos"
10
-
11
- module Tutorial03
12
- module FileBrowser
13
- Entry = Data.define(:name, :directory?)
14
-
15
- Model = Data.define(:current_directory, :entries, :selected_index)
16
-
17
- View = -> (model, tui) {
18
- items = model.entries.map { |e| e.directory? ? "#{e.name}/" : e.name }
19
- tui.layout(children: [
20
- tui.paragraph(text: model.current_directory),
21
- tui.list(
22
- items:,
23
- selected_index: model.selected_index,
24
- highlight_style: tui.style(modifiers: [:reversed])
25
- ),
26
- ])
27
- }
28
-
29
- Update = -> (message, model) {
30
- if message.ctrl_c? or message.q?
31
- Rooibos::Command.exit
32
- elsif message.down_arrow? or message.j?
33
- new_index = (model.selected_index + 1) % model.entries.length
34
- model.with(selected_index: new_index)
35
- elsif message.up_arrow? or message.k?
36
- new_index = (model.selected_index - 1) % model.entries.length
37
- model.with(selected_index: new_index)
38
- elsif message.home? or message.g?
39
- model.with(selected_index: 0)
40
- elsif message.end? or message.G?
41
- model.with(selected_index: model.entries.length - 1)
42
- elsif message.enter? or message.right_arrow? or message.l?
43
- selected_entry = model.entries[model.selected_index]
44
- if selected_entry.directory?
45
- new_dir = File.join(model.current_directory, selected_entry.name)
46
- new_entries = read_entries(new_dir)
47
- model.with(current_directory: new_dir, entries: new_entries, selected_index: 0)
48
- else
49
- model
50
- end
51
- elsif message.backspace? or message.left_arrow? or message.h?
52
- parent_dir = File.dirname(model.current_directory)
53
- new_entries = read_entries(parent_dir)
54
- model.with(current_directory: parent_dir, entries: new_entries, selected_index: 0)
55
- elsif message.tilde?
56
- home_dir = Dir.home
57
- new_entries = read_entries(home_dir)
58
- model.with(current_directory: home_dir, entries: new_entries, selected_index: 0)
59
- elsif message.slash?
60
- new_entries = read_entries("/")
61
- model.with(current_directory: "/", entries: new_entries, selected_index: 0)
62
- elsif message.R?
63
- new_entries = read_entries(model.current_directory)
64
- model.with(entries: new_entries)
65
- else
66
- model
67
- end
68
- }
69
-
70
- ReadEntries = -> (path) {
71
- Dir.children(path).map { |name|
72
- full_path = File.join(path, name)
73
- Entry.new(name:, directory?: File.directory?(full_path))
74
- }.sort_by { |e| [e.directory? ? 0 : 1, e.name.downcase] }
75
- }
76
-
77
- Init = -> {
78
- current_directory = Dir.pwd
79
- entries = ReadEntries.call(current_directory)
80
- Ractor.make_shareable Model.new(current_directory, entries, 0)
81
- }
82
-
83
- private_class_method def self.read_entries(path)
84
- ReadEntries.call(path)
85
- end
86
- end
87
- end
88
-
89
- if __FILE__ == $0
90
- Rooibos.run(Tutorial03::FileBrowser)
91
- end
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- #
6
- # SPDX-License-Identifier: AGPL-3.0-or-later
7
- #++
8
-
9
- require "rooibos"
10
-
11
- module Tutorial06
12
- # A Fragment is a module with Model, Init, Update, View.
13
- # This FileList fragment handles the list of files and navigation.
14
- module FileList
15
- Entry = Data.define(:name, :directory?)
16
-
17
- Model = Data.define(:current_directory, :entries, :selected_index)
18
-
19
- View = -> (model, tui) {
20
- items = model.entries.map { |e| e.directory? ? "#{e.name}/" : e.name }
21
- tui.layout(children: [
22
- tui.paragraph(text: model.current_directory),
23
- tui.list(
24
- items:,
25
- selected_index: model.selected_index,
26
- highlight_style: tui.style(modifiers: [:reversed])
27
- ),
28
- ])
29
- }
30
-
31
- Update = -> (message, model) {
32
- if message.move_down?
33
- new_index = (model.selected_index + 1) % model.entries.length
34
- model.with(selected_index: new_index)
35
- elsif message.move_up?
36
- new_index = (model.selected_index - 1) % model.entries.length
37
- model.with(selected_index: new_index)
38
- elsif message.jump_to_first?
39
- model.with(selected_index: 0)
40
- elsif message.jump_to_last?
41
- model.with(selected_index: model.entries.length - 1)
42
- elsif message.enter?
43
- selected_entry = model.entries[model.selected_index]
44
- if selected_entry.directory?
45
- new_dir = File.join(model.current_directory, selected_entry.name)
46
- new_entries = read_entries(new_dir)
47
- model.with(current_directory: new_dir, entries: new_entries, selected_index: 0)
48
- else
49
- nil
50
- end
51
- elsif message.go_back?
52
- parent_dir = File.dirname(model.current_directory)
53
- new_entries = read_entries(parent_dir)
54
- model.with(current_directory: parent_dir, entries: new_entries, selected_index: 0)
55
- elsif message.go_home?
56
- home_dir = Dir.home
57
- new_entries = read_entries(home_dir)
58
- model.with(current_directory: home_dir, entries: new_entries, selected_index: 0)
59
- elsif message.go_root?
60
- root = File.expand_path("/")
61
- new_entries = read_entries(root)
62
- model.with(current_directory: root, entries: new_entries, selected_index: 0)
63
- elsif message.refresh?
64
- new_entries = read_entries(model.current_directory)
65
- model.with(entries: new_entries)
66
- end
67
- }
68
-
69
- ReadEntries = -> (path) {
70
- Dir.children(path).map { |name|
71
- full_path = File.join(path, name)
72
- Entry.new(name:, directory?: File.directory?(full_path))
73
- }.sort_by { |e| [e.directory? ? 0 : 1, e.name.downcase] }
74
- }
75
-
76
- Init = -> {
77
- current_directory = Dir.pwd
78
- entries = ReadEntries.call(current_directory)
79
- Ractor.make_shareable Model.new(current_directory, entries, 0)
80
- }
81
-
82
- private_class_method def self.read_entries(path)
83
- ReadEntries.call(path)
84
- end
85
- end
86
-
87
- # The main FileBrowser delegates to the FileList fragment via Router.
88
- module FileBrowser
89
- include Rooibos::Router
90
-
91
- Command = Rooibos::Command
92
-
93
- # Parent model wraps child fragment
94
- Model = Data.define(:file_list)
95
-
96
- route :file_list, to: FileList
97
-
98
- action go_back: FileList, keys: %i[backspace left h]
99
- action go_home: FileList, key: :~
100
- action go_root: FileList, key: :/
101
- action refresh: FileList, key: :R
102
- action enter: FileList, keys: %i[enter right l]
103
- action move_down: FileList, keys: %i[down j]
104
- action move_up: FileList, keys: %i[up k]
105
- action jump_to_first: FileList, keys: %i[home g]
106
- action jump_to_last: FileList, keys: %i[end G]
107
- action -> { Command.exit }, keys: %i[ctrl_c q]
108
-
109
- View = -> (model, tui) {
110
- FileList::View.call(model.file_list, tui)
111
- }
112
-
113
- Update = from_router
114
-
115
- Init = -> {
116
- file_list = FileList::Init.()
117
- Ractor.make_shareable Model.new(file_list:)
118
- }
119
- end
120
- end
121
-
122
- if __FILE__ == $0
123
- Rooibos.run(Tutorial06::FileBrowser)
124
- end
@@ -1,54 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # README Usage Verification
7
-
8
- Verifies the primary usage example for the Rooibos gem.
9
-
10
- This example exists as a documentation regression test. It ensures that the very first MVU pattern a user sees actually works.
11
-
12
- ## Usage
13
-
14
- <!-- SPDX-SnippetBegin -->
15
- <!--
16
- SPDX-FileCopyrightText: 2026 Kerrick Long
17
- SPDX-License-Identifier: MIT-0
18
- -->
19
- <!-- SYNC:START:./app.rb:mvu -->
20
- ```ruby
21
- Model = Data.define(:text)
22
-
23
- Init = -> do
24
- Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
25
- end
26
-
27
- View = -> (model, tui) do
28
- tui.paragraph(
29
- text: model.text,
30
- alignment: :center,
31
- block: tui.block(
32
- title: "My Ruby TUI App",
33
- borders: [:all],
34
- border_style: { fg: "cyan" }
35
- )
36
- )
37
- end
38
-
39
- Update = -> (msg, model) do
40
- if msg.q? || msg.ctrl_c?
41
- Rooibos::Command.exit
42
- else
43
- model
44
- end
45
- end
46
-
47
- def run
48
- Rooibos.run(VerifyReadmeUsage)
49
- end
50
- ```
51
- <!-- SYNC:END -->
52
- <!-- SPDX-SnippetEnd -->
53
-
54
- [![verify_readme_usage](../../doc/images/verify_readme_usage.png)](../../README.md#usage)