ratatui_ruby-tea 0.3.1 → 0.4.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/AGENTS.md +42 -2
- data/CHANGELOG.md +76 -0
- data/README.md +8 -5
- data/doc/concepts/async_work.md +164 -0
- data/doc/concepts/commands.md +528 -0
- data/doc/concepts/message_processing.md +51 -0
- data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
- data/doc/contributors/WIP/implementation_plan.md +405 -0
- data/doc/contributors/WIP/init_callable_proposal.md +341 -0
- data/doc/contributors/WIP/mvu_tea_implementations_research.md +372 -0
- data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
- data/doc/contributors/WIP/task.md +36 -0
- data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
- data/doc/contributors/design/commands_and_outlets.md +11 -1
- data/doc/contributors/priorities.md +22 -24
- data/examples/app_fractal_dashboard/app.rb +3 -7
- data/examples/app_fractal_dashboard/dashboard/base.rb +15 -16
- data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +8 -8
- data/examples/app_fractal_dashboard/dashboard/update_manual.rb +11 -11
- data/examples/app_fractal_dashboard/dashboard/update_router.rb +4 -4
- data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_input.rb +8 -4
- data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
- data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_output.rb +8 -4
- data/examples/app_fractal_dashboard/{bags → fragments}/disk_usage.rb +13 -10
- data/examples/app_fractal_dashboard/{bags → fragments}/network_panel.rb +12 -12
- data/examples/app_fractal_dashboard/{bags → fragments}/ping.rb +12 -8
- data/examples/app_fractal_dashboard/{bags → fragments}/stats_panel.rb +12 -12
- data/examples/app_fractal_dashboard/{bags → fragments}/system_info.rb +11 -7
- data/examples/app_fractal_dashboard/{bags → fragments}/uptime.rb +11 -7
- data/examples/verify_readme_usage/README.md +7 -4
- data/examples/verify_readme_usage/app.rb +7 -4
- data/lib/ratatui_ruby/tea/command/all.rb +71 -0
- data/lib/ratatui_ruby/tea/command/batch.rb +79 -0
- data/lib/ratatui_ruby/tea/command/custom.rb +1 -1
- data/lib/ratatui_ruby/tea/command/http.rb +194 -0
- data/lib/ratatui_ruby/tea/command/lifecycle.rb +136 -0
- data/lib/ratatui_ruby/tea/command/outlet.rb +59 -27
- data/lib/ratatui_ruby/tea/command/wait.rb +82 -0
- data/lib/ratatui_ruby/tea/command.rb +245 -64
- data/lib/ratatui_ruby/tea/message/all.rb +47 -0
- data/lib/ratatui_ruby/tea/message/http_response.rb +63 -0
- data/lib/ratatui_ruby/tea/message/system/batch.rb +63 -0
- data/lib/ratatui_ruby/tea/message/system/stream.rb +69 -0
- data/lib/ratatui_ruby/tea/message/timer.rb +48 -0
- data/lib/ratatui_ruby/tea/message.rb +40 -0
- data/lib/ratatui_ruby/tea/router.rb +11 -11
- data/lib/ratatui_ruby/tea/runtime.rb +320 -185
- data/lib/ratatui_ruby/tea/shortcuts.rb +2 -2
- data/lib/ratatui_ruby/tea/test_helper.rb +58 -0
- data/lib/ratatui_ruby/tea/version.rb +1 -1
- data/lib/ratatui_ruby/tea.rb +44 -10
- data/rbs_collection.lock.yaml +1 -17
- data/sig/concurrent.rbs +72 -0
- data/sig/ratatui_ruby/tea/command.rbs +141 -37
- data/sig/ratatui_ruby/tea/message.rbs +123 -0
- data/sig/ratatui_ruby/tea/router.rbs +1 -1
- data/sig/ratatui_ruby/tea/runtime.rbs +39 -6
- data/sig/ratatui_ruby/tea/test_helper.rbs +12 -0
- data/sig/ratatui_ruby/tea.rbs +24 -4
- metadata +63 -11
- data/examples/app_fractal_dashboard/bags/custom_shell_modal.rb +0 -73
- data/lib/ratatui_ruby/tea/command/cancellation_token.rb +0 -135
|
@@ -5,26 +5,25 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
-
require_relative "../
|
|
9
|
-
require_relative "../
|
|
10
|
-
require_relative "../
|
|
8
|
+
require_relative "../fragments/stats_panel"
|
|
9
|
+
require_relative "../fragments/network_panel"
|
|
10
|
+
require_relative "../fragments/custom_shell_modal"
|
|
11
11
|
|
|
12
|
-
# Shared Model,
|
|
13
|
-
#
|
|
14
|
-
# This module is extended by the three UPDATE variants to demonstrate
|
|
15
|
-
# the progression from verbose manual routing to declarative DSL.
|
|
12
|
+
# Shared Model, Init, and View for the Dashboard.
|
|
13
|
+
# Each Dashboard variation (Manual, Router, Helpers) provides its own Update.
|
|
16
14
|
module DashboardBase
|
|
17
15
|
Command = RatatuiRuby::Tea::Command
|
|
18
16
|
|
|
19
17
|
Model = Data.define(:stats, :network, :shell_modal)
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
stats
|
|
23
|
-
network
|
|
24
|
-
shell_modal
|
|
25
|
-
|
|
19
|
+
Init = -> do
|
|
20
|
+
stats, = RatatuiRuby::Tea.normalize_init(StatsPanel::Init.())
|
|
21
|
+
network, = RatatuiRuby::Tea.normalize_init(NetworkPanel::Init.())
|
|
22
|
+
shell_modal, = RatatuiRuby::Tea.normalize_init(CustomShellModal::Init.())
|
|
23
|
+
Model.new(stats:, network:, shell_modal:)
|
|
24
|
+
end
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
View = -> (model, tui) do
|
|
28
27
|
modal_active = CustomShellModal.active?(model.shell_modal)
|
|
29
28
|
hotkey, label_style = if modal_active
|
|
30
29
|
[tui.style(fg: :dark_gray), tui.style(fg: :dark_gray)]
|
|
@@ -57,14 +56,14 @@ module DashboardBase
|
|
|
57
56
|
direction: :vertical,
|
|
58
57
|
constraints: [tui.constraint_fill(1), tui.constraint_fill(1), tui.constraint_length(3)],
|
|
59
58
|
children: [
|
|
60
|
-
StatsPanel::
|
|
61
|
-
NetworkPanel::
|
|
59
|
+
StatsPanel::View.call(model.stats, tui, disabled: modal_active),
|
|
60
|
+
NetworkPanel::View.call(model.network, tui, disabled: modal_active),
|
|
62
61
|
controls,
|
|
63
62
|
]
|
|
64
63
|
)
|
|
65
64
|
|
|
66
65
|
# Compose modal overlay if active
|
|
67
|
-
modal_widget = CustomShellModal::
|
|
66
|
+
modal_widget = CustomShellModal::View.call(model.shell_modal, tui)
|
|
68
67
|
if modal_widget
|
|
69
68
|
tui.overlay(layers: [dashboard, modal_widget])
|
|
70
69
|
else
|
|
@@ -17,10 +17,10 @@ module DashboardHelpers
|
|
|
17
17
|
|
|
18
18
|
# Shared with other UPDATE variants
|
|
19
19
|
Model = DashboardBase::Model
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
Init = DashboardBase::Init
|
|
21
|
+
View = DashboardBase::View
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Update = -> (message, model) do
|
|
24
24
|
# Global Force Quit
|
|
25
25
|
return [model, RatatuiRuby::Tea::Command.exit] if message.respond_to?(:ctrl_c?) && message.ctrl_c?
|
|
26
26
|
|
|
@@ -29,25 +29,25 @@ module DashboardHelpers
|
|
|
29
29
|
# modal is active. Only user input (keys/mouse) should be blocked.
|
|
30
30
|
|
|
31
31
|
# Route streaming command output to modal
|
|
32
|
-
if (result = Tea.delegate(message, :shell_output, CustomShellModal::
|
|
32
|
+
if (result = Tea.delegate(message, :shell_output, CustomShellModal::Update, model.shell_modal))
|
|
33
33
|
new_modal, command = result
|
|
34
34
|
return [model.with(shell_modal: new_modal), command]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# Route to child
|
|
38
|
-
if (result = Tea.delegate(message, :stats, StatsPanel::
|
|
37
|
+
# Route to child fragments
|
|
38
|
+
if (result = Tea.delegate(message, :stats, StatsPanel::Update, model.stats))
|
|
39
39
|
new_child, command = result
|
|
40
40
|
return [model.with(stats: new_child), command && Tea.route(command, :stats)]
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
if (result = Tea.delegate(message, :network, NetworkPanel::
|
|
43
|
+
if (result = Tea.delegate(message, :network, NetworkPanel::Update, model.network))
|
|
44
44
|
new_child, command = result
|
|
45
45
|
return [model.with(network: new_child), command && Tea.route(command, :network)]
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Modal intercepts user input (not command results)
|
|
49
49
|
if CustomShellModal.active?(model.shell_modal)
|
|
50
|
-
new_modal, command = CustomShellModal::
|
|
50
|
+
new_modal, command = CustomShellModal::Update.call(message, model.shell_modal)
|
|
51
51
|
return [model.with(shell_modal: new_modal), command]
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -16,10 +16,10 @@ module DashboardManual
|
|
|
16
16
|
|
|
17
17
|
# Shared with other UPDATE variants
|
|
18
18
|
Model = DashboardBase::Model
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
Init = DashboardBase::Init
|
|
20
|
+
View = DashboardBase::View
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Update = -> (message, model) do
|
|
23
23
|
# Global Force Quit
|
|
24
24
|
return [model, RatatuiRuby::Tea::Command.exit] if message.respond_to?(:ctrl_c?) && message.ctrl_c?
|
|
25
25
|
|
|
@@ -29,18 +29,18 @@ module DashboardManual
|
|
|
29
29
|
case message
|
|
30
30
|
# Route command results to panels
|
|
31
31
|
in [:stats, *rest]
|
|
32
|
-
new_panel, command = StatsPanel::
|
|
32
|
+
new_panel, command = StatsPanel::Update.call(rest, model.stats)
|
|
33
33
|
mapped_command = command ? Command.map(command) { |child_result| [:stats, *child_result] } : nil
|
|
34
34
|
return [model.with(stats: new_panel), mapped_command]
|
|
35
35
|
|
|
36
36
|
in [:network, *rest]
|
|
37
|
-
new_panel, command = NetworkPanel::
|
|
37
|
+
new_panel, command = NetworkPanel::Update.call(rest, model.network)
|
|
38
38
|
mapped_command = command ? Command.map(command) { |child_result| [:network, *child_result] } : nil
|
|
39
39
|
return [model.with(network: new_panel), mapped_command]
|
|
40
40
|
|
|
41
41
|
in [:shell_output, *rest]
|
|
42
42
|
# Route streaming command output to modal
|
|
43
|
-
new_modal, command = CustomShellModal::
|
|
43
|
+
new_modal, command = CustomShellModal::Update.call(message, model.shell_modal)
|
|
44
44
|
return [model.with(shell_modal: new_modal), command]
|
|
45
45
|
else
|
|
46
46
|
nil # Fall through to input handling
|
|
@@ -48,7 +48,7 @@ module DashboardManual
|
|
|
48
48
|
|
|
49
49
|
# Modal intercepts user input (not command results)
|
|
50
50
|
if CustomShellModal.active?(model.shell_modal)
|
|
51
|
-
new_modal, command = CustomShellModal::
|
|
51
|
+
new_modal, command = CustomShellModal::Update.call(message, model.shell_modal)
|
|
52
52
|
return [model.with(shell_modal: new_modal), command]
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -61,22 +61,22 @@ module DashboardManual
|
|
|
61
61
|
[model.with(shell_modal: CustomShellModal.open), nil]
|
|
62
62
|
|
|
63
63
|
in _ if message.s?
|
|
64
|
-
command = Command.map(SystemInfo.fetch_command) { |
|
|
64
|
+
command = Command.map(SystemInfo.fetch_command) { |batch| [:stats, batch.envelope, batch] }
|
|
65
65
|
new_stats = model.stats.with(system_info: model.stats.system_info.with(loading: true))
|
|
66
66
|
[model.with(stats: new_stats), command]
|
|
67
67
|
|
|
68
68
|
in _ if message.d?
|
|
69
|
-
command = Command.map(DiskUsage.fetch_command) { |
|
|
69
|
+
command = Command.map(DiskUsage.fetch_command) { |batch| [:stats, batch.envelope, batch] }
|
|
70
70
|
new_stats = model.stats.with(disk_usage: model.stats.disk_usage.with(loading: true))
|
|
71
71
|
[model.with(stats: new_stats), command]
|
|
72
72
|
|
|
73
73
|
in _ if message.p?
|
|
74
|
-
command = Command.map(Ping.fetch_command) { |
|
|
74
|
+
command = Command.map(Ping.fetch_command) { |batch| [:network, batch.envelope, batch] }
|
|
75
75
|
new_network = model.network.with(ping: model.network.ping.with(loading: true))
|
|
76
76
|
[model.with(network: new_network), command]
|
|
77
77
|
|
|
78
78
|
in _ if message.u?
|
|
79
|
-
command = Command.map(Uptime.fetch_command) { |
|
|
79
|
+
command = Command.map(Uptime.fetch_command) { |batch| [:network, batch.envelope, batch] }
|
|
80
80
|
new_network = model.network.with(uptime: model.network.uptime.with(loading: true))
|
|
81
81
|
[model.with(network: new_network), command]
|
|
82
82
|
|
|
@@ -16,10 +16,10 @@ module DashboardRouter
|
|
|
16
16
|
|
|
17
17
|
Command = RatatuiRuby::Tea::Command
|
|
18
18
|
|
|
19
|
-
# Shared with other
|
|
19
|
+
# Shared with other Update variants
|
|
20
20
|
Model = DashboardBase::Model
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Init = DashboardBase::Init
|
|
22
|
+
View = DashboardBase::View
|
|
23
23
|
|
|
24
24
|
route :stats, to: StatsPanel
|
|
25
25
|
route :network, to: NetworkPanel
|
|
@@ -39,5 +39,5 @@ module DashboardRouter
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Update = from_router
|
|
43
43
|
end
|
|
@@ -5,14 +5,18 @@
|
|
|
5
5
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
9
|
+
# Text input fragment for custom shell command modal.
|
|
9
10
|
#
|
|
10
11
|
# Handles text entry. Sets cancelled: or submitted: in model for parent to detect.
|
|
11
12
|
module CustomShellInput
|
|
12
13
|
Model = Data.define(:text, :cancelled, :submitted)
|
|
13
|
-
INITIAL = Ractor.make_shareable(Model.new(text: "", cancelled: false, submitted: false))
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Init = -> do
|
|
16
|
+
Ractor.make_shareable(Model.new(text: "", cancelled: false, submitted: false))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
View = -> (model, tui) do
|
|
16
20
|
content = if model.text.empty?
|
|
17
21
|
tui.paragraph(text: tui.text_span(content: "Type a command...", style: { fg: :dark_gray }))
|
|
18
22
|
else
|
|
@@ -50,7 +54,7 @@ module CustomShellInput
|
|
|
50
54
|
)
|
|
51
55
|
end
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
Update = -> (message, model) do
|
|
54
58
|
case message
|
|
55
59
|
in _ if message.respond_to?(:esc?) && message.esc?
|
|
56
60
|
[model.with(cancelled: true), nil]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
require_relative "custom_shell_input"
|
|
9
|
+
require_relative "custom_shell_output"
|
|
10
|
+
|
|
11
|
+
# Modal overlay for custom shell command execution.
|
|
12
|
+
module CustomShellModal
|
|
13
|
+
Command = RatatuiRuby::Tea::Command
|
|
14
|
+
|
|
15
|
+
Model = Data.define(:mode, :input, :output)
|
|
16
|
+
|
|
17
|
+
Init = -> do
|
|
18
|
+
input, = RatatuiRuby::Tea.normalize_init(CustomShellInput::Init.())
|
|
19
|
+
output, = RatatuiRuby::Tea.normalize_init(CustomShellOutput::Init.())
|
|
20
|
+
Ractor.make_shareable(Model.new(mode: :none, input:, output:))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
View = -> (model, tui) do
|
|
24
|
+
case model.mode
|
|
25
|
+
when :input
|
|
26
|
+
CustomShellInput::View.call(model.input, tui)
|
|
27
|
+
when :output
|
|
28
|
+
CustomShellOutput::View.call(model.output, tui)
|
|
29
|
+
else
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Update = -> (message, model) do
|
|
35
|
+
case [model.mode, message]
|
|
36
|
+
in [:input, _]
|
|
37
|
+
# Delegate first, then check if user wants to close
|
|
38
|
+
new_input, _cmd = CustomShellInput::Update.call(message, model.input)
|
|
39
|
+
|
|
40
|
+
if new_input.cancelled
|
|
41
|
+
[Init.(), nil]
|
|
42
|
+
elsif new_input.submitted
|
|
43
|
+
shell_cmd = new_input.text
|
|
44
|
+
new_output = CustomShellOutput::Init.().with(command: shell_cmd, running: true)
|
|
45
|
+
reset_input, = RatatuiRuby::Tea.normalize_init(CustomShellInput::Init.())
|
|
46
|
+
[
|
|
47
|
+
model.with(mode: :output, input: reset_input, output: new_output),
|
|
48
|
+
Command.system(shell_cmd, :shell_output, stream: true),
|
|
49
|
+
]
|
|
50
|
+
else
|
|
51
|
+
[model.with(input: new_input), nil]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
in [:output, _]
|
|
55
|
+
# Delegate first, then check if user wants to close
|
|
56
|
+
case message
|
|
57
|
+
in RatatuiRuby::Event::Key if message.ctrl_c?
|
|
58
|
+
[Init.(), nil]
|
|
59
|
+
else
|
|
60
|
+
new_output, _cmd = CustomShellOutput::Update.call(message, model.output)
|
|
61
|
+
|
|
62
|
+
if new_output.dismissed
|
|
63
|
+
[Init.(), nil]
|
|
64
|
+
else
|
|
65
|
+
[model.with(output: new_output), nil]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
else
|
|
70
|
+
[model, nil]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.open
|
|
75
|
+
input, = RatatuiRuby::Tea.normalize_init(CustomShellInput::Init.())
|
|
76
|
+
Init.().with(mode: :input, input:)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.active?(model)
|
|
80
|
+
model.mode != :none
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
9
|
+
# Streaming output fragment for custom shell command modal.
|
|
9
10
|
#
|
|
10
11
|
# Displays interleaved stdout/stderr. Border color reflects exit status.
|
|
11
12
|
# Sets dismissed: in model for parent to detect.
|
|
12
13
|
module CustomShellOutput
|
|
13
14
|
Chunk = Data.define(:stream, :text)
|
|
14
15
|
Model = Data.define(:command, :chunks, :running, :exit_status, :dismissed)
|
|
15
|
-
INITIAL = Ractor.make_shareable(Model.new(command: "", chunks: [].freeze, running: false, exit_status: nil, dismissed: false))
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Init = -> do
|
|
18
|
+
Ractor.make_shareable(Model.new(command: "", chunks: [].freeze, running: false, exit_status: nil, dismissed: false))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
View = -> (model, tui) do
|
|
18
22
|
# Build styled spans from chunks
|
|
19
23
|
spans = if model.chunks.empty? && model.running
|
|
20
24
|
[tui.text_span(content: "Running...", style: tui.style(fg: :dark_gray))]
|
|
@@ -56,7 +60,7 @@ module CustomShellOutput
|
|
|
56
60
|
)
|
|
57
61
|
end
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
Update = -> (message, model) do
|
|
60
64
|
case message
|
|
61
65
|
in [:stdout, chunk]
|
|
62
66
|
new_chunks = Ractor.make_shareable([*model.chunks, Chunk.new(stream: :stdout, text: chunk)].freeze)
|
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
8
9
|
# Fetches and displays disk usage via +df -h+.
|
|
9
|
-
# A
|
|
10
|
+
# A fragment for fetching and displaying disk usage.
|
|
10
11
|
module DiskUsage
|
|
11
12
|
Command = RatatuiRuby::Tea::Command
|
|
12
13
|
|
|
13
14
|
Model = Data.define(:output, :loading)
|
|
14
|
-
INITIAL = Model.new(output: "Press 'd' for disk usage", loading: false)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
18
22
|
tui.style(fg: :dark_gray)
|
|
19
23
|
else
|
|
20
24
|
nil
|
|
@@ -26,12 +30,11 @@ module DiskUsage
|
|
|
26
30
|
)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Update = -> (message, model) do
|
|
30
34
|
case message
|
|
31
|
-
in [:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
in [:disk_usage, { stderr:, _status: }]
|
|
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: }]
|
|
35
38
|
[model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
|
|
36
39
|
else
|
|
37
40
|
[model, nil]
|
|
@@ -39,6 +42,6 @@ module DiskUsage
|
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def self.fetch_command
|
|
42
|
-
Command.system("df -h", :disk_usage)
|
|
45
|
+
Command.system("df -h /", :disk_usage)
|
|
43
46
|
end
|
|
44
47
|
end
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
8
9
|
require_relative "ping"
|
|
9
10
|
require_relative "uptime"
|
|
10
11
|
|
|
@@ -12,31 +13,30 @@ require_relative "uptime"
|
|
|
12
13
|
module NetworkPanel
|
|
13
14
|
Model = Data.define(:ping, :uptime)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
ping
|
|
17
|
-
uptime
|
|
18
|
-
|
|
16
|
+
Init = -> do
|
|
17
|
+
ping, = RatatuiRuby::Tea.normalize_init(Ping::Init.())
|
|
18
|
+
uptime, = RatatuiRuby::Tea.normalize_init(Uptime::Init.())
|
|
19
|
+
Model.new(ping:, uptime:)
|
|
20
|
+
end
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
View = -> (model, tui, disabled: false) do
|
|
21
23
|
tui.layout(
|
|
22
24
|
direction: :horizontal,
|
|
23
25
|
constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)],
|
|
24
26
|
children: [
|
|
25
|
-
Ping::
|
|
26
|
-
Uptime::
|
|
27
|
+
Ping::View.call(model.ping, tui, disabled:),
|
|
28
|
+
Uptime::View.call(model.uptime, tui, disabled:),
|
|
27
29
|
]
|
|
28
30
|
)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Update = -> (message, model) do
|
|
32
34
|
case message
|
|
33
35
|
in [:ping, *rest]
|
|
34
|
-
|
|
35
|
-
new_child, command = Ping::UPDATE.call(child_message, model.ping)
|
|
36
|
+
new_child, command = Ping::Update.call(rest, model.ping)
|
|
36
37
|
[model.with(ping: new_child), command]
|
|
37
38
|
in [:uptime, *rest]
|
|
38
|
-
|
|
39
|
-
new_child, command = Uptime::UPDATE.call(child_message, model.uptime)
|
|
39
|
+
new_child, command = Uptime::Update.call(rest, model.uptime)
|
|
40
40
|
[model.with(uptime: new_child), command]
|
|
41
41
|
else
|
|
42
42
|
[model, nil]
|
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
8
9
|
# Pings localhost to check network connectivity.
|
|
9
|
-
# A
|
|
10
|
+
# A fragment for pinging localhost.
|
|
10
11
|
module Ping
|
|
11
12
|
Command = RatatuiRuby::Tea::Command
|
|
12
13
|
|
|
13
14
|
Model = Data.define(:output, :loading)
|
|
14
|
-
INITIAL = Model.new(output: "Press 'p' for ping", loading: false)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
18
22
|
tui.style(fg: :dark_gray)
|
|
19
23
|
else
|
|
20
24
|
nil
|
|
@@ -26,11 +30,11 @@ module Ping
|
|
|
26
30
|
)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Update = -> (message, model) do
|
|
30
34
|
case message
|
|
31
|
-
in [:
|
|
35
|
+
in [{ type: :system, envelope: :ping, status: 0, stdout: }]
|
|
32
36
|
[model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
|
|
33
|
-
in [:ping,
|
|
37
|
+
in [{ type: :system, envelope: :ping, stderr: }]
|
|
34
38
|
[model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
|
|
35
39
|
else
|
|
36
40
|
[model, nil]
|
|
@@ -38,6 +42,6 @@ module Ping
|
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def self.fetch_command
|
|
41
|
-
Command.system("ping -c
|
|
45
|
+
Command.system("ping -c 3 8.8.8.8", :ping)
|
|
42
46
|
end
|
|
43
47
|
end
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
8
9
|
require_relative "system_info"
|
|
9
10
|
require_relative "disk_usage"
|
|
10
11
|
|
|
@@ -12,31 +13,30 @@ require_relative "disk_usage"
|
|
|
12
13
|
module StatsPanel
|
|
13
14
|
Model = Data.define(:system_info, :disk_usage)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
system_info
|
|
17
|
-
disk_usage
|
|
18
|
-
|
|
16
|
+
Init = -> do
|
|
17
|
+
system_info, = RatatuiRuby::Tea.normalize_init(SystemInfo::Init.())
|
|
18
|
+
disk_usage, = RatatuiRuby::Tea.normalize_init(DiskUsage::Init.())
|
|
19
|
+
Model.new(system_info:, disk_usage:)
|
|
20
|
+
end
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
View = -> (model, tui, disabled: false) do
|
|
21
23
|
tui.layout(
|
|
22
24
|
direction: :horizontal,
|
|
23
25
|
constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)],
|
|
24
26
|
children: [
|
|
25
|
-
SystemInfo::
|
|
26
|
-
DiskUsage::
|
|
27
|
+
SystemInfo::View.call(model.system_info, tui, disabled:),
|
|
28
|
+
DiskUsage::View.call(model.disk_usage, tui, disabled:),
|
|
27
29
|
]
|
|
28
30
|
)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Update = -> (message, model) do
|
|
32
34
|
case message
|
|
33
35
|
in [:system_info, *rest]
|
|
34
|
-
|
|
35
|
-
new_child, command = SystemInfo::UPDATE.call(child_message, model.system_info)
|
|
36
|
+
new_child, command = SystemInfo::Update.call(rest, model.system_info)
|
|
36
37
|
[model.with(system_info: new_child), command]
|
|
37
38
|
in [:disk_usage, *rest]
|
|
38
|
-
|
|
39
|
-
new_child, command = DiskUsage::UPDATE.call(child_message, model.disk_usage)
|
|
39
|
+
new_child, command = DiskUsage::Update.call(rest, model.disk_usage)
|
|
40
40
|
[model.with(disk_usage: new_child), command]
|
|
41
41
|
else
|
|
42
42
|
[model, nil]
|
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
8
9
|
# Fetches and displays system information via +uname -a+.
|
|
9
|
-
# A
|
|
10
|
+
# A fragment for fetching and displaying system information.
|
|
10
11
|
module SystemInfo
|
|
11
12
|
Command = RatatuiRuby::Tea::Command
|
|
12
13
|
|
|
13
14
|
Model = Data.define(:output, :loading)
|
|
14
|
-
INITIAL = Model.new(output: "Press 's' for system info", loading: false)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
18
22
|
tui.style(fg: :dark_gray)
|
|
19
23
|
else
|
|
20
24
|
nil
|
|
@@ -26,11 +30,11 @@ module SystemInfo
|
|
|
26
30
|
)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Update = -> (message, model) do
|
|
30
34
|
case message
|
|
31
|
-
in [:
|
|
35
|
+
in [{ type: :system, envelope: :system_info, status: 0, stdout: }]
|
|
32
36
|
[model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
|
|
33
|
-
in [:system_info,
|
|
37
|
+
in [{ type: :system, envelope: :system_info, stderr: }]
|
|
34
38
|
[model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
|
|
35
39
|
else
|
|
36
40
|
[model, nil]
|
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT-0
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
|
+
require "ratatui_ruby/tea"
|
|
8
9
|
# Displays system uptime.
|
|
9
|
-
# A
|
|
10
|
+
# A fragment for displaying system uptime.
|
|
10
11
|
module Uptime
|
|
11
12
|
Command = RatatuiRuby::Tea::Command
|
|
12
13
|
|
|
13
14
|
Model = Data.define(:output, :loading)
|
|
14
|
-
INITIAL = Model.new(output: "Press 'u' for uptime", loading: false)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
18
22
|
tui.style(fg: :dark_gray)
|
|
19
23
|
else
|
|
20
24
|
nil
|
|
@@ -26,11 +30,11 @@ module Uptime
|
|
|
26
30
|
)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Update = -> (message, model) do
|
|
30
34
|
case message
|
|
31
|
-
in [:
|
|
35
|
+
in [{ type: :system, envelope: :uptime, status: 0, stdout: }]
|
|
32
36
|
[model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
|
|
33
|
-
in [:uptime,
|
|
37
|
+
in [{ type: :system, envelope: :uptime, stderr: }]
|
|
34
38
|
[model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
|
|
35
39
|
else
|
|
36
40
|
[model, nil]
|
|
@@ -19,9 +19,12 @@ This example exists as a documentation regression test. It ensures that the very
|
|
|
19
19
|
<!-- SYNC:START:./app.rb:mvu -->
|
|
20
20
|
```ruby
|
|
21
21
|
Model = Data.define(:text)
|
|
22
|
-
MODEL = Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
Init = -> do
|
|
24
|
+
Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
View = -> (model, tui) do
|
|
25
28
|
tui.paragraph(
|
|
26
29
|
text: model.text,
|
|
27
30
|
alignment: :center,
|
|
@@ -33,7 +36,7 @@ VIEW = -> (model, tui) do
|
|
|
33
36
|
)
|
|
34
37
|
end
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
Update = -> (msg, model) do
|
|
37
40
|
if msg.q? || msg.ctrl_c?
|
|
38
41
|
RatatuiRuby::Tea::Command.exit
|
|
39
42
|
else
|
|
@@ -42,7 +45,7 @@ UPDATE = -> (msg, model) do
|
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def run
|
|
45
|
-
RatatuiRuby::Tea.run(
|
|
48
|
+
RatatuiRuby::Tea.run(VerifyReadmeUsage)
|
|
46
49
|
end
|
|
47
50
|
```
|
|
48
51
|
<!-- SYNC:END -->
|