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
data/lib/ratatui_ruby/tea.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
require_relative "tea/version"
|
|
9
|
+
require_relative "tea/message"
|
|
9
10
|
require_relative "tea/command"
|
|
10
11
|
require_relative "tea/runtime"
|
|
11
12
|
require_relative "tea/router"
|
|
@@ -24,35 +25,68 @@ module RatatuiRuby # :nodoc: Documented in the ratatui_ruby gem.
|
|
|
24
25
|
# Starts the MVU event loop.
|
|
25
26
|
#
|
|
26
27
|
# Convenience delegator to Runtime.run. See Runtime for full documentation.
|
|
27
|
-
def self.run(
|
|
28
|
-
Runtime.run(
|
|
28
|
+
def self.run(root_fragment = nil, **)
|
|
29
|
+
Runtime.run(root_fragment, **)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Normalizes Init callable return value to <tt>[model, command]</tt> tuple.
|
|
33
|
+
#
|
|
34
|
+
# Init callables use DWIM syntax. They can return just a model, just a command,
|
|
35
|
+
# or a full <tt>[model, command]</tt> tuple.
|
|
36
|
+
#
|
|
37
|
+
# This method handles all formats. Use it when composing child fragment Inits
|
|
38
|
+
# in fractal architecture.
|
|
39
|
+
#
|
|
40
|
+
# [result] The Init return value.
|
|
41
|
+
#
|
|
42
|
+
# === Examples
|
|
43
|
+
#
|
|
44
|
+
#--
|
|
45
|
+
# SPDX-SnippetBegin
|
|
46
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
47
|
+
# SPDX-License-Identifier: MIT-0
|
|
48
|
+
#++
|
|
49
|
+
# # Parent fragment composes children
|
|
50
|
+
# Init = ->(theme:) do
|
|
51
|
+
# stats_model, stats_cmd = Tea.normalize_init(StatsPanel::Init.(theme: theme))
|
|
52
|
+
# network_model, network_cmd = Tea.normalize_init(NetworkPanel::Init.(theme: theme))
|
|
53
|
+
#
|
|
54
|
+
# model = Model.new(stats: stats_model, network: network_model)
|
|
55
|
+
# command = Command.batch(stats_cmd, network_cmd)
|
|
56
|
+
# [model, command]
|
|
57
|
+
# end
|
|
58
|
+
#--
|
|
59
|
+
# SPDX-SnippetEnd
|
|
60
|
+
#++
|
|
61
|
+
def self.normalize_init(result)
|
|
62
|
+
Runtime.normalize_init(result)
|
|
29
63
|
end
|
|
30
64
|
|
|
31
65
|
# Wraps a command with a routing prefix.
|
|
32
66
|
#
|
|
33
|
-
# Parent
|
|
34
|
-
# to the correct child
|
|
67
|
+
# Parent fragments trigger child fragment commands. The results need routing back
|
|
68
|
+
# to the correct child fragment. Manually wrapping every command is tedious.
|
|
35
69
|
#
|
|
36
70
|
# This method prefixes command results automatically. Use it to route
|
|
37
|
-
# child
|
|
71
|
+
# child fragment command results in Fractal Architecture.
|
|
38
72
|
#
|
|
39
|
-
# [command] The child
|
|
73
|
+
# [command] The child fragment command to wrap.
|
|
40
74
|
# [prefix] Symbol prepended to results (e.g., <tt>:stats</tt>).
|
|
41
75
|
#
|
|
42
76
|
# === Example
|
|
43
77
|
#
|
|
44
78
|
# # Verbose:
|
|
45
|
-
# Command.map(
|
|
79
|
+
# Command.map(child_fragment.fetch_command) { |r| [:stats, *r] }
|
|
46
80
|
#
|
|
47
81
|
# # Concise:
|
|
48
|
-
# Tea.route(
|
|
82
|
+
# Tea.route(child_fragment.fetch_command, :stats)
|
|
49
83
|
def self.route(command, prefix)
|
|
50
84
|
Command.map(command) { |result| [prefix, *result] }
|
|
51
85
|
end
|
|
52
86
|
|
|
53
|
-
# Delegates a prefixed message to a child
|
|
87
|
+
# Delegates a prefixed message to a child fragment's UPDATE.
|
|
54
88
|
#
|
|
55
|
-
# Parent
|
|
89
|
+
# Parent fragment UPDATE functions route messages to child fragments. Each route
|
|
56
90
|
# requires pattern matching, calling the child, and rewrapping any returned
|
|
57
91
|
# command. The boilerplate adds up fast.
|
|
58
92
|
#
|
data/rbs_collection.lock.yaml
CHANGED
|
@@ -17,14 +17,6 @@ gems:
|
|
|
17
17
|
version: '0'
|
|
18
18
|
source:
|
|
19
19
|
type: stdlib
|
|
20
|
-
- name: dbm
|
|
21
|
-
version: '0'
|
|
22
|
-
source:
|
|
23
|
-
type: stdlib
|
|
24
|
-
- name: erb
|
|
25
|
-
version: '0'
|
|
26
|
-
source:
|
|
27
|
-
type: stdlib
|
|
28
20
|
- name: ffi
|
|
29
21
|
version: 1.17.3
|
|
30
22
|
source:
|
|
@@ -77,16 +69,8 @@ gems:
|
|
|
77
69
|
version: 1.7.0
|
|
78
70
|
source:
|
|
79
71
|
type: rubygems
|
|
80
|
-
- name: pstore
|
|
81
|
-
version: '0'
|
|
82
|
-
source:
|
|
83
|
-
type: stdlib
|
|
84
|
-
- name: psych
|
|
85
|
-
version: '0'
|
|
86
|
-
source:
|
|
87
|
-
type: stdlib
|
|
88
72
|
- name: ratatui_ruby
|
|
89
|
-
version: 0.10.
|
|
73
|
+
version: 0.10.2
|
|
90
74
|
source:
|
|
91
75
|
type: rubygems
|
|
92
76
|
- name: ratatui_ruby-devtools
|
data/sig/concurrent.rbs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Minimal stubs for concurrent-ruby-edge types used by Tea.
|
|
5
|
+
# This is not a complete type definition — just enough for Steep.
|
|
6
|
+
|
|
7
|
+
module Concurrent
|
|
8
|
+
module Promises
|
|
9
|
+
# Factory for resolvable events.
|
|
10
|
+
def self.resolvable_event: () -> ResolvableEvent
|
|
11
|
+
|
|
12
|
+
# A future/event that can be resolved by user code.
|
|
13
|
+
class ResolvableEvent
|
|
14
|
+
def resolve: (?bool wait, ?bool raise_on_reassign) -> self
|
|
15
|
+
def resolved?: () -> bool
|
|
16
|
+
def wait: (?Numeric? timeout) -> self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Thread-safe FIFO channel for message passing.
|
|
20
|
+
class Channel
|
|
21
|
+
def initialize: (?Integer capacity) -> void
|
|
22
|
+
def push: (untyped message, ?Numeric? timeout) -> self
|
|
23
|
+
def pop: (?Numeric? timeout, ?untyped timeout_value) -> untyped
|
|
24
|
+
def try_pop: (?untyped no_value) -> untyped
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Represents a value that may not yet be available.
|
|
28
|
+
class Future[T]
|
|
29
|
+
def pending?: () -> bool
|
|
30
|
+
def resolved?: () -> bool
|
|
31
|
+
def rejected?: () -> bool
|
|
32
|
+
def wait: (?Numeric? timeout) -> self
|
|
33
|
+
def value: () -> T?
|
|
34
|
+
def reason: () -> Exception?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Factory method for creating futures.
|
|
38
|
+
def self.future: [T] () { () -> T } -> Future[T]
|
|
39
|
+
|
|
40
|
+
# Races multiple futures/events, returning when any resolves.
|
|
41
|
+
def self.any_event: (*Future[untyped] | ResolvableEvent) -> ResolvableEvent
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Single-element blocking container for synchronization.
|
|
45
|
+
class MVar
|
|
46
|
+
TIMEOUT: Symbol
|
|
47
|
+
|
|
48
|
+
def initialize: (?untyped initial_value) -> void
|
|
49
|
+
def put: (untyped value) -> void
|
|
50
|
+
def take: (?Numeric? timeout) -> untyped
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Cooperative cancellation abstraction.
|
|
54
|
+
class Cancellation
|
|
55
|
+
def self.new: (?Promises::ResolvableEvent origin) -> [Cancellation, Promises::ResolvableEvent]
|
|
56
|
+
def self.timeout: (Numeric seconds) -> Cancellation
|
|
57
|
+
|
|
58
|
+
def origin: () -> Promises::ResolvableEvent
|
|
59
|
+
def canceled?: () -> bool
|
|
60
|
+
def check!: (?Class error) -> self
|
|
61
|
+
def join: (*Cancellation cancellations) -> Cancellation
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Thread-safe hash implementation.
|
|
65
|
+
class Map[K, V]
|
|
66
|
+
def initialize: () -> void
|
|
67
|
+
def []: (K key) -> V?
|
|
68
|
+
def []=: (K key, V value) -> V
|
|
69
|
+
def delete: (K key) -> V?
|
|
70
|
+
def each: () { (K, V) -> void } -> self
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -14,7 +14,7 @@ module RatatuiRuby
|
|
|
14
14
|
|
|
15
15
|
# Execute the command's side effect.
|
|
16
16
|
# Push result messages via the outlet.
|
|
17
|
-
def call: (Outlet out,
|
|
17
|
+
def call: (Outlet out, Concurrent::Cancellation token) -> void
|
|
18
18
|
|
|
19
19
|
# Grace period for cooperative cancellation (seconds).
|
|
20
20
|
# Runtime waits this long before force-killing the thread.
|
|
@@ -42,7 +42,7 @@ module RatatuiRuby
|
|
|
42
42
|
def stream?: () -> bool
|
|
43
43
|
|
|
44
44
|
# Executes the shell command and sends results via outlet.
|
|
45
|
-
def call: (Outlet out,
|
|
45
|
+
def call: (Outlet out, Concurrent::Cancellation token) -> void
|
|
46
46
|
|
|
47
47
|
def self.new: (command: String, tag: (Symbol | Class), stream: bool) -> instance
|
|
48
48
|
end
|
|
@@ -59,7 +59,7 @@ module RatatuiRuby
|
|
|
59
59
|
def tea_cancellation_grace_period: () -> Float
|
|
60
60
|
|
|
61
61
|
# Executes the inner command and transforms the result.
|
|
62
|
-
def call: (Outlet out,
|
|
62
|
+
def call: (Outlet out, Concurrent::Cancellation token) -> void
|
|
63
63
|
|
|
64
64
|
def self.new: (inner_command: execution, mapper: ^(Array[untyped]) -> Array[untyped]) -> instance
|
|
65
65
|
end
|
|
@@ -85,6 +85,9 @@ module RatatuiRuby
|
|
|
85
85
|
# Creates a quit command.
|
|
86
86
|
def self.exit: () -> Exit
|
|
87
87
|
|
|
88
|
+
# Creates a fresh cancellation that never fires.
|
|
89
|
+
def self.uncancellable: () -> Concurrent::Cancellation
|
|
90
|
+
|
|
88
91
|
# Creates a shell execution command.
|
|
89
92
|
def self.system: (String command, (Symbol | Class) tag, ?stream: bool) -> System
|
|
90
93
|
|
|
@@ -98,45 +101,37 @@ module RatatuiRuby
|
|
|
98
101
|
def self.error: (_Command command, Exception exception) -> Error
|
|
99
102
|
|
|
100
103
|
# Wraps a callable for unique identity per dispatch.
|
|
101
|
-
def self.custom: (^(Outlet,
|
|
102
|
-
| (?grace_period: Float?) { (Outlet,
|
|
103
|
-
|
|
104
|
-
# Private wrapper for Command.custom.
|
|
105
|
-
class Wrapped < Data
|
|
106
|
-
include Custom
|
|
107
|
-
|
|
108
|
-
attr_reader callable: ^(Outlet, CancellationToken) -> void
|
|
109
|
-
attr_reader grace_period: Float?
|
|
104
|
+
def self.custom: (^(Outlet, Concurrent::Cancellation) -> void callable, ?grace_period: Float?) -> _Command
|
|
105
|
+
| (?grace_period: Float?) { (Outlet, Concurrent::Cancellation) -> void } -> _Command
|
|
110
106
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
end
|
|
107
|
+
# Creates a one-shot timer command.
|
|
108
|
+
def self.wait: (Float seconds, Symbol tag) -> Wait
|
|
114
109
|
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
# Number of times cancel! has been called.
|
|
118
|
-
attr_reader cancel_count: Integer
|
|
110
|
+
# Alias for wait, semantically used for recurring timers.
|
|
111
|
+
def self.tick: (Float seconds, Symbol tag) -> Wait
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
# One-shot timer command that waits, then sends a message.
|
|
114
|
+
class Wait < Data
|
|
115
|
+
include Custom
|
|
121
116
|
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
attr_reader seconds: Float
|
|
118
|
+
attr_reader tag: Symbol
|
|
124
119
|
|
|
125
|
-
|
|
126
|
-
def cancelled?: () -> bool
|
|
120
|
+
def self.new: (seconds: Float, tag: Symbol) -> instance
|
|
127
121
|
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
# Execute the timer with cooperative cancellation.
|
|
123
|
+
def call: (Outlet out, Concurrent::Cancellation token) -> void
|
|
124
|
+
end
|
|
131
125
|
|
|
132
|
-
|
|
126
|
+
# Private wrapper for Command.custom.
|
|
127
|
+
class Wrapped < Data
|
|
128
|
+
include Custom
|
|
133
129
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
end
|
|
130
|
+
attr_reader callable: ^(Outlet, Concurrent::Cancellation) -> void
|
|
131
|
+
attr_reader grace_period: Float?
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
def self.new: (callable: ^(Outlet, Concurrent::Cancellation) -> void, grace_period: Float?) -> instance
|
|
134
|
+
def call: (Outlet out, Concurrent::Cancellation token) -> void
|
|
140
135
|
end
|
|
141
136
|
|
|
142
137
|
# Mixin for user-defined custom commands.
|
|
@@ -149,15 +144,124 @@ module RatatuiRuby
|
|
|
149
144
|
def tea_cancellation_grace_period: () -> Float
|
|
150
145
|
end
|
|
151
146
|
|
|
147
|
+
# Minimal interface for callables accepted by Lifecycle.run_sync.
|
|
148
|
+
interface _Callable
|
|
149
|
+
def call: (Outlet out, Concurrent::Cancellation token) -> void
|
|
150
|
+
def respond_to?: (Symbol, ?bool) -> bool
|
|
151
|
+
def tea_cancellation_grace_period: () -> Float
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Manages command execution with lifecycle support.
|
|
155
|
+
class Lifecycle
|
|
156
|
+
# Internal representation of a tracked async command.
|
|
157
|
+
class Entry < Data
|
|
158
|
+
attr_reader future: Concurrent::Promises::Future[void]
|
|
159
|
+
attr_reader origin: Concurrent::Promises::ResolvableEvent
|
|
160
|
+
|
|
161
|
+
def self.new: (future: Concurrent::Promises::Future[void], origin: Concurrent::Promises::ResolvableEvent) -> instance
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
@active: Concurrent::Map[_Callable, Entry]
|
|
165
|
+
|
|
166
|
+
def initialize: () -> void
|
|
167
|
+
|
|
168
|
+
# Runs a command synchronously, returning its result.
|
|
169
|
+
def run_sync: (_Callable command, Concurrent::Cancellation token, timeout: Float) -> Object?
|
|
170
|
+
|
|
171
|
+
# Runs a command asynchronously, tracking it for later cancellation.
|
|
172
|
+
def run_async: (_Callable command, Concurrent::Promises::Channel channel) -> Entry
|
|
173
|
+
|
|
174
|
+
# Cancels a running command, waiting for its grace period.
|
|
175
|
+
def cancel: (_Callable command) -> void
|
|
176
|
+
|
|
177
|
+
# Cancels all active commands and waits for them to complete.
|
|
178
|
+
def shutdown: () -> void
|
|
179
|
+
end
|
|
180
|
+
|
|
152
181
|
# Messaging gateway for custom commands.
|
|
153
182
|
class Outlet
|
|
154
|
-
@
|
|
183
|
+
@channel: Concurrent::Promises::Channel
|
|
184
|
+
@live: Lifecycle
|
|
155
185
|
|
|
156
|
-
def initialize: (
|
|
186
|
+
def initialize: (Concurrent::Promises::Channel channel, lifecycle: Lifecycle) -> void
|
|
157
187
|
|
|
158
|
-
#
|
|
159
|
-
|
|
188
|
+
# Internal infrastructure for nested command lifecycle sharing.
|
|
189
|
+
attr_reader live: Lifecycle
|
|
190
|
+
|
|
191
|
+
# Sends a message to the runtime.
|
|
192
|
+
def put: (*Object args) -> void
|
|
193
|
+
|
|
194
|
+
# Runs a child command synchronously, returning its result.
|
|
195
|
+
def source: (_Callable command, Concurrent::Cancellation token, ?timeout: Float) -> Object?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# A fire-and-forget parallel command.
|
|
199
|
+
class Batch
|
|
200
|
+
include Custom
|
|
201
|
+
|
|
202
|
+
attr_reader commands: Array[Custom]
|
|
203
|
+
|
|
204
|
+
def self.new: (*Custom | Array[Custom]) -> instance
|
|
205
|
+
|
|
206
|
+
def call: (Outlet outlet, Concurrent::Cancellation token) -> void
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Creates a parallel batch command.
|
|
210
|
+
def self.batch: (*Custom | Array[Custom]) -> Batch
|
|
211
|
+
|
|
212
|
+
# An aggregating parallel command.
|
|
213
|
+
class All
|
|
214
|
+
include Custom
|
|
215
|
+
|
|
216
|
+
attr_reader tag: Symbol
|
|
217
|
+
attr_reader commands: Array[Custom]
|
|
218
|
+
attr_reader nested: bool
|
|
219
|
+
|
|
220
|
+
def self.new: (Symbol tag, *Custom | Array[Custom]) -> instance
|
|
221
|
+
|
|
222
|
+
def call: (Outlet outlet, Concurrent::Cancellation token) -> void
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Creates an aggregating parallel command.
|
|
226
|
+
def self.all: (Symbol tag, *Custom | Array[Custom]) -> All
|
|
227
|
+
|
|
228
|
+
# HTTP response shapes for pattern matching
|
|
229
|
+
type http_success_shape = { type: :http, envelope: Symbol, status: Integer, body: String, headers: Hash[String, String] }
|
|
230
|
+
type http_error_shape = { type: :http, envelope: Symbol, error: String }
|
|
231
|
+
|
|
232
|
+
# HTTP response with deconstruct_keys for pattern matching.
|
|
233
|
+
class HttpResponse < Data
|
|
234
|
+
attr_reader envelope: Symbol
|
|
235
|
+
attr_reader status: Integer?
|
|
236
|
+
attr_reader body: String?
|
|
237
|
+
attr_reader headers: Hash[String, String]?
|
|
238
|
+
attr_reader error: String?
|
|
239
|
+
|
|
240
|
+
def self.new: (envelope: Symbol, status: Integer?, body: String?, headers: Hash[String, String]?, error: String?) -> instance
|
|
241
|
+
|
|
242
|
+
def deconstruct_keys: (Array[Symbol]?) -> (http_success_shape | http_error_shape)
|
|
160
243
|
end
|
|
244
|
+
|
|
245
|
+
# An HTTP request command.
|
|
246
|
+
class Http < Data
|
|
247
|
+
include Custom
|
|
248
|
+
|
|
249
|
+
attr_reader method: Symbol
|
|
250
|
+
attr_reader url: String
|
|
251
|
+
attr_reader envelope: Symbol
|
|
252
|
+
attr_reader headers: Hash[String, String]?
|
|
253
|
+
attr_reader body: String?
|
|
254
|
+
attr_reader timeout: (Integer | Float)?
|
|
255
|
+
|
|
256
|
+
def self.new: (method: Symbol, url: String, envelope: Symbol, headers: Hash[String, String]?, body: String?, timeout: (Integer | Float)?) -> instance
|
|
257
|
+
|
|
258
|
+
def call: (Outlet outlet, Concurrent::Cancellation token) -> void
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Creates an HTTP request command.
|
|
262
|
+
# DWIM: accepts positional args, method keywords (get:, post:, etc.), or explicit keywords.
|
|
263
|
+
# See Http class and documentation for all supported patterns.
|
|
264
|
+
def self.http: (*(Symbol | String | nil), **(Symbol | String | Integer | Float | Hash[String, String] | ^(String, Hash[String, String]?, Integer?) -> Object | nil)) -> Http
|
|
161
265
|
end
|
|
162
266
|
end
|
|
163
267
|
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
#++
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
module Tea
|
|
8
|
+
# Messages sent from commands to update functions.
|
|
9
|
+
module Message
|
|
10
|
+
# Fallback predicate mixin.
|
|
11
|
+
module Predicates
|
|
12
|
+
# Returns false for unknown predicate methods.
|
|
13
|
+
def method_missing: (Symbol name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
|
|
14
|
+
|
|
15
|
+
# Responds to all predicate methods.
|
|
16
|
+
def respond_to_missing?: (Symbol name, ?bool include_private) -> bool
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# System command response types.
|
|
20
|
+
module System
|
|
21
|
+
# Response from a system command (batch mode).
|
|
22
|
+
class Batch
|
|
23
|
+
include Predicates
|
|
24
|
+
|
|
25
|
+
attr_reader envelope: Symbol
|
|
26
|
+
attr_reader stdout: String
|
|
27
|
+
attr_reader stderr: String
|
|
28
|
+
attr_reader status: Integer
|
|
29
|
+
|
|
30
|
+
def initialize: (
|
|
31
|
+
envelope: Symbol,
|
|
32
|
+
stdout: String,
|
|
33
|
+
stderr: String,
|
|
34
|
+
status: Integer
|
|
35
|
+
) -> void
|
|
36
|
+
|
|
37
|
+
def system?: () -> bool
|
|
38
|
+
def success?: () -> bool
|
|
39
|
+
def error?: () -> bool
|
|
40
|
+
def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Streaming message from a system command.
|
|
44
|
+
class Stream
|
|
45
|
+
include Predicates
|
|
46
|
+
|
|
47
|
+
attr_reader envelope: Symbol
|
|
48
|
+
attr_reader stream: Symbol
|
|
49
|
+
attr_reader content: String?
|
|
50
|
+
attr_reader status: Integer?
|
|
51
|
+
|
|
52
|
+
def initialize: (
|
|
53
|
+
envelope: Symbol,
|
|
54
|
+
stream: Symbol,
|
|
55
|
+
content: String?,
|
|
56
|
+
status: Integer?
|
|
57
|
+
) -> void
|
|
58
|
+
|
|
59
|
+
def system?: () -> bool
|
|
60
|
+
def stdout?: () -> bool
|
|
61
|
+
def stderr?: () -> bool
|
|
62
|
+
def complete?: () -> bool
|
|
63
|
+
def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Response from a timer command.
|
|
68
|
+
class Timer
|
|
69
|
+
include Predicates
|
|
70
|
+
|
|
71
|
+
attr_reader envelope: Symbol
|
|
72
|
+
attr_reader elapsed: Float
|
|
73
|
+
|
|
74
|
+
def initialize: (envelope: Symbol, elapsed: Float) -> void
|
|
75
|
+
|
|
76
|
+
def timer?: () -> bool
|
|
77
|
+
def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Response from an HTTP command.
|
|
81
|
+
class HttpResponse
|
|
82
|
+
include Predicates
|
|
83
|
+
|
|
84
|
+
attr_reader envelope: Symbol
|
|
85
|
+
attr_reader status: Integer?
|
|
86
|
+
attr_reader body: String?
|
|
87
|
+
attr_reader headers: Hash[String, String]?
|
|
88
|
+
attr_reader error: String?
|
|
89
|
+
|
|
90
|
+
def initialize: (
|
|
91
|
+
envelope: Symbol,
|
|
92
|
+
status: Integer?,
|
|
93
|
+
body: String?,
|
|
94
|
+
headers: Hash[String, String]?,
|
|
95
|
+
error: String?
|
|
96
|
+
) -> void
|
|
97
|
+
|
|
98
|
+
def http?: () -> bool
|
|
99
|
+
def success?: () -> bool
|
|
100
|
+
def error?: () -> bool
|
|
101
|
+
def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Response from Command.all aggregating parallel execution.
|
|
105
|
+
class All
|
|
106
|
+
include Predicates
|
|
107
|
+
|
|
108
|
+
attr_reader envelope: Symbol
|
|
109
|
+
attr_reader results: Array[untyped]
|
|
110
|
+
attr_reader nested: bool
|
|
111
|
+
|
|
112
|
+
def initialize: (
|
|
113
|
+
envelope: Symbol,
|
|
114
|
+
results: Array[untyped],
|
|
115
|
+
nested: bool
|
|
116
|
+
) -> void
|
|
117
|
+
|
|
118
|
+
def all?: () -> bool
|
|
119
|
+
def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -15,7 +15,7 @@ module RatatuiRuby
|
|
|
15
15
|
def with: (**Object) -> self
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# Interface for fractal child modules (
|
|
18
|
+
# Interface for fractal child modules (fragments).
|
|
19
19
|
# Routed modules must have an UPDATE constant that handles child messages.
|
|
20
20
|
# NOTE: We use Module here because RBS interfaces can't declare class methods.
|
|
21
21
|
|
|
@@ -8,7 +8,7 @@ module RatatuiRuby
|
|
|
8
8
|
# MVU runtime event loop.
|
|
9
9
|
class Runtime
|
|
10
10
|
# Active command tracking entry.
|
|
11
|
-
type active_entry = {
|
|
11
|
+
type active_entry = { future: Concurrent::Promises::Future[void], origin: Concurrent::Promises::ResolvableEvent }
|
|
12
12
|
|
|
13
13
|
# Widget type accepted by view functions.
|
|
14
14
|
type renderable = RatatuiRuby::_CustomWidget | RatatuiRuby::widget
|
|
@@ -17,6 +17,9 @@ module RatatuiRuby
|
|
|
17
17
|
# Steep needs a union type (not interface) for is_a? narrowing.
|
|
18
18
|
type update_result = [Object, Command::execution?] | Command::execution | Object
|
|
19
19
|
|
|
20
|
+
# Duck type for init result that can be normalized.
|
|
21
|
+
type init_result = [Object, Command::execution?] | Command::execution | Object
|
|
22
|
+
|
|
20
23
|
# Duck type for values that can be queried for command-ness.
|
|
21
24
|
interface _MaybeCommand
|
|
22
25
|
def nil?: () -> bool
|
|
@@ -25,20 +28,50 @@ module RatatuiRuby
|
|
|
25
28
|
def tea_command?: () -> bool
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
# Starts the MVU event loop.
|
|
31
|
+
# Starts the MVU event loop (positional fragment).
|
|
29
32
|
def self.run: [Model] (
|
|
33
|
+
?Module? root_fragment,
|
|
34
|
+
?fps: Integer
|
|
35
|
+
) -> Model
|
|
36
|
+
|
|
37
|
+
# Starts the MVU event loop (explicit parameters).
|
|
38
|
+
| [Model] (
|
|
39
|
+
?Module? root_fragment,
|
|
40
|
+
?fps: Integer,
|
|
30
41
|
model: Model,
|
|
31
42
|
view: ^(Model, RatatuiRuby::TUI) -> renderable,
|
|
32
43
|
update: ^(RatatuiRuby::Event, Model) -> update_result?,
|
|
33
|
-
?
|
|
44
|
+
?command: Command::execution?
|
|
34
45
|
) -> Model
|
|
35
46
|
|
|
47
|
+
# Starts the MVU event loop (explicit parameters without fps).
|
|
48
|
+
| [Model] (
|
|
49
|
+
?Module? root_fragment,
|
|
50
|
+
model: Model,
|
|
51
|
+
view: ^(Model, RatatuiRuby::TUI) -> renderable,
|
|
52
|
+
update: ^(RatatuiRuby::Event, Model) -> update_result?,
|
|
53
|
+
?command: Command::execution?
|
|
54
|
+
) -> Model
|
|
55
|
+
|
|
56
|
+
# Normalizes Init callable return value to [model, command] tuple.
|
|
57
|
+
def self.normalize_init: [Model] (init_result result) -> [Model?, Command::execution?]
|
|
58
|
+
QUIT: Object
|
|
59
|
+
|
|
36
60
|
private
|
|
37
61
|
|
|
38
|
-
def self.
|
|
39
|
-
def self.
|
|
62
|
+
def self.validate_view_return!: (renderable? widget) -> void
|
|
63
|
+
def self.normalize_update_return: [Model] (update_result? result, Model? previous_model) -> [Model?, Command::execution?]
|
|
40
64
|
def self.validate_ractor_shareable!: [T] (T object, String name) -> void
|
|
41
|
-
def self.
|
|
65
|
+
def self.fragment_from_kwargs: (Module? root_fragment, ?model: untyped, ?view: untyped, ?update: untyped, ?command: untyped) -> Module
|
|
66
|
+
def self.fragment_invariant!: (String param) -> void
|
|
67
|
+
def self.init_callable: [Model] () -> ^() -> [Model?, Command::execution?]
|
|
68
|
+
def self.start_runtime: () -> untyped
|
|
69
|
+
def self.draw_view: () -> void
|
|
70
|
+
def self.handle_ratatui_event: () -> void
|
|
71
|
+
def self.handle_sync: () -> void
|
|
72
|
+
QUEUE_EMPTY: Object
|
|
73
|
+
def self.send_pending_messages: (?dispatch: bool) -> void
|
|
74
|
+
def self.dispatch_command: () -> void
|
|
42
75
|
end
|
|
43
76
|
end
|
|
44
77
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
#++
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
module Tea
|
|
8
|
+
module TestHelper
|
|
9
|
+
def validate_tea_command!: (Runtime::_MaybeCommand) -> nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|