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.
- checksums.yaml +4 -4
- data/LICENSES/BSD-2-Clause.txt +9 -0
- data/REUSE.toml +5 -0
- data/exe/.gitkeep +0 -0
- data/lib/rooibos/cli/commands/new.rb +24 -0
- data/lib/rooibos/command/batch.rb +10 -0
- data/lib/rooibos/command/bubble.rb +34 -0
- data/lib/rooibos/command/custom.rb +3 -2
- data/lib/rooibos/command/deliver.rb +50 -0
- data/lib/rooibos/command/http.rb +1 -1
- data/lib/rooibos/command/lifecycle.rb +3 -1
- data/lib/rooibos/command/outlet.rb +19 -9
- data/lib/rooibos/command.rb +107 -3
- data/lib/rooibos/configuration.rb +29 -0
- data/lib/rooibos/message/bubbled.rb +29 -0
- data/lib/rooibos/message.rb +24 -6
- data/lib/rooibos/router/action.rb +36 -0
- data/lib/rooibos/router/flow/dispatch.rb +39 -0
- data/lib/rooibos/router/flow/inward.rb +41 -0
- data/lib/rooibos/router/flow/outward.rb +44 -0
- data/lib/rooibos/router/guard.rb +56 -0
- data/lib/rooibos/router/predicate.rb +65 -0
- data/lib/rooibos/router/registry/actions.rb +41 -0
- data/lib/rooibos/router/registry/forwards.rb +58 -0
- data/lib/rooibos/router/registry/observes.rb +57 -0
- data/lib/rooibos/router/registry/otherwises.rb +29 -0
- data/lib/rooibos/router/registry/receives.rb +57 -0
- data/lib/rooibos/router/registry/routes.rb +59 -0
- data/lib/rooibos/router/registry.rb +26 -0
- data/lib/rooibos/router/route.rb +42 -0
- data/lib/rooibos/router/router_update.rb +53 -0
- data/lib/rooibos/router/rule/forward.rb +39 -0
- data/lib/rooibos/router/rule/observe.rb +22 -0
- data/lib/rooibos/router/rule/otherwise.rb +26 -0
- data/lib/rooibos/router/rule/receive.rb +22 -0
- data/lib/rooibos/router/rule.rb +40 -0
- data/lib/rooibos/router.rb +424 -438
- data/lib/rooibos/runtime.rb +37 -52
- data/lib/rooibos/test_helper.rb +22 -0
- data/lib/rooibos/transition.rb +92 -0
- data/lib/rooibos/version.rb +1 -1
- data/lib/rooibos.rb +2 -57
- data/sig/rooibos/cli.rbs +1 -0
- data/sig/rooibos/command.rbs +44 -0
- data/sig/rooibos/configuration.rbs +20 -0
- data/sig/rooibos/message.rbs +12 -0
- data/sig/rooibos/router/action.rbs +33 -0
- data/sig/rooibos/router/actions.rbs +27 -0
- data/sig/rooibos/router/flow/dispatch.rbs +29 -0
- data/sig/rooibos/router/flow/inward.rbs +37 -0
- data/sig/rooibos/router/flow/outward.rbs +36 -0
- data/sig/rooibos/router/forward.rbs +35 -0
- data/sig/rooibos/router/forwards.rbs +34 -0
- data/sig/rooibos/router/guard.rbs +21 -0
- data/sig/rooibos/router/observe.rbs +20 -0
- data/sig/rooibos/router/observes.rbs +38 -0
- data/sig/rooibos/router/otherwise.rbs +22 -0
- data/sig/rooibos/router/otherwises.rbs +20 -0
- data/sig/rooibos/router/predicate.rbs +51 -0
- data/sig/rooibos/router/receive.rbs +20 -0
- data/sig/rooibos/router/receives.rbs +38 -0
- data/sig/rooibos/router/registry.rbs +24 -0
- data/sig/rooibos/router/route.rbs +46 -0
- data/sig/rooibos/router/router_update.rbs +33 -0
- data/sig/rooibos/router/routes.rbs +41 -0
- data/sig/rooibos/router/rule.rbs +36 -0
- data/sig/rooibos/router.rbs +216 -161
- data/sig/rooibos/runtime.rbs +0 -1
- data/sig/rooibos/test_helper.rbs +6 -0
- data/sig/rooibos/transition.rbs +33 -0
- data/sig/rooibos.rbs +0 -10
- metadata +144 -198
- data/.builds/ruby-3.2.yml +0 -55
- data/.builds/ruby-3.3.yml +0 -55
- data/.builds/ruby-3.4.yml +0 -55
- data/.builds/ruby-4.0.0.yml +0 -55
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -8
- data/AGENTS.md +0 -108
- data/CHANGELOG.md +0 -308
- data/README.md +0 -183
- data/README.rdoc +0 -374
- data/Rakefile +0 -16
- data/Steepfile +0 -13
- data/doc/best_practices/forms_and_validation.md +0 -20
- data/doc/best_practices/http_workflows.md +0 -20
- data/doc/best_practices/index.md +0 -26
- data/doc/best_practices/lists_and_tables.md +0 -20
- data/doc/best_practices/modal_dialogs.md +0 -20
- data/doc/best_practices/no_stateful_widgets.md +0 -184
- data/doc/best_practices/orchestration.md +0 -20
- data/doc/best_practices/streaming_data.md +0 -20
- data/doc/contributors/design/commands_and_outlets.md +0 -214
- data/doc/contributors/design/mvu_tea_implementations_research.md +0 -373
- data/doc/contributors/documentation_plan.md +0 -616
- data/doc/contributors/documentation_stub_audit.md +0 -112
- data/doc/contributors/documentation_style.md +0 -275
- data/doc/contributors/e2e_pty.md +0 -168
- data/doc/contributors/maybe_stateful_router.md +0 -56
- data/doc/contributors/specs/earliest_tutorial_steps_per_story.md +0 -70
- data/doc/contributors/specs/file_browser.md +0 -789
- data/doc/contributors/specs/file_browser_stories.md +0 -784
- data/doc/contributors/specs/tutorials_to_stories.rb +0 -167
- data/doc/contributors/todo/scrollbar.md +0 -118
- data/doc/contributors/tutorial_old/01_project_setup.md +0 -20
- data/doc/contributors/tutorial_old/02_hello_world.md +0 -24
- data/doc/contributors/tutorial_old/03_adding_state.md +0 -26
- data/doc/contributors/tutorial_old/06_organizing_your_code.md +0 -20
- data/doc/contributors/tutorial_old/07_your_first_command.md +0 -21
- data/doc/contributors/tutorial_old/08_the_preview_pane.md +0 -20
- data/doc/contributors/tutorial_old/09_loading_states.md +0 -20
- data/doc/contributors/tutorial_old/10_testing_your_app.md +0 -20
- data/doc/contributors/tutorial_old/11_polish_and_refine.md +0 -20
- data/doc/contributors/tutorial_old/12_going_further.md +0 -20
- data/doc/contributors/tutorial_old/index.md +0 -20
- data/doc/custom.css +0 -22
- data/doc/essentials/commands.md +0 -20
- data/doc/essentials/index.md +0 -31
- data/doc/essentials/messages.md +0 -21
- data/doc/essentials/models.md +0 -21
- data/doc/essentials/shortcuts.md +0 -19
- data/doc/essentials/the_elm_architecture.md +0 -24
- data/doc/essentials/the_runtime.md +0 -21
- data/doc/essentials/update_functions.md +0 -20
- data/doc/essentials/views.md +0 -22
- data/doc/getting_started/for_go_developers.md +0 -16
- data/doc/getting_started/for_python_developers.md +0 -16
- data/doc/getting_started/for_rails_developers.md +0 -17
- data/doc/getting_started/for_ratatui_ruby_developers.md +0 -17
- data/doc/getting_started/for_react_developers.md +0 -17
- data/doc/getting_started/index.md +0 -52
- data/doc/getting_started/install.md +0 -20
- data/doc/getting_started/quickstart.md +0 -20
- data/doc/getting_started/ruby_primer.md +0 -19
- data/doc/getting_started/why_rooibos.md +0 -20
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_cmd_exec.png +0 -0
- data/doc/index.md +0 -93
- data/doc/scaling_up/async_patterns.md +0 -20
- data/doc/scaling_up/command_composition.md +0 -20
- data/doc/scaling_up/custom_commands.md +0 -21
- data/doc/scaling_up/fractal_architecture.md +0 -20
- data/doc/scaling_up/index.md +0 -30
- data/doc/scaling_up/message_routing.md +0 -20
- data/doc/scaling_up/ractor_safety.md +0 -20
- data/doc/scaling_up/testing.md +0 -21
- data/doc/troubleshooting/common_errors.md +0 -20
- data/doc/troubleshooting/debugging.md +0 -21
- data/doc/troubleshooting/index.md +0 -23
- data/doc/troubleshooting/performance.md +0 -20
- data/doc/tutorial/01_project_setup.md +0 -44
- data/doc/tutorial/02_hello_world.md +0 -45
- data/doc/tutorial/03_static_file_list.md +0 -44
- data/doc/tutorial/04_arrow_navigation.md +0 -47
- data/doc/tutorial/05_real_files.md +0 -45
- data/doc/tutorial/06_safe_refactoring.md +0 -21
- data/doc/tutorial/07_red_first_tdd.md +0 -26
- data/doc/tutorial/08_file_metadata.md +0 -42
- data/doc/tutorial/09_text_preview.md +0 -44
- data/doc/tutorial/10_directory_tree.md +0 -42
- data/doc/tutorial/11_pane_focus.md +0 -40
- data/doc/tutorial/12_sorting.md +0 -41
- data/doc/tutorial/13_filtering.md +0 -43
- data/doc/tutorial/14_toggle_hidden.md +0 -41
- data/doc/tutorial/15_text_input_widget.md +0 -43
- data/doc/tutorial/16_rename_files.md +0 -42
- data/doc/tutorial/17_confirmation_dialogs.md +0 -43
- data/doc/tutorial/18_progress_indicators.md +0 -43
- data/doc/tutorial/19_atomic_operations.md +0 -42
- data/doc/tutorial/20_external_editor.md +0 -42
- data/doc/tutorial/21_modal_overlays.md +0 -41
- data/doc/tutorial/22_error_handling.md +0 -43
- data/doc/tutorial/23_terminal_capabilities.md +0 -53
- data/doc/tutorial/24_mouse_events.md +0 -43
- data/doc/tutorial/25_resize_events.md +0 -43
- data/doc/tutorial/26_loading_states.md +0 -42
- data/doc/tutorial/27_performance.md +0 -43
- data/doc/tutorial/28_color_schemes.md +0 -47
- data/doc/tutorial/29_configuration.md +0 -124
- data/doc/tutorial/30_going_further.md +0 -17
- data/doc/tutorial/index.md +0 -17
- data/examples/app_fractal_dashboard/README.md +0 -60
- data/examples/app_fractal_dashboard/app.rb +0 -63
- data/examples/app_fractal_dashboard/dashboard/base.rb +0 -73
- data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +0 -86
- data/examples/app_fractal_dashboard/dashboard/update_manual.rb +0 -87
- data/examples/app_fractal_dashboard/dashboard/update_router.rb +0 -43
- data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +0 -81
- data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +0 -82
- data/examples/app_fractal_dashboard/fragments/custom_shell_output.rb +0 -90
- data/examples/app_fractal_dashboard/fragments/disk_usage.rb +0 -47
- data/examples/app_fractal_dashboard/fragments/network_panel.rb +0 -45
- data/examples/app_fractal_dashboard/fragments/ping.rb +0 -47
- data/examples/app_fractal_dashboard/fragments/stats_panel.rb +0 -45
- data/examples/app_fractal_dashboard/fragments/system_info.rb +0 -47
- data/examples/app_fractal_dashboard/fragments/uptime.rb +0 -47
- data/examples/tutorial/01/app.rb +0 -50
- data/examples/tutorial/02/app.rb +0 -64
- data/examples/tutorial/03/app.rb +0 -91
- data/examples/tutorial/06_safe_refactoring/app.rb +0 -124
- data/examples/verify_readme_usage/README.md +0 -54
- data/examples/verify_readme_usage/app.rb +0 -47
- data/examples/verify_website_first_app/app.rb +0 -85
- data/examples/verify_website_hello_mvu/app.rb +0 -31
- data/examples/widget_command_system/README.md +0 -70
- data/examples/widget_command_system/app.rb +0 -134
- data/generate_tutorial_stubs.rb +0 -126
- data/mise.toml +0 -8
- data/rbs_collection.lock.yaml +0 -108
- data/rbs_collection.yaml +0 -15
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/install.rake +0 -29
- data/tasks/resources/build.yml.erb +0 -55
- data/tasks/resources/index.html.erb +0 -44
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/steep.rake +0 -11
- /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
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
-
|
|
10
|
-
require "ratatui_ruby"
|
|
11
|
-
require "rooibos"
|
|
12
|
-
|
|
13
|
-
class VerifyReadmeUsage
|
|
14
|
-
# [SYNC:START:mvu]
|
|
15
|
-
Model = Data.define(:text)
|
|
16
|
-
|
|
17
|
-
Init = -> do
|
|
18
|
-
Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
View = -> (model, tui) do
|
|
22
|
-
tui.paragraph(
|
|
23
|
-
text: model.text,
|
|
24
|
-
alignment: :center,
|
|
25
|
-
block: tui.block(
|
|
26
|
-
title: "My Ruby TUI App",
|
|
27
|
-
borders: [:all],
|
|
28
|
-
border_style: { fg: "cyan" }
|
|
29
|
-
)
|
|
30
|
-
)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
Update = -> (msg, model) do
|
|
34
|
-
if msg.q? || msg.ctrl_c?
|
|
35
|
-
Rooibos::Command.exit
|
|
36
|
-
else
|
|
37
|
-
model
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def run
|
|
42
|
-
Rooibos.run(VerifyReadmeUsage)
|
|
43
|
-
end
|
|
44
|
-
# [SYNC:END:mvu]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
VerifyReadmeUsage.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -1,85 +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
|
-
|
|
10
|
-
module FileBrowser
|
|
11
|
-
# Model: What state does your app need?
|
|
12
|
-
Model = Data.define(:path, :entries, :selected, :error)
|
|
13
|
-
|
|
14
|
-
Init = -> {
|
|
15
|
-
path = Dir.pwd
|
|
16
|
-
entries = Entries[path]
|
|
17
|
-
Ractor.make_shareable( # Ensures thread safety
|
|
18
|
-
Model.new(path:, entries:, selected: entries.first, error: nil))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
View = -> (model, tui) {
|
|
22
|
-
tui.block(
|
|
23
|
-
titles: [
|
|
24
|
-
model.error || model.path,
|
|
25
|
-
{ content: KEYS, position: :bottom, alignment: :right },
|
|
26
|
-
],
|
|
27
|
-
borders: [:all],
|
|
28
|
-
border_style: if model.error then tui.style(fg: :red) else nil end,
|
|
29
|
-
children: [
|
|
30
|
-
tui.list(items: model.entries.map(&ListItem[model, tui]),
|
|
31
|
-
selected_index: model.entries.index(model.selected),
|
|
32
|
-
highlight_symbol: "",
|
|
33
|
-
highlight_style: tui.style(modifiers: [:reversed])),
|
|
34
|
-
]
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
Update = -> (message, model) {
|
|
39
|
-
return model.with(error: ERROR) if message.error?
|
|
40
|
-
model = model.with(error: nil) if model.error && message.key?
|
|
41
|
-
|
|
42
|
-
if message.ctrl_c? || message.q? then Rooibos::Command.exit
|
|
43
|
-
elsif message.home? || message.g? then model.with(selected: model.entries.first)
|
|
44
|
-
elsif message.end? || message.G? then model.with(selected: model.entries.last)
|
|
45
|
-
elsif message.up_arrow? || message.k? then Select[:-, model]
|
|
46
|
-
elsif message.down_arrow? || message.j? then Select[:+, model]
|
|
47
|
-
elsif message.enter? then Open[model]
|
|
48
|
-
elsif message.escape? then Navigate[File.dirname(model.path), model]
|
|
49
|
-
end
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
KEYS = "↑/↓/Home/End: Select | Enter: Open | Esc: Navigate Up | q: Quit"
|
|
53
|
-
ERROR = "Sorry, opening the selected file failed."
|
|
54
|
-
|
|
55
|
-
ListItem = -> (model, tui) {
|
|
56
|
-
-> (name) {
|
|
57
|
-
modifiers = name.start_with?(".") ? [:dim] : []
|
|
58
|
-
fg = :blue if name.end_with?("/")
|
|
59
|
-
tui.list_item(content: name, style: tui.style(fg:, modifiers:))
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
Select = -> (operator, model) {
|
|
64
|
-
new_index = model.entries.index(model.selected).public_send(operator, 1)
|
|
65
|
-
model.with(selected: model.entries[new_index.clamp(0, model.entries.length - 1)])
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
Open = -> (model) {
|
|
69
|
-
full = File.join(model.path, model.selected.delete_suffix("/"))
|
|
70
|
-
model.selected.end_with?("/") ? Navigate[full, model] : Rooibos::Command.open(full)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
Navigate = -> (path, model) {
|
|
74
|
-
entries = Entries[path]
|
|
75
|
-
model.with(path:, entries:, selected: entries.first, error: nil)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
Entries = -> (path) {
|
|
79
|
-
Dir.children(path).map { |name|
|
|
80
|
-
File.directory?(File.join(path, name)) ? "#{name}/" : name
|
|
81
|
-
}.sort_by { |name| [name.end_with?("/") ? 0 : 1, name.downcase] }
|
|
82
|
-
}
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
Rooibos.run(FileBrowser) if __FILE__ == $0
|
|
@@ -1,31 +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
|
-
|
|
10
|
-
module Counter
|
|
11
|
-
# Init: How do you create the initial model?
|
|
12
|
-
Init = -> { 0 }
|
|
13
|
-
|
|
14
|
-
# View: What does the user see?
|
|
15
|
-
View = -> (model, tui) { tui.paragraph(text: <<~END) }
|
|
16
|
-
Current count: #{model}.
|
|
17
|
-
Press any key to increment.
|
|
18
|
-
Press Ctrl+C to quit.
|
|
19
|
-
END
|
|
20
|
-
|
|
21
|
-
# Update: What happens when things change?
|
|
22
|
-
Update = -> (message, model) {
|
|
23
|
-
if message.ctrl_c?
|
|
24
|
-
Rooibos::Command.exit
|
|
25
|
-
elsif message.key?
|
|
26
|
-
model + 1
|
|
27
|
-
end
|
|
28
|
-
}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
Rooibos.run(Counter) if __FILE__ == $0
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
# Cmd.exec Example
|
|
6
|
-
|
|
7
|
-
Demonstrates running shell commands using `Cmd.exec`.
|
|
8
|
-
|
|
9
|
-
Commands in TEA produce **messages**, not callbacks. When a command completes, the runtime sends a tagged tuple to your `update` function. Pattern match on the tag to handle success and failure.
|
|
10
|
-
|
|
11
|
-
## Key Concepts
|
|
12
|
-
|
|
13
|
-
- **Message Tags:** `Cmd.exec(command, tag)` produces `[tag, {stdout:, stderr:, status:}]`.
|
|
14
|
-
- **Non-Blocking:** Commands run in background threads. The UI remains responsive.
|
|
15
|
-
- **Success Handling:** Match on `status: 0` to handle successful execution.
|
|
16
|
-
- **Error Handling:** Match on non-zero status to handle failures.
|
|
17
|
-
- **Ractor-Safe:** No callbacks means no Proc captures. Messages are shareable.
|
|
18
|
-
|
|
19
|
-
## Hotkeys
|
|
20
|
-
|
|
21
|
-
- `d`: Run `ls -la` (directory listing)
|
|
22
|
-
- `u`: Run `uname -a` (system info)
|
|
23
|
-
- `f`: Run a command that fails (demonstrates error handling)
|
|
24
|
-
- `q`: **Quit**
|
|
25
|
-
|
|
26
|
-
## Usage
|
|
27
|
-
|
|
28
|
-
<!-- SPDX-SnippetBegin -->
|
|
29
|
-
<!--
|
|
30
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
31
|
-
SPDX-License-Identifier: MIT-0
|
|
32
|
-
-->
|
|
33
|
-
```bash
|
|
34
|
-
ruby examples/widget_cmd_exec/app.rb
|
|
35
|
-
```
|
|
36
|
-
<!-- SPDX-SnippetEnd -->
|
|
37
|
-
|
|
38
|
-
## How It Works
|
|
39
|
-
|
|
40
|
-
The update function handles both key presses and command results:
|
|
41
|
-
|
|
42
|
-
<!-- SPDX-SnippetBegin -->
|
|
43
|
-
<!--
|
|
44
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
45
|
-
SPDX-License-Identifier: MIT-0
|
|
46
|
-
-->
|
|
47
|
-
```ruby
|
|
48
|
-
UPDATE = -> (msg, model) do
|
|
49
|
-
case msg
|
|
50
|
-
# Handle command results
|
|
51
|
-
in [:got_output, {stdout:, status: 0}]
|
|
52
|
-
[model.with(result: stdout.strip, loading: false), nil]
|
|
53
|
-
in [:got_output, {stderr:, status:}]
|
|
54
|
-
[model.with(result: "Error (exit #{status}): #{stderr.strip}", loading: false), nil]
|
|
55
|
-
|
|
56
|
-
# Handle key presses
|
|
57
|
-
in _ if msg.d?
|
|
58
|
-
[model.with(loading: true), Cmd.exec("ls -la", :got_output)]
|
|
59
|
-
else
|
|
60
|
-
model
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
```
|
|
64
|
-
<!-- SPDX-SnippetEnd -->
|
|
65
|
-
|
|
66
|
-
All logic stays in `update`. The command just runs and produces a message.
|
|
67
|
-
|
|
68
|
-
[Read the source code →](app.rb)
|
|
69
|
-
|
|
70
|
-
[](app.rb)
|
|
@@ -1,134 +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
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
-
|
|
10
|
-
require "ratatui_ruby"
|
|
11
|
-
require "rooibos"
|
|
12
|
-
|
|
13
|
-
# Demonstrates the Command.execute command for running shell commands.
|
|
14
|
-
#
|
|
15
|
-
# This example shows how to execute shell commands and handle both success
|
|
16
|
-
# and failure cases using pattern matching in update. The split layout shows
|
|
17
|
-
# command output in the main area with controls at the bottom.
|
|
18
|
-
#
|
|
19
|
-
# === Examples
|
|
20
|
-
#
|
|
21
|
-
# Run the demo from the terminal:
|
|
22
|
-
#
|
|
23
|
-
# ruby examples/widget_cmd_exec/app.rb
|
|
24
|
-
#
|
|
25
|
-
# rdoc-image:/doc/images/widget_cmd_exec.png
|
|
26
|
-
class WidgetCommandSystem
|
|
27
|
-
Model = Data.define(:result, :loading, :last_command)
|
|
28
|
-
Init = -> {
|
|
29
|
-
Model.new(
|
|
30
|
-
result: "Press a key to run a command...",
|
|
31
|
-
loading: false,
|
|
32
|
-
last_command: nil
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
View = -> (model, tui) do
|
|
37
|
-
hotkey_style = tui.style(modifiers: [:bold, :underlined])
|
|
38
|
-
dim_style = tui.style(fg: :dark_gray)
|
|
39
|
-
|
|
40
|
-
# Styles
|
|
41
|
-
border_color = if model.loading
|
|
42
|
-
"yellow"
|
|
43
|
-
elsif model.result.start_with?("Error")
|
|
44
|
-
"red"
|
|
45
|
-
else
|
|
46
|
-
"cyan"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
title = model.last_command ? "Output: #{model.last_command}" : "Command.execute Demo"
|
|
50
|
-
content_text = model.loading ? "Running command..." : model.result
|
|
51
|
-
|
|
52
|
-
# 1. Main Output Widget
|
|
53
|
-
output_widget = tui.paragraph(
|
|
54
|
-
text: content_text,
|
|
55
|
-
block: tui.block(
|
|
56
|
-
title:,
|
|
57
|
-
borders: [:all],
|
|
58
|
-
border_style: { fg: border_color },
|
|
59
|
-
padding: 1
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# 2. Control Panel Widget
|
|
64
|
-
control_widget = tui.paragraph(
|
|
65
|
-
text: [
|
|
66
|
-
tui.text_line(spans: [
|
|
67
|
-
tui.text_span(content: "d", style: hotkey_style),
|
|
68
|
-
tui.text_span(content: ": Directory listing (ls -la) "),
|
|
69
|
-
tui.text_span(content: "u", style: hotkey_style),
|
|
70
|
-
tui.text_span(content: ": System info (uname -a)"),
|
|
71
|
-
]),
|
|
72
|
-
tui.text_line(spans: [
|
|
73
|
-
tui.text_span(content: "f", style: hotkey_style),
|
|
74
|
-
tui.text_span(content: ": Force failure "),
|
|
75
|
-
tui.text_span(content: "s", style: hotkey_style),
|
|
76
|
-
tui.text_span(content: ": Sleep (3s) "),
|
|
77
|
-
tui.text_span(content: "q", style: hotkey_style),
|
|
78
|
-
tui.text_span(content: ": Quit"),
|
|
79
|
-
]),
|
|
80
|
-
],
|
|
81
|
-
block: tui.block(
|
|
82
|
-
title: "Controls",
|
|
83
|
-
borders: [:all],
|
|
84
|
-
border_style: dim_style
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
# Return the Root Layout Widget (Blueprint)
|
|
89
|
-
tui.layout(
|
|
90
|
-
direction: :vertical,
|
|
91
|
-
constraints: [
|
|
92
|
-
tui.constraint_fill(1),
|
|
93
|
-
tui.constraint_length(6),
|
|
94
|
-
],
|
|
95
|
-
children: [
|
|
96
|
-
output_widget,
|
|
97
|
-
control_widget,
|
|
98
|
-
]
|
|
99
|
-
)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
Update = -> (message, model) do
|
|
103
|
-
case message
|
|
104
|
-
# Handle command results (hash-based pattern matching)
|
|
105
|
-
in { type: :system, envelope: :got_output, stdout:, status: 0 }
|
|
106
|
-
[model.with(result: stdout.strip.freeze, loading: false), nil]
|
|
107
|
-
in { type: :system, envelope: :got_output, stderr:, status: }
|
|
108
|
-
[model.with(result: "Error (exit #{status}): #{stderr.strip}".freeze, loading: false), nil]
|
|
109
|
-
|
|
110
|
-
# Handle key presses
|
|
111
|
-
in _ if message.q? || message.ctrl_c?
|
|
112
|
-
Rooibos::Command.exit
|
|
113
|
-
in _ if message.d?
|
|
114
|
-
[model.with(loading: true, last_command: "ls -la"), Rooibos::Command.system("ls -la", :got_output)]
|
|
115
|
-
in _ if message.u?
|
|
116
|
-
[model.with(loading: true, last_command: "uname -a"), Rooibos::Command.system("uname -a", :got_output)]
|
|
117
|
-
in _ if message.s?
|
|
118
|
-
cmd = "sleep 3 && echo 'Slept for 3s'"
|
|
119
|
-
[model.with(loading: true, last_command: cmd.freeze), Rooibos::Command.system(cmd, :got_output)]
|
|
120
|
-
in _ if message.f?
|
|
121
|
-
# Intentional failure to demonstrate error handling
|
|
122
|
-
cmd = "ls /nonexistent_path_12345"
|
|
123
|
-
[model.with(loading: true, last_command: cmd.freeze), Rooibos::Command.system(cmd, :got_output)]
|
|
124
|
-
else
|
|
125
|
-
model
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def run
|
|
130
|
-
Rooibos.run(WidgetCommandSystem)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
WidgetCommandSystem.new.run if __FILE__ == $PROGRAM_NAME
|
data/generate_tutorial_stubs.rb
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "fileutils"
|
|
5
|
-
|
|
6
|
-
# Parse tutorial structure from documentation_plan.md
|
|
7
|
-
TUTORIAL_FILES = {
|
|
8
|
-
"index.md" => { title: "Tutorial: Build a File Browser", story: nil },
|
|
9
|
-
"01_project_setup.md" => { title: "Project Setup", story: "-4" },
|
|
10
|
-
"02_hello_world.md" => { title: "Hello World", story: "-3" },
|
|
11
|
-
"03_static_file_list.md" => { title: "Static File List", story: "-2" },
|
|
12
|
-
"04_arrow_navigation.md" => { title: "Arrow Navigation", story: "-1" },
|
|
13
|
-
"05_real_files.md" => { title: "Real Files", story: "0" },
|
|
14
|
-
"06_safe_refactoring.md" => { title: "Safe Refactoring", story: "4a" },
|
|
15
|
-
"07_red_first_tdd.md" => { title: "Red-First TDD", story: "4b" },
|
|
16
|
-
"08_file_metadata.md" => { title: "File Metadata", story: "5" },
|
|
17
|
-
"09_text_preview.md" => { title: "Text Preview", story: "6" },
|
|
18
|
-
"10_directory_tree.md" => { title: "Directory Tree", story: "7" },
|
|
19
|
-
"11_pane_focus.md" => { title: "Pane Focus", story: "8" },
|
|
20
|
-
"12_sorting.md" => { title: "Sorting", story: "9" },
|
|
21
|
-
"13_filtering.md" => { title: "Filtering", story: "10" },
|
|
22
|
-
"14_toggle_hidden.md" => { title: "Toggle Hidden Files", story: "11" },
|
|
23
|
-
"15_text_input_widget.md" => { title: "Text Input Widget", story: "12" },
|
|
24
|
-
"16_rename_files.md" => { title: "Rename Files", story: "13" },
|
|
25
|
-
"17_confirmation_dialogs.md" => { title: "Confirmation Dialogs", story: "14" },
|
|
26
|
-
"18_progress_indicators.md" => { title: "Progress Indicators", story: "15" },
|
|
27
|
-
"19_atomic_operations.md" => { title: "Atomic Operations", story: "16" },
|
|
28
|
-
"20_external_editor.md" => { title: "External Editor", story: "17" },
|
|
29
|
-
"21_modal_overlays.md" => { title: "Modal Overlays", story: "18" },
|
|
30
|
-
"22_error_handling.md" => { title: "Error Handling", story: "19" },
|
|
31
|
-
"23_terminal_capabilities.md" => { title: "Terminal Capabilities", story: "23" },
|
|
32
|
-
"24_mouse_events.md" => { title: "Mouse Events", story: "20" },
|
|
33
|
-
"25_resize_events.md" => { title: "Resize Events", story: "21" },
|
|
34
|
-
"26_loading_states.md" => { title: "Loading States", story: "22" },
|
|
35
|
-
"27_performance.md" => { title: "Performance", story: "24" },
|
|
36
|
-
"28_color_schemes.md" => { title: "Color Schemes", story: "26" },
|
|
37
|
-
"29_configuration.md" => { title: "Configuration", story: "27" },
|
|
38
|
-
"30_going_further.md" => { title: "Going Further", story: nil },
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Parse stories from file_browser_stories.md
|
|
42
|
-
stories_content = File.read("doc/contributors/specs/file_browser_stories.md")
|
|
43
|
-
|
|
44
|
-
# Extract each story
|
|
45
|
-
stories = {}
|
|
46
|
-
current_story = nil
|
|
47
|
-
current_content = []
|
|
48
|
-
|
|
49
|
-
stories_content.each_line do |line|
|
|
50
|
-
if line =~ /^## Story (-?\d+[ab]?): (.+)$/
|
|
51
|
-
# Save previous story
|
|
52
|
-
if current_story
|
|
53
|
-
stories[current_story] = current_content.join
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
current_story = $1
|
|
57
|
-
current_content = [line]
|
|
58
|
-
elsif current_story
|
|
59
|
-
current_content << line
|
|
60
|
-
|
|
61
|
-
# Stop at the next story or end of stories section
|
|
62
|
-
if line =~ /^---$/ && current_content.size > 5
|
|
63
|
-
# Check if next section is another story or implementation notes
|
|
64
|
-
peek_ahead = stories_content.lines[stories_content.lines.index(line) + 1]
|
|
65
|
-
if peek_ahead && !peek_ahead.start_with?("## Story")
|
|
66
|
-
stories[current_story] = current_content.join
|
|
67
|
-
break if peek_ahead.start_with?("## Implementation Notes")
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Save last story
|
|
74
|
-
stories[current_story] = current_content.join if current_story
|
|
75
|
-
|
|
76
|
-
# Create tutorial directory
|
|
77
|
-
FileUtils.mkdir_p("doc/tutorial")
|
|
78
|
-
|
|
79
|
-
# Generate stub files
|
|
80
|
-
TUTORIAL_FILES.each do |filename, meta|
|
|
81
|
-
filepath = "doc/tutorial/#{filename}"
|
|
82
|
-
|
|
83
|
-
# Determine previous/next files
|
|
84
|
-
files_list = TUTORIAL_FILES.keys
|
|
85
|
-
current_index = files_list.index(filename)
|
|
86
|
-
prev_file = (current_index > 0) ? files_list[current_index - 1] : nil
|
|
87
|
-
next_file = (current_index < files_list.size - 1) ? files_list[current_index + 1] : nil
|
|
88
|
-
|
|
89
|
-
prev_title = prev_file ? TUTORIAL_FILES[prev_file][:title] : nil
|
|
90
|
-
next_title = next_file ? TUTORIAL_FILES[next_file][:title] : nil
|
|
91
|
-
|
|
92
|
-
# Get story content if applicable
|
|
93
|
-
story_section = ""
|
|
94
|
-
if meta[:story]
|
|
95
|
-
story_key = meta[:story]
|
|
96
|
-
if stories[story_key]
|
|
97
|
-
story_section = "\n## User Stories\n\n#{stories[story_key]}\n"
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Generate stub content
|
|
102
|
-
content = <<~MARKDOWN
|
|
103
|
-
<!--
|
|
104
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
105
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
106
|
-
-->
|
|
107
|
-
|
|
108
|
-
# #{meta[:title]}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
By the end of this guide, you will:
|
|
112
|
-
|
|
113
|
-
- TODO: Write learning objectives
|
|
114
|
-
|
|
115
|
-
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
116
|
-
#{story_section}
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
#{prev_file ? "[**Previous:** #{prev_title}](./#{prev_file})" : ''} | #{next_file ? "[**Next:** #{next_title}](./#{next_file})" : ''}
|
|
120
|
-
MARKDOWN
|
|
121
|
-
|
|
122
|
-
File.write(filepath, content)
|
|
123
|
-
puts "Created #{filepath}"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
puts "\nDone! Created #{TUTORIAL_FILES.size} tutorial stub files."
|
data/mise.toml
DELETED
data/rbs_collection.lock.yaml
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
path: ".gem_rbs_collection"
|
|
7
|
-
gems:
|
|
8
|
-
- name: base64
|
|
9
|
-
version: 0.3.0
|
|
10
|
-
source:
|
|
11
|
-
type: rubygems
|
|
12
|
-
- name: bigdecimal
|
|
13
|
-
version: '0'
|
|
14
|
-
source:
|
|
15
|
-
type: stdlib
|
|
16
|
-
- name: csv
|
|
17
|
-
version: '0'
|
|
18
|
-
source:
|
|
19
|
-
type: stdlib
|
|
20
|
-
- name: ffi
|
|
21
|
-
version: 1.17.3
|
|
22
|
-
source:
|
|
23
|
-
type: rubygems
|
|
24
|
-
- name: fileutils
|
|
25
|
-
version: '0'
|
|
26
|
-
source:
|
|
27
|
-
type: stdlib
|
|
28
|
-
- name: forwardable
|
|
29
|
-
version: '0'
|
|
30
|
-
source:
|
|
31
|
-
type: stdlib
|
|
32
|
-
- name: io-console
|
|
33
|
-
version: '0'
|
|
34
|
-
source:
|
|
35
|
-
type: stdlib
|
|
36
|
-
- name: json
|
|
37
|
-
version: '0'
|
|
38
|
-
source:
|
|
39
|
-
type: stdlib
|
|
40
|
-
- name: logger
|
|
41
|
-
version: '0'
|
|
42
|
-
source:
|
|
43
|
-
type: stdlib
|
|
44
|
-
- name: minitest
|
|
45
|
-
version: '0'
|
|
46
|
-
source:
|
|
47
|
-
type: stdlib
|
|
48
|
-
- name: monitor
|
|
49
|
-
version: '0'
|
|
50
|
-
source:
|
|
51
|
-
type: stdlib
|
|
52
|
-
- name: mutex_m
|
|
53
|
-
version: 0.3.0
|
|
54
|
-
source:
|
|
55
|
-
type: rubygems
|
|
56
|
-
- name: optparse
|
|
57
|
-
version: '0'
|
|
58
|
-
source:
|
|
59
|
-
type: stdlib
|
|
60
|
-
- name: pp
|
|
61
|
-
version: '0'
|
|
62
|
-
source:
|
|
63
|
-
type: stdlib
|
|
64
|
-
- name: prettyprint
|
|
65
|
-
version: '0'
|
|
66
|
-
source:
|
|
67
|
-
type: stdlib
|
|
68
|
-
- name: prism
|
|
69
|
-
version: 1.8.0
|
|
70
|
-
source:
|
|
71
|
-
type: rubygems
|
|
72
|
-
- name: ratatui_ruby
|
|
73
|
-
version: 1.1.0
|
|
74
|
-
source:
|
|
75
|
-
type: rubygems
|
|
76
|
-
- name: ratatui_ruby-devtools
|
|
77
|
-
version: 0.1.0
|
|
78
|
-
source:
|
|
79
|
-
type: rubygems
|
|
80
|
-
- name: rbs
|
|
81
|
-
version: 3.10.2
|
|
82
|
-
source:
|
|
83
|
-
type: rubygems
|
|
84
|
-
- name: rdoc
|
|
85
|
-
version: '0'
|
|
86
|
-
source:
|
|
87
|
-
type: stdlib
|
|
88
|
-
- name: securerandom
|
|
89
|
-
version: '0'
|
|
90
|
-
source:
|
|
91
|
-
type: stdlib
|
|
92
|
-
- name: stringio
|
|
93
|
-
version: '0'
|
|
94
|
-
source:
|
|
95
|
-
type: stdlib
|
|
96
|
-
- name: strscan
|
|
97
|
-
version: '0'
|
|
98
|
-
source:
|
|
99
|
-
type: stdlib
|
|
100
|
-
- name: tsort
|
|
101
|
-
version: '0'
|
|
102
|
-
source:
|
|
103
|
-
type: stdlib
|
|
104
|
-
- name: uri
|
|
105
|
-
version: '0'
|
|
106
|
-
source:
|
|
107
|
-
type: stdlib
|
|
108
|
-
gemfile_lock_path: Gemfile.lock
|
data/rbs_collection.yaml
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
-
|
|
5
|
-
# RBS collection configuration
|
|
6
|
-
# See: https://github.com/ruby/rbs/blob/master/docs/collection.md
|
|
7
|
-
|
|
8
|
-
sources:
|
|
9
|
-
- type: stdlib
|
|
10
|
-
- type: rubygems
|
|
11
|
-
|
|
12
|
-
path: .gem_rbs_collection
|
|
13
|
-
|
|
14
|
-
gems:
|
|
15
|
-
- name: ratatui_ruby
|