rooibos 0.5.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 +7 -0
- data/.builds/ruby-3.2.yml +51 -0
- data/.builds/ruby-3.3.yml +51 -0
- data/.builds/ruby-3.4.yml +51 -0
- data/.builds/ruby-4.0.0.yml +51 -0
- data/.pre-commit-config.yaml +16 -0
- data/.rubocop.yml +8 -0
- data/AGENTS.md +108 -0
- data/CHANGELOG.md +214 -0
- data/LICENSE +304 -0
- data/LICENSES/AGPL-3.0-or-later.txt +235 -0
- data/LICENSES/CC-BY-SA-4.0.txt +170 -0
- data/LICENSES/CC0-1.0.txt +121 -0
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/LICENSES/MIT.txt +18 -0
- data/README.md +183 -0
- data/REUSE.toml +24 -0
- data/Rakefile +16 -0
- data/Steepfile +13 -0
- data/doc/concepts/application_architecture.md +197 -0
- data/doc/concepts/application_testing.md +49 -0
- data/doc/concepts/async_work.md +164 -0
- data/doc/concepts/commands.md +530 -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 +409 -0
- data/doc/contributors/WIP/init_callable_proposal.md +344 -0
- data/doc/contributors/WIP/mvu_tea_implementations_research.md +373 -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 +214 -0
- data/doc/contributors/kit-no-outlet.md +238 -0
- data/doc/contributors/priorities.md +38 -0
- data/doc/custom.css +22 -0
- data/doc/getting_started/quickstart.md +56 -0
- data/doc/images/.gitkeep +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_cmd_exec.png +0 -0
- data/doc/index.md +25 -0
- data/examples/app_fractal_dashboard/README.md +60 -0
- data/examples/app_fractal_dashboard/app.rb +63 -0
- data/examples/app_fractal_dashboard/dashboard/base.rb +73 -0
- data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +86 -0
- data/examples/app_fractal_dashboard/dashboard/update_manual.rb +87 -0
- data/examples/app_fractal_dashboard/dashboard/update_router.rb +43 -0
- data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +81 -0
- data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
- data/examples/app_fractal_dashboard/fragments/custom_shell_output.rb +90 -0
- data/examples/app_fractal_dashboard/fragments/disk_usage.rb +47 -0
- data/examples/app_fractal_dashboard/fragments/network_panel.rb +45 -0
- data/examples/app_fractal_dashboard/fragments/ping.rb +47 -0
- data/examples/app_fractal_dashboard/fragments/stats_panel.rb +45 -0
- data/examples/app_fractal_dashboard/fragments/system_info.rb +47 -0
- data/examples/app_fractal_dashboard/fragments/uptime.rb +47 -0
- data/examples/verify_readme_usage/README.md +54 -0
- data/examples/verify_readme_usage/app.rb +47 -0
- data/examples/widget_command_system/README.md +70 -0
- data/examples/widget_command_system/app.rb +132 -0
- data/exe/.gitkeep +0 -0
- data/lib/rooibos/command/all.rb +69 -0
- data/lib/rooibos/command/batch.rb +77 -0
- data/lib/rooibos/command/custom.rb +104 -0
- data/lib/rooibos/command/http.rb +192 -0
- data/lib/rooibos/command/lifecycle.rb +134 -0
- data/lib/rooibos/command/outlet.rb +157 -0
- data/lib/rooibos/command/wait.rb +80 -0
- data/lib/rooibos/command.rb +546 -0
- data/lib/rooibos/error.rb +55 -0
- data/lib/rooibos/message/all.rb +45 -0
- data/lib/rooibos/message/http_response.rb +61 -0
- data/lib/rooibos/message/system/batch.rb +61 -0
- data/lib/rooibos/message/system/stream.rb +67 -0
- data/lib/rooibos/message/timer.rb +46 -0
- data/lib/rooibos/message.rb +38 -0
- data/lib/rooibos/router.rb +403 -0
- data/lib/rooibos/runtime.rb +396 -0
- data/lib/rooibos/shortcuts.rb +49 -0
- data/lib/rooibos/test_helper.rb +56 -0
- data/lib/rooibos/version.rb +12 -0
- data/lib/rooibos.rb +121 -0
- data/mise.toml +8 -0
- data/rbs_collection.lock.yaml +108 -0
- data/rbs_collection.yaml +15 -0
- data/sig/concurrent.rbs +72 -0
- data/sig/examples/verify_readme_usage/app.rbs +19 -0
- data/sig/examples/widget_command_system/app.rbs +26 -0
- data/sig/open3.rbs +17 -0
- data/sig/rooibos/command.rbs +265 -0
- data/sig/rooibos/error.rbs +13 -0
- data/sig/rooibos/message.rbs +121 -0
- data/sig/rooibos/router.rbs +153 -0
- data/sig/rooibos/runtime.rbs +75 -0
- data/sig/rooibos/shortcuts.rbs +16 -0
- data/sig/rooibos/test_helper.rbs +10 -0
- data/sig/rooibos/version.rbs +8 -0
- data/sig/rooibos.rbs +46 -0
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/resources/build.yml.erb +53 -0
- data/tasks/resources/index.html.erb +44 -0
- data/tasks/resources/rubies.yml +7 -0
- data/tasks/steep.rake +11 -0
- data/vendor/goodcop/base.yml +1047 -0
- metadata +241 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
#++
|
|
5
|
+
|
|
6
|
+
module Rooibos
|
|
7
|
+
# Declarative DSL for Fractal Architecture.
|
|
8
|
+
module Router
|
|
9
|
+
def self.included: (Class base) -> void
|
|
10
|
+
|
|
11
|
+
# Duck type for Data-like models that support `with`.
|
|
12
|
+
interface _DataModel
|
|
13
|
+
def public_send: (Symbol, *Object) -> Object
|
|
14
|
+
def with: (**Object) -> self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Interface for fractal child modules (fragments).
|
|
18
|
+
# Routed modules must have an UPDATE constant that handles child messages.
|
|
19
|
+
# NOTE: We use Module here because RBS interfaces can't declare class methods.
|
|
20
|
+
|
|
21
|
+
# Configuration for key handlers.
|
|
22
|
+
class KeyHandlerConfig < Data
|
|
23
|
+
attr_reader handler: (^() -> Command::execution?)?
|
|
24
|
+
attr_reader action: Symbol?
|
|
25
|
+
attr_reader route: Symbol?
|
|
26
|
+
attr_reader guard: (^(_DataModel) -> bool)?
|
|
27
|
+
|
|
28
|
+
def initialize: (?handler: (^() -> Command::execution?)?, ?action: Symbol?, ?route: Symbol?, ?guard: (^(_DataModel) -> bool)?) -> void
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Configuration for scroll handlers (no coordinates).
|
|
32
|
+
class ScrollHandlerConfig < Data
|
|
33
|
+
attr_reader handler: (^() -> Command::execution?)?
|
|
34
|
+
attr_reader action: Symbol?
|
|
35
|
+
|
|
36
|
+
def initialize: (?handler: (^() -> Command::execution?)?, ?action: Symbol?) -> void
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Configuration for click handlers (x, y coordinates).
|
|
40
|
+
class ClickHandlerConfig < Data
|
|
41
|
+
attr_reader handler: (^(Integer, Integer) -> Command::execution?)?
|
|
42
|
+
attr_reader action: Symbol?
|
|
43
|
+
|
|
44
|
+
def initialize: (?handler: (^(Integer, Integer) -> Command::execution?)?, ?action: Symbol?) -> void
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Class methods added when Router is included.
|
|
48
|
+
module ClassMethods : Module
|
|
49
|
+
@routes: Hash[Symbol, Module]
|
|
50
|
+
@actions: Hash[Symbol, ^() -> Command::execution?]
|
|
51
|
+
@key_handlers: Hash[String, KeyHandlerConfig]
|
|
52
|
+
@scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
|
|
53
|
+
@click_handler: ClickHandlerConfig?
|
|
54
|
+
|
|
55
|
+
def route: (Symbol | String prefix, to: Module) -> void
|
|
56
|
+
def routes: () -> Hash[Symbol, Module]
|
|
57
|
+
|
|
58
|
+
def action: (Symbol | String name, ^() -> Command::execution? handler) -> void
|
|
59
|
+
def actions: () -> Hash[Symbol, ^() -> Command::execution?]
|
|
60
|
+
|
|
61
|
+
def keymap: () { (KeymapBuilder) [self: KeymapBuilder] -> void } -> void
|
|
62
|
+
def key_handlers: () -> Hash[String, KeyHandlerConfig]
|
|
63
|
+
|
|
64
|
+
def mousemap: () { (MousemapBuilder) [self: MousemapBuilder] -> void } -> void
|
|
65
|
+
def scroll_handlers: () -> Hash[Symbol, ScrollHandlerConfig]
|
|
66
|
+
def click_handler: () -> ClickHandlerConfig?
|
|
67
|
+
|
|
68
|
+
# Returns UPDATE callable that handles routing.
|
|
69
|
+
# Uses is_a? checks for Event::Key and Event::Mouse type narrowing.
|
|
70
|
+
# Model must be Data-like with `with` and field accessors.
|
|
71
|
+
def from_router: () -> RouterUpdate
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# UPDATE callable returned by from_router with proper typing.
|
|
75
|
+
# Implements #call to satisfy Proc-like interfaces.
|
|
76
|
+
class RouterUpdate
|
|
77
|
+
@routes: Hash[Symbol, Module]
|
|
78
|
+
@actions: Hash[Symbol, ^() -> Command::execution?]
|
|
79
|
+
@key_handlers: Hash[String, KeyHandlerConfig]
|
|
80
|
+
@scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
|
|
81
|
+
@click_handler: ClickHandlerConfig?
|
|
82
|
+
|
|
83
|
+
def initialize: (
|
|
84
|
+
routes: Hash[Symbol, Module],
|
|
85
|
+
actions: Hash[Symbol, ^() -> Command::execution?],
|
|
86
|
+
key_handlers: Hash[String, KeyHandlerConfig],
|
|
87
|
+
scroll_handlers: Hash[Symbol, ScrollHandlerConfig],
|
|
88
|
+
click_handler: ClickHandlerConfig?
|
|
89
|
+
) -> void
|
|
90
|
+
|
|
91
|
+
# Process message and return [model, command] tuple.
|
|
92
|
+
def call: (
|
|
93
|
+
(RatatuiRuby::Event::Key | RatatuiRuby::Event::Mouse | Array[Object]) message,
|
|
94
|
+
_DataModel model
|
|
95
|
+
) -> [_DataModel, Command::execution?]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Builder for keymap DSL.
|
|
99
|
+
class KeymapBuilder
|
|
100
|
+
@handlers: Hash[String, KeyHandlerConfig]
|
|
101
|
+
@guard_stack: Array[^(_DataModel) -> bool]
|
|
102
|
+
|
|
103
|
+
attr_reader handlers: Hash[String, KeyHandlerConfig]
|
|
104
|
+
|
|
105
|
+
def initialize: () -> void
|
|
106
|
+
|
|
107
|
+
def key: (
|
|
108
|
+
String | Symbol key_name,
|
|
109
|
+
(^() -> Command::execution?) | Symbol handler_or_action,
|
|
110
|
+
?route: Symbol?,
|
|
111
|
+
?when: (^(_DataModel) -> bool)?,
|
|
112
|
+
?if: (^(_DataModel) -> bool)?,
|
|
113
|
+
?only: (^(_DataModel) -> bool)?,
|
|
114
|
+
?guard: (^(_DataModel) -> bool)?,
|
|
115
|
+
?unless: (^(_DataModel) -> bool)?,
|
|
116
|
+
?except: (^(_DataModel) -> bool)?,
|
|
117
|
+
?skip: (^(_DataModel) -> bool)?
|
|
118
|
+
) -> void
|
|
119
|
+
|
|
120
|
+
def only: (
|
|
121
|
+
?when: (^(_DataModel) -> bool)?,
|
|
122
|
+
?if: (^(_DataModel) -> bool)?,
|
|
123
|
+
?only: (^(_DataModel) -> bool)?,
|
|
124
|
+
?guard: (^(_DataModel) -> bool)?
|
|
125
|
+
) { () -> void } -> void
|
|
126
|
+
|
|
127
|
+
def skip: (
|
|
128
|
+
?when: (^(_DataModel) -> bool)?,
|
|
129
|
+
?if: (^(_DataModel) -> bool)?,
|
|
130
|
+
?skip: (^(_DataModel) -> bool)?,
|
|
131
|
+
?guard: (^(_DataModel) -> bool)?
|
|
132
|
+
) { () -> void } -> void
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def with_guard: ((^(_DataModel) -> bool)?) { () -> void } -> void
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Builder for mousemap DSL.
|
|
140
|
+
class MousemapBuilder
|
|
141
|
+
@scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
|
|
142
|
+
@click_handler: ClickHandlerConfig?
|
|
143
|
+
|
|
144
|
+
attr_reader scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
|
|
145
|
+
attr_reader click_handler: ClickHandlerConfig?
|
|
146
|
+
|
|
147
|
+
def initialize: () -> void
|
|
148
|
+
|
|
149
|
+
def click: ((^(Integer, Integer) -> Command::execution?) | Symbol handler_or_action) -> void
|
|
150
|
+
def scroll: (:up | :down direction, (^() -> Command::execution?) | Symbol handler_or_action) -> void
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
#++
|
|
5
|
+
|
|
6
|
+
module Rooibos
|
|
7
|
+
# MVU runtime event loop.
|
|
8
|
+
class Runtime
|
|
9
|
+
# Active command tracking entry.
|
|
10
|
+
type active_entry = { future: Concurrent::Promises::Future[void], origin: Concurrent::Promises::ResolvableEvent }
|
|
11
|
+
|
|
12
|
+
# Widget type accepted by view functions.
|
|
13
|
+
type renderable = RatatuiRuby::_CustomWidget | RatatuiRuby::widget
|
|
14
|
+
|
|
15
|
+
# Duck type for update result that can be normalized.
|
|
16
|
+
# Steep needs a union type (not interface) for is_a? narrowing.
|
|
17
|
+
type update_result = [Object, Command::execution?] | Command::execution | Object
|
|
18
|
+
|
|
19
|
+
# Duck type for init result that can be normalized.
|
|
20
|
+
type init_result = [Object, Command::execution?] | Command::execution | Object
|
|
21
|
+
|
|
22
|
+
# Duck type for values that can be queried for command-ness.
|
|
23
|
+
interface _MaybeCommand
|
|
24
|
+
def nil?: () -> bool
|
|
25
|
+
def class: () -> Class
|
|
26
|
+
def respond_to?: (Symbol, ?bool) -> bool
|
|
27
|
+
def rooibos_command?: () -> bool
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Starts the MVU event loop (positional fragment).
|
|
31
|
+
def self.run: [Model] (
|
|
32
|
+
?Module? root_fragment,
|
|
33
|
+
?fps: Integer
|
|
34
|
+
) -> Model
|
|
35
|
+
|
|
36
|
+
# Starts the MVU event loop (explicit parameters).
|
|
37
|
+
| [Model] (
|
|
38
|
+
?Module? root_fragment,
|
|
39
|
+
?fps: Integer,
|
|
40
|
+
model: Model,
|
|
41
|
+
view: ^(Model, RatatuiRuby::TUI) -> renderable,
|
|
42
|
+
update: ^(RatatuiRuby::Event, Model) -> update_result?,
|
|
43
|
+
?command: Command::execution?
|
|
44
|
+
) -> Model
|
|
45
|
+
|
|
46
|
+
# Starts the MVU event loop (explicit parameters without fps).
|
|
47
|
+
| [Model] (
|
|
48
|
+
?Module? root_fragment,
|
|
49
|
+
model: Model,
|
|
50
|
+
view: ^(Model, RatatuiRuby::TUI) -> renderable,
|
|
51
|
+
update: ^(RatatuiRuby::Event, Model) -> update_result?,
|
|
52
|
+
?command: Command::execution?
|
|
53
|
+
) -> Model
|
|
54
|
+
|
|
55
|
+
# Normalizes Init callable return value to [model, command] tuple.
|
|
56
|
+
def self.normalize_init: [Model] (init_result result) -> [Model?, Command::execution?]
|
|
57
|
+
QUIT: Object
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def self.validate_view_return!: (renderable? widget) -> void
|
|
62
|
+
def self.normalize_update_return: [Model] (update_result? result, Model? previous_model) -> [Model?, Command::execution?]
|
|
63
|
+
def self.validate_ractor_shareable!: [T] (T object, String name) -> void
|
|
64
|
+
def self.fragment_from_kwargs: (Module? root_fragment, ?model: untyped, ?view: untyped, ?update: untyped, ?command: untyped) -> Module
|
|
65
|
+
def self.fragment_invariant!: (String param) -> void
|
|
66
|
+
def self.init_callable: [Model] () -> ^() -> [Model?, Command::execution?]
|
|
67
|
+
def self.start_runtime: () -> untyped
|
|
68
|
+
def self.draw_view: () -> void
|
|
69
|
+
def self.handle_ratatui_event: () -> void
|
|
70
|
+
def self.handle_sync: () -> void
|
|
71
|
+
QUEUE_EMPTY: Object
|
|
72
|
+
def self.send_pending_messages: (?dispatch: bool) -> void
|
|
73
|
+
def self.dispatch_command: () -> void
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
#++
|
|
5
|
+
|
|
6
|
+
module Rooibos
|
|
7
|
+
# Convenient short aliases for Rooibos APIs.
|
|
8
|
+
module Shortcuts
|
|
9
|
+
# Short alias for Command.
|
|
10
|
+
module Cmd
|
|
11
|
+
def self.exit: () -> Command::Exit
|
|
12
|
+
def self.sh: (String command, Symbol | Class tag) -> Command::System
|
|
13
|
+
def self.map: (Command::execution inner_command) { (Array[untyped]) -> Array[untyped] } -> Command::Mapped
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/sig/rooibos.rbs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
#++
|
|
5
|
+
|
|
6
|
+
# The Elm Architecture for Ruby.
|
|
7
|
+
module Rooibos
|
|
8
|
+
# Starts the MVU event loop (positional fragment).
|
|
9
|
+
def self.run: [Model] (
|
|
10
|
+
?Module? root_fragment,
|
|
11
|
+
?fps: Integer
|
|
12
|
+
) -> Model
|
|
13
|
+
|
|
14
|
+
# Starts the MVU event loop (explicit parameters).
|
|
15
|
+
| [Model] (
|
|
16
|
+
?Module? root_fragment,
|
|
17
|
+
?fps: Integer,
|
|
18
|
+
model: Model,
|
|
19
|
+
view: ^(Model, RatatuiRuby::TUI) -> Runtime::renderable,
|
|
20
|
+
update: ^(RatatuiRuby::Event, Model) -> Runtime::update_result?,
|
|
21
|
+
?command: Command::execution?
|
|
22
|
+
) -> Model
|
|
23
|
+
|
|
24
|
+
# Starts the MVU event loop (explicit parameters without fps).
|
|
25
|
+
| [Model] (
|
|
26
|
+
?Module? root_fragment,
|
|
27
|
+
model: Model,
|
|
28
|
+
view: ^(Model, RatatuiRuby::TUI) -> Runtime::renderable,
|
|
29
|
+
update: ^(RatatuiRuby::Event, Model) -> Runtime::update_result?,
|
|
30
|
+
?command: Command::execution?
|
|
31
|
+
) -> Model
|
|
32
|
+
|
|
33
|
+
# Wraps a command with a routing prefix.
|
|
34
|
+
def self.route: (Command::execution command, Symbol prefix) -> Command::Mapped
|
|
35
|
+
|
|
36
|
+
# Delegates a prefixed message to a child fragment's UPDATE.
|
|
37
|
+
def self.delegate: (
|
|
38
|
+
untyped message,
|
|
39
|
+
Symbol prefix,
|
|
40
|
+
^(Array[untyped]?, untyped) -> [untyped, Command::execution?] child_update,
|
|
41
|
+
untyped child_model
|
|
42
|
+
) -> ([untyped, Command::execution?] | nil)
|
|
43
|
+
|
|
44
|
+
# Normalizes Init callable return value to [model, command] tuple.
|
|
45
|
+
def self.normalize_init: [Model] (Runtime::init_result result) -> [Model?, Command::execution?]
|
|
46
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
%>
|
|
5
|
+
<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
|
+
<title><%= page_title %> - Example Viewer</title>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
var rdoc_rel_prefix = "<%= doc_root_link.sub('index.html', '') %>";
|
|
14
|
+
var index_rel_prefix = "<%= '../' * (relative_path.split('/').size - 1) %>";
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_navigation.js" defer></script>
|
|
18
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_data.js" defer></script>
|
|
19
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_ranker.js" defer></script>
|
|
20
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_controller.js" defer></script>
|
|
21
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/aliki.js" defer></script>
|
|
22
|
+
|
|
23
|
+
<link href="<%= doc_root_link.sub('index.html', '') %>css/rdoc.css" rel="stylesheet">
|
|
24
|
+
<link href="<%= doc_root_link.sub('index.html', '') %>custom.css" rel="stylesheet">
|
|
25
|
+
</head>
|
|
26
|
+
<body class="file<%= ' has-toc' unless toc_items.empty? %>">
|
|
27
|
+
<%= icons_svg %>
|
|
28
|
+
<header class="top-navbar">
|
|
29
|
+
<div class="navbar-brand">
|
|
30
|
+
Example Viewer
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Desktop search bar -->
|
|
34
|
+
<div class="navbar-search navbar-search-desktop" role="search">
|
|
35
|
+
<form action="#" method="get" accept-charset="utf-8">
|
|
36
|
+
<input id="search-field" role="combobox" aria-label="Search"
|
|
37
|
+
aria-autocomplete="list" aria-controls="search-results-desktop"
|
|
38
|
+
type="text" name="search" placeholder="Search (/) examples..."
|
|
39
|
+
spellcheck="false" autocomplete="off"
|
|
40
|
+
title="Type to search, Up and Down to navigate, Enter to load">
|
|
41
|
+
<ul id="search-results-desktop" aria-label="Search Results"
|
|
42
|
+
aria-busy="false" aria-expanded="false"
|
|
43
|
+
aria-atomic="false" class="initially-hidden search-results"></ul>
|
|
44
|
+
</form>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Mobile search icon button -->
|
|
48
|
+
<button id="search-toggle" class="navbar-search-mobile" aria-label="Open search" type="button">
|
|
49
|
+
<span aria-hidden="true">🔍</span>
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
<button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
|
|
53
|
+
<span class="theme-toggle-icon" aria-hidden="true">🌙</span>
|
|
54
|
+
</button>
|
|
55
|
+
</header>
|
|
56
|
+
|
|
57
|
+
<!-- Search Modal (Mobile) -->
|
|
58
|
+
<div id="search-modal" class="search-modal" hidden aria-modal="true" role="dialog" aria-label="Search">
|
|
59
|
+
<div class="search-modal-backdrop"></div>
|
|
60
|
+
<div class="search-modal-content">
|
|
61
|
+
<div class="search-modal-header">
|
|
62
|
+
<form class="search-modal-form" action="#" method="get" accept-charset="utf-8">
|
|
63
|
+
<span class="search-modal-icon" aria-hidden="true">🔍</span>
|
|
64
|
+
<input id="search-field-mobile" role="combobox" aria-label="Search"
|
|
65
|
+
aria-autocomplete="list" aria-controls="search-results-mobile"
|
|
66
|
+
type="text" name="search" placeholder="Search examples"
|
|
67
|
+
spellcheck="false" autocomplete="off">
|
|
68
|
+
<button type="button" class="search-modal-close" aria-label="Close search" id="search-modal-close">
|
|
69
|
+
<span aria-hidden="true">esc</span>
|
|
70
|
+
</button>
|
|
71
|
+
</form>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="search-modal-body">
|
|
74
|
+
<ul id="search-results-mobile" aria-label="Search Results"
|
|
75
|
+
aria-busy="false" aria-expanded="false"
|
|
76
|
+
aria-atomic="false" class="search-results search-modal-results initially-hidden"></ul>
|
|
77
|
+
<div class="search-modal-empty">
|
|
78
|
+
<p>No recent searches</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<nav id="navigation" role="navigation">
|
|
85
|
+
<div id="fileindex-section" class="nav-section">
|
|
86
|
+
<details class="nav-section-collapsible" open>
|
|
87
|
+
<summary class="nav-section-header">
|
|
88
|
+
<span class="nav-section-icon">
|
|
89
|
+
<svg><use href="#icon-file"></use></svg>
|
|
90
|
+
</span>
|
|
91
|
+
<span class="nav-section-title">Examples</span>
|
|
92
|
+
<span class="nav-section-chevron">
|
|
93
|
+
<svg><use href="#icon-chevron"></use></svg>
|
|
94
|
+
</span>
|
|
95
|
+
</summary>
|
|
96
|
+
<ul class="nav-list">
|
|
97
|
+
<li><a href="<%= doc_root_link %>">← Back to Docs</a></li>
|
|
98
|
+
</ul>
|
|
99
|
+
</details>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="nav-section">
|
|
102
|
+
<details class="nav-section-collapsible" open>
|
|
103
|
+
<summary class="nav-section-header">
|
|
104
|
+
<span class="nav-section-icon">
|
|
105
|
+
<svg><use href="#icon-layers"></use></svg>
|
|
106
|
+
</span>
|
|
107
|
+
<span class="nav-section-title">Files</span>
|
|
108
|
+
<span class="nav-section-chevron">
|
|
109
|
+
<svg><use href="#icon-chevron"></use></svg>
|
|
110
|
+
</span>
|
|
111
|
+
</summary>
|
|
112
|
+
<ul class="link-list nav-list">
|
|
113
|
+
<%= render_tree(tree_data, relative_path, current_file_html) %>
|
|
114
|
+
</ul>
|
|
115
|
+
</details>
|
|
116
|
+
</div>
|
|
117
|
+
</nav>
|
|
118
|
+
|
|
119
|
+
<main role="main">
|
|
120
|
+
<div class="breadcrumb">
|
|
121
|
+
<%= breadcrumb_path %>
|
|
122
|
+
</div>
|
|
123
|
+
<%= file_header_html %>
|
|
124
|
+
<div class="content">
|
|
125
|
+
<%= file_content_html %>
|
|
126
|
+
</div>
|
|
127
|
+
</main>
|
|
128
|
+
|
|
129
|
+
<% unless toc_items.empty? %>
|
|
130
|
+
<aside class="table-of-contents" role="complementary" aria-label="Table of Contents">
|
|
131
|
+
<div class="toc-sticky">
|
|
132
|
+
<div class="toc-list">
|
|
133
|
+
<h3>On This Page</h3>
|
|
134
|
+
<%= render_toc(toc_items) %>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</aside>
|
|
138
|
+
<% end %>
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
<script>
|
|
142
|
+
const modes = ['auto', 'light', 'dark'];
|
|
143
|
+
const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
|
|
144
|
+
|
|
145
|
+
function setColorMode(mode) {
|
|
146
|
+
if (mode === 'auto') {
|
|
147
|
+
document.documentElement.removeAttribute('data-theme');
|
|
148
|
+
const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
|
|
149
|
+
document.documentElement.setAttribute('data-theme', systemTheme);
|
|
150
|
+
} else {
|
|
151
|
+
document.documentElement.setAttribute('data-theme', mode);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const icon = icons[mode];
|
|
155
|
+
const toggle = document.getElementById('theme-toggle');
|
|
156
|
+
toggle.querySelector('.theme-toggle-icon').textContent = icon;
|
|
157
|
+
|
|
158
|
+
localStorage.setItem('rdoc-theme', mode);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cycleColorMode() {
|
|
162
|
+
const current = localStorage.getItem('rdoc-theme') || 'auto';
|
|
163
|
+
const currentIndex = modes.indexOf(current);
|
|
164
|
+
const nextMode = modes[(currentIndex + 1) % modes.length];
|
|
165
|
+
setColorMode(nextMode);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
|
|
169
|
+
setColorMode(savedMode);
|
|
170
|
+
</script>
|
|
171
|
+
</body>
|
|
172
|
+
</html>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
image: archlinux
|
|
5
|
+
packages:
|
|
6
|
+
- bash
|
|
7
|
+
- base-devel
|
|
8
|
+
- curl
|
|
9
|
+
- openssl
|
|
10
|
+
- libyaml
|
|
11
|
+
- zlib
|
|
12
|
+
- readline
|
|
13
|
+
- gdbm
|
|
14
|
+
- ncurses
|
|
15
|
+
- libffi
|
|
16
|
+
- clang
|
|
17
|
+
- git
|
|
18
|
+
artifacts:
|
|
19
|
+
- <%= gem_name %>/pkg/<%= gem_filename %>
|
|
20
|
+
sources:
|
|
21
|
+
- https://git.sr.ht/~kerrick/<%= gem_name %>
|
|
22
|
+
tasks:
|
|
23
|
+
- setup: |
|
|
24
|
+
curl https://mise.jdx.dev/install.sh | sh
|
|
25
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
|
|
26
|
+
echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
|
|
27
|
+
echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
|
|
28
|
+
echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
|
|
29
|
+
echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
|
|
30
|
+
. ~/.buildenv
|
|
31
|
+
export CI="true"
|
|
32
|
+
cd <%= gem_name %>
|
|
33
|
+
sed -i 's/ruby = .*/ruby = "<%= ruby_version %>"/' mise.toml
|
|
34
|
+
mise install
|
|
35
|
+
mise x -- pip install reuse
|
|
36
|
+
mise x -- gem install bundler:<%= bundler_version %>
|
|
37
|
+
mise reshim
|
|
38
|
+
mise x -- bundle config set --local frozen 'true'
|
|
39
|
+
mise x -- bundle install
|
|
40
|
+
- test: |
|
|
41
|
+
. ~/.buildenv
|
|
42
|
+
cd <%= gem_name %>
|
|
43
|
+
echo "Testing Ruby <%= ruby_version %>"
|
|
44
|
+
mise x -- bundle exec rake test
|
|
45
|
+
- lint: |
|
|
46
|
+
. ~/.buildenv
|
|
47
|
+
cd <%= gem_name %>
|
|
48
|
+
echo "Linting Ruby <%= ruby_version %>"
|
|
49
|
+
mise x -- bundle exec rake lint
|
|
50
|
+
- package: |
|
|
51
|
+
. ~/.buildenv
|
|
52
|
+
cd <%= gem_name %>
|
|
53
|
+
mise x -- bundle exec rake build
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: LGPL-3.0-or-later
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<!DOCTYPE html>
|
|
8
|
+
<html>
|
|
9
|
+
<head>
|
|
10
|
+
<title><%= project_name %> documentation</title>
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<style>
|
|
13
|
+
:root { color-scheme: light dark; }
|
|
14
|
+
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.5; color: light-dark(#111, #eee); background: light-dark(#fff, #111); }
|
|
15
|
+
h1 { border-bottom: 2px solid light-dark(#eee, #333); padding-bottom: 0.5rem; }
|
|
16
|
+
ul { list-style: none; padding: 0; }
|
|
17
|
+
li { margin: 0.5rem 0; border: 1px solid light-dark(#ddd, #444); border-radius: 4px; }
|
|
18
|
+
a { display: block; padding: 1rem; text-decoration: none; color: light-dark(#0055aa, #44aaff); font-weight: bold; }
|
|
19
|
+
a:hover { background: light-dark(#f5f5f5, #222); }
|
|
20
|
+
.meta { font-weight: normal; color: light-dark(#666, #aaa); font-size: 0.9em; float: right; }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<h1><%= project_name %> documentation</h1>
|
|
25
|
+
<ul>
|
|
26
|
+
<% versions.each do |version| %>
|
|
27
|
+
<li>
|
|
28
|
+
<a href='<%= version.slug %>/index.html'>
|
|
29
|
+
<%= version.name %>
|
|
30
|
+
<span class='meta'>
|
|
31
|
+
<% if version.latest? %>
|
|
32
|
+
Latest
|
|
33
|
+
<% elsif version.edge? %>
|
|
34
|
+
Edge
|
|
35
|
+
<% else %>
|
|
36
|
+
Historical
|
|
37
|
+
<% end %>
|
|
38
|
+
</span>
|
|
39
|
+
</a>
|
|
40
|
+
</li>
|
|
41
|
+
<% end %>
|
|
42
|
+
</ul>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
data/tasks/steep.rake
ADDED