thor-interactive 0.1.0.pre.5 → 0.1.0.pre.6
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/README.md +143 -399
- data/docs/assets/thor-interactive-wide.png +0 -0
- data/docs/assets/thor-interactive.png +0 -0
- data/examples/tui_demo.rb +94 -0
- data/lib/thor/interactive/command.rb +12 -1
- data/lib/thor/interactive/command_dispatch.rb +410 -0
- data/lib/thor/interactive/shell.rb +41 -517
- data/lib/thor/interactive/tui/output_buffer.rb +87 -0
- data/lib/thor/interactive/tui/ratatui_shell.rb +606 -0
- data/lib/thor/interactive/tui/spinner.rb +107 -0
- data/lib/thor/interactive/tui/status_bar.rb +58 -0
- data/lib/thor/interactive/tui/text_input.rb +218 -0
- data/lib/thor/interactive/tui/theme.rb +158 -0
- data/lib/thor/interactive/tui.rb +14 -0
- data/lib/thor/interactive/version_constant.rb +1 -1
- metadata +14 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79cee8c04a17409c31979f43c38dc2d03bd725e894fd4970970c0c5784a768ac
|
|
4
|
+
data.tar.gz: 91bb06f9ddefce2a5d150efc644aa10a77e3abe8099854dccade6e5770941339
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6b99410867f9b4008f106dee56e87446b85e2473180369e211b66ea5b41cef2960e7da7848d399f7aeee47f3f28b0dc450bd1d623f2124ff75a1f7873506f3a
|
|
7
|
+
data.tar.gz: 1a8c863bb31996ee2378a26845c4793cd0260fbf067813aecf285fb3f9aeb9bd14001538e5613c12862a79b065285cf5642c75942585eac0244cb10365773169
|
data/README.md
CHANGED
|
@@ -1,38 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
<img src="/docs/assets/thor-interactive-wide.png" alt="thor-interactive" height="80px">
|
|
2
2
|
|
|
3
|
-
Turn any Thor CLI into an interactive
|
|
3
|
+
Turn any Thor CLI into an interactive terminal application with persistent state, auto-completion, and a rich TUI powered by [ratatui_ruby](https://www.ratatui-ruby.dev/).
|
|
4
4
|
|
|
5
|
-
Thor::Interactive
|
|
5
|
+
Thor::Interactive converts your existing Thor command-line applications into interactive sessions — a Claude Code-like terminal UI with multi-line input, a status bar, animated spinners, and theming. Perfect for RAG pipelines, database tools, or any CLI that benefits from persistent connections and cached state.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **TUI Mode**: Rich terminal UI with multi-line input, status bar, spinner, tab completion overlay, and theming — powered by Rust via [ratatui_ruby](https://www.ratatui-ruby.dev/)
|
|
10
10
|
- **State Persistence**: Maintains class variables and instance state between commands
|
|
11
|
-
- **Auto-completion**: Tab completion for command names and
|
|
12
|
-
- **Default Handlers**: Configurable fallback for non-command input
|
|
13
|
-
- **Command History**: Persistent
|
|
14
|
-
- **
|
|
15
|
-
- **Graceful Exit**: Proper handling of Ctrl+C interrupts and Ctrl+D/exit commands
|
|
11
|
+
- **Auto-completion**: Tab completion for command names, options, and paths
|
|
12
|
+
- **Default Handlers**: Configurable fallback for non-command input (great for natural language interfaces)
|
|
13
|
+
- **Command History**: Persistent history with up/down arrow navigation
|
|
14
|
+
- **Graceful Degradation**: Falls back to a Reline-based REPL if `ratatui_ruby` is not installed
|
|
16
15
|
|
|
17
|
-
##
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Installation
|
|
18
19
|
|
|
19
20
|
Add to your application's Gemfile:
|
|
20
21
|
|
|
21
22
|
```ruby
|
|
22
23
|
gem 'thor-interactive'
|
|
24
|
+
gem 'ratatui_ruby', '~> 1.4' # Enables TUI mode
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
gem install thor-interactive
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Quick Start
|
|
32
|
-
|
|
33
|
-
### Option 1: Add Interactive Command (Recommended)
|
|
34
|
-
|
|
35
|
-
Add one line to your Thor class to get an `interactive` command:
|
|
27
|
+
### Basic Usage
|
|
36
28
|
|
|
37
29
|
```ruby
|
|
38
30
|
require 'thor'
|
|
@@ -41,462 +33,214 @@ require 'thor/interactive'
|
|
|
41
33
|
class MyApp < Thor
|
|
42
34
|
include Thor::Interactive::Command
|
|
43
35
|
|
|
44
|
-
|
|
36
|
+
configure_interactive(
|
|
37
|
+
ui_mode: :tui,
|
|
38
|
+
prompt: "myapp> "
|
|
39
|
+
)
|
|
40
|
+
|
|
45
41
|
desc "hello NAME", "Say hello"
|
|
46
42
|
def hello(name)
|
|
47
43
|
puts "Hello #{name}!"
|
|
48
44
|
end
|
|
45
|
+
|
|
46
|
+
desc "search QUERY", "Search for something"
|
|
47
|
+
def search(query)
|
|
48
|
+
puts "Searching for: #{query}"
|
|
49
|
+
end
|
|
49
50
|
end
|
|
51
|
+
|
|
52
|
+
MyApp.start(ARGV)
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
Then `bundle install` and run:
|
|
53
56
|
|
|
54
57
|
```bash
|
|
55
58
|
# Normal CLI usage (unchanged)
|
|
56
|
-
ruby myapp.rb hello World
|
|
57
|
-
|
|
58
|
-
# New interactive mode with slash commands
|
|
59
|
-
ruby myapp.rb interactive
|
|
60
|
-
myapp> /hello Alice
|
|
61
|
-
Hello Alice!
|
|
62
|
-
myapp> Natural language input goes to default handler
|
|
63
|
-
myapp> exit
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Option 2: Programmatic Usage
|
|
59
|
+
bundle exec ruby myapp.rb hello World
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
# Interactive TUI mode
|
|
62
|
+
bundle exec ruby myapp.rb interactive
|
|
63
|
+
```
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
require 'thor/interactive'
|
|
65
|
+
> **Note:** `bundle exec` ensures `ratatui_ruby` is loaded. Without it, you'll get the basic Reline REPL instead of the TUI.
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
desc "hello NAME", "Say hello"
|
|
75
|
-
def hello(name)
|
|
76
|
-
puts "Hello #{name}!"
|
|
77
|
-
end
|
|
78
|
-
end
|
|
67
|
+
### Key Bindings
|
|
79
68
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
| Key | Action |
|
|
70
|
+
|-----|--------|
|
|
71
|
+
| Enter | Submit input |
|
|
72
|
+
| Shift+Enter | Insert newline (Kitty protocol terminals) |
|
|
73
|
+
| Ctrl+N | Toggle multi-line mode (fallback for older terminals) |
|
|
74
|
+
| Ctrl+J | Always submit (even in multi-line mode) |
|
|
75
|
+
| Tab | Auto-complete commands |
|
|
76
|
+
| Ctrl+C | Clear input / double-tap to exit |
|
|
77
|
+
| Ctrl+D | Exit |
|
|
78
|
+
| Escape | Clear input / exit multi-line mode |
|
|
79
|
+
| Up/Down | History navigation (single-line) or cursor movement (multi-line) |
|
|
83
80
|
|
|
84
|
-
## State Persistence
|
|
81
|
+
## State Persistence
|
|
85
82
|
|
|
86
|
-
The key benefit is maintaining state between commands:
|
|
83
|
+
The key benefit of interactive mode is maintaining state between commands. In normal CLI mode, each invocation starts fresh. In interactive mode, a single instance persists — so expensive connections, caches, and counters survive between commands:
|
|
87
84
|
|
|
88
85
|
```ruby
|
|
89
|
-
class
|
|
86
|
+
class ProjectApp < Thor
|
|
90
87
|
include Thor::Interactive::Command
|
|
91
88
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class_variable_set(:@@conversation_history, [])
|
|
89
|
+
@@db = nil
|
|
90
|
+
@@tasks = []
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
configure_interactive(
|
|
93
|
+
ui_mode: :tui,
|
|
94
|
+
prompt: "project> "
|
|
95
|
+
)
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
desc "connect HOST", "Connect to database"
|
|
98
|
+
def connect(host)
|
|
99
|
+
@@db = Database.new(host) # Expensive — only done once
|
|
100
|
+
puts "Connected to #{host}"
|
|
104
101
|
end
|
|
105
102
|
|
|
106
|
-
desc "
|
|
107
|
-
def
|
|
108
|
-
@@
|
|
109
|
-
|
|
110
|
-
puts " A: #{item[:output]}"
|
|
111
|
-
end
|
|
103
|
+
desc "add TASK", "Add a task"
|
|
104
|
+
def add(task)
|
|
105
|
+
@@tasks << {name: task, created_at: Time.now}
|
|
106
|
+
puts "Added: #{task} (#{@@tasks.size} total)"
|
|
112
107
|
end
|
|
113
|
-
end
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
In interactive mode:
|
|
117
|
-
```bash
|
|
118
|
-
ruby rag_app.rb interactive
|
|
119
|
-
|
|
120
|
-
rag> /ask What is Ruby?
|
|
121
|
-
# LLM initializes once
|
|
122
|
-
Ruby is a programming language...
|
|
123
|
-
|
|
124
|
-
rag> /ask Tell me more
|
|
125
|
-
# LLM client reused, conversation context maintained
|
|
126
|
-
Based on our previous discussion about Ruby...
|
|
127
|
-
|
|
128
|
-
rag> What's the difference between Ruby and Python?
|
|
129
|
-
# Natural language goes directly to default handler (ask command)
|
|
130
|
-
Ruby and Python differ in several ways...
|
|
131
|
-
|
|
132
|
-
rag> /history
|
|
133
|
-
1. Q: What is Ruby?
|
|
134
|
-
A: Ruby is a programming language...
|
|
135
|
-
2. Q: Tell me more
|
|
136
|
-
A: Based on our previous discussion about Ruby...
|
|
137
|
-
3. Q: What's the difference between Ruby and Python?
|
|
138
|
-
A: Ruby and Python differ in several ways...
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Configuration
|
|
142
|
-
|
|
143
|
-
Configure interactive behavior:
|
|
144
|
-
|
|
145
|
-
```ruby
|
|
146
|
-
class MyApp < Thor
|
|
147
|
-
include Thor::Interactive::Command
|
|
148
108
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
default_handler: proc do |input, thor_instance|
|
|
154
|
-
# Handle unrecognized input
|
|
155
|
-
# IMPORTANT: Use direct method calls, NOT invoke(), to avoid Thor's
|
|
156
|
-
# silent failure on repeated calls to the same method
|
|
157
|
-
thor_instance.search(input) # ✅ Works repeatedly
|
|
158
|
-
# thor_instance.invoke(:search, [input]) # ❌ Fails after first call
|
|
109
|
+
desc "list", "Show all tasks"
|
|
110
|
+
def list
|
|
111
|
+
@@tasks.each_with_index do |t, i|
|
|
112
|
+
puts "#{i + 1}. #{t[:name]}"
|
|
159
113
|
end
|
|
160
|
-
|
|
114
|
+
end
|
|
161
115
|
|
|
162
|
-
desc "
|
|
163
|
-
def
|
|
164
|
-
puts "
|
|
116
|
+
desc "status", "Show connection and task count"
|
|
117
|
+
def status
|
|
118
|
+
puts "Database: #{@@db ? 'connected' : 'not connected'}"
|
|
119
|
+
puts "Tasks: #{@@tasks.size}"
|
|
165
120
|
end
|
|
166
121
|
end
|
|
167
122
|
```
|
|
168
123
|
|
|
169
|
-
Now unrecognized input gets sent to the search command:
|
|
170
|
-
|
|
171
124
|
```bash
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
myapp> some random text
|
|
176
|
-
Searching for: some random text
|
|
177
|
-
```
|
|
125
|
+
project> /connect localhost
|
|
126
|
+
Connected to localhost
|
|
178
127
|
|
|
179
|
-
|
|
128
|
+
project> /add "Design API"
|
|
129
|
+
Added: Design API (1 total)
|
|
180
130
|
|
|
181
|
-
|
|
131
|
+
project> /add "Write tests"
|
|
132
|
+
Added: Write tests (2 total)
|
|
182
133
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
configure_interactive(
|
|
188
|
-
prompt: "myapp> ",
|
|
189
|
-
allow_nested: false # Default behavior
|
|
190
|
-
)
|
|
191
|
-
end
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
If you try to run `interactive` while already in an interactive session:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
myapp> interactive
|
|
198
|
-
Already in an interactive session.
|
|
199
|
-
To allow nested sessions, configure with: configure_interactive(allow_nested: true)
|
|
134
|
+
project> /status
|
|
135
|
+
Database: connected # Still connected — same instance!
|
|
136
|
+
Tasks: 2
|
|
200
137
|
```
|
|
201
138
|
|
|
202
|
-
|
|
139
|
+
## Configuration
|
|
203
140
|
|
|
204
|
-
|
|
141
|
+
### TUI Options
|
|
205
142
|
|
|
206
143
|
```ruby
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
144
|
+
configure_interactive(
|
|
145
|
+
ui_mode: :tui,
|
|
146
|
+
prompt: "myapp> ",
|
|
147
|
+
theme: :dark, # :default, :dark, :light, :minimal, or custom hash
|
|
148
|
+
status_bar: {
|
|
149
|
+
left: ->(instance) { " MyApp" }, # Left section
|
|
150
|
+
right: ->(instance) { " v1.0 " } # Right section
|
|
151
|
+
},
|
|
152
|
+
spinner_messages: [ # Custom spinner messages (optional)
|
|
153
|
+
"Thinking", "Brewing", "Crunching"
|
|
154
|
+
]
|
|
155
|
+
)
|
|
216
156
|
```
|
|
217
157
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
$ ruby advanced_app.rb interactive
|
|
222
|
-
AdvancedApp Interactive Shell
|
|
223
|
-
Type 'help' for available commands, 'exit' to quit
|
|
224
|
-
|
|
225
|
-
advanced> interactive
|
|
226
|
-
AdvancedApp Interactive Shell (nested level 2)
|
|
227
|
-
Type 'exit' to return to previous level, or 'help' for commands
|
|
158
|
+
### Custom Theme
|
|
228
159
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
160
|
+
```ruby
|
|
161
|
+
configure_interactive(
|
|
162
|
+
ui_mode: :tui,
|
|
163
|
+
theme: {
|
|
164
|
+
error_fg: :light_red,
|
|
165
|
+
input_border: :cyan,
|
|
166
|
+
status_bar_fg: :white,
|
|
167
|
+
status_bar_bg: :dark_gray
|
|
168
|
+
}
|
|
169
|
+
)
|
|
237
170
|
```
|
|
238
171
|
|
|
239
|
-
|
|
172
|
+
See `Thor::Interactive::TUI::Theme::THEMES` for the full list of configurable color keys.
|
|
240
173
|
|
|
241
|
-
|
|
174
|
+
### Default Handlers
|
|
242
175
|
|
|
243
|
-
|
|
244
|
-
# ✅ CORRECT - Works for repeated calls
|
|
245
|
-
configure_interactive(
|
|
246
|
-
default_handler: proc do |input, thor_instance|
|
|
247
|
-
thor_instance.ask(input) # Direct method call
|
|
248
|
-
end
|
|
249
|
-
)
|
|
176
|
+
Route unrecognized input to a command automatically:
|
|
250
177
|
|
|
251
|
-
|
|
178
|
+
```ruby
|
|
252
179
|
configure_interactive(
|
|
180
|
+
ui_mode: :tui,
|
|
181
|
+
prompt: "myapp> ",
|
|
253
182
|
default_handler: proc do |input, thor_instance|
|
|
254
|
-
thor_instance.
|
|
183
|
+
thor_instance.search(input)
|
|
255
184
|
end
|
|
256
185
|
)
|
|
257
186
|
```
|
|
258
187
|
|
|
259
|
-
**Why:** Thor's `invoke` method has internal deduplication that prevents repeated calls to the same method on the same instance. This causes silent failures in interactive mode where users expect to be able to repeat commands.
|
|
260
|
-
|
|
261
|
-
## Advanced Usage
|
|
262
|
-
|
|
263
|
-
### Custom Options
|
|
264
|
-
|
|
265
|
-
Pass options to the interactive command:
|
|
266
|
-
|
|
267
188
|
```bash
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
### Multiple Applications
|
|
272
|
-
|
|
273
|
-
Use the same gem with different Thor applications:
|
|
274
|
-
|
|
275
|
-
```ruby
|
|
276
|
-
# Database CLI
|
|
277
|
-
class DBApp < Thor
|
|
278
|
-
include Thor::Interactive::Command
|
|
279
|
-
configure_interactive(prompt: "db> ")
|
|
280
|
-
end
|
|
189
|
+
myapp> /search thor interactive
|
|
190
|
+
Searching for: thor interactive
|
|
281
191
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
include Thor::Interactive::Command
|
|
285
|
-
configure_interactive(prompt: "api> ")
|
|
286
|
-
end
|
|
192
|
+
myapp> some random text
|
|
193
|
+
Searching for: some random text # No slash needed — default handler kicks in
|
|
287
194
|
```
|
|
288
195
|
|
|
289
|
-
|
|
196
|
+
**Important:** Always use direct method calls in default handlers, not `invoke()`. Thor's `invoke` has internal deduplication that silently prevents repeated calls to the same method.
|
|
290
197
|
|
|
291
|
-
|
|
198
|
+
### All Options
|
|
292
199
|
|
|
293
200
|
```ruby
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
201
|
+
configure_interactive(
|
|
202
|
+
ui_mode: :tui, # :tui for TUI mode (omit for basic REPL)
|
|
203
|
+
prompt: "myapp> ", # Custom prompt
|
|
204
|
+
theme: :dark, # TUI theme
|
|
205
|
+
status_bar: { left: ..., right: ... }, # TUI status bar sections
|
|
206
|
+
spinner_messages: [...], # TUI spinner messages
|
|
207
|
+
history_file: "~/.myapp_history", # Custom history file location
|
|
208
|
+
allow_nested: false, # Prevent nested sessions (default)
|
|
209
|
+
default_handler: proc { |input, i| }, # Handle non-command input
|
|
210
|
+
ctrl_c_behavior: :clear_prompt, # :clear_prompt, :show_help, or :silent
|
|
211
|
+
double_ctrl_c_timeout: 0.5 # Seconds for double Ctrl+C exit
|
|
302
212
|
)
|
|
303
213
|
```
|
|
304
214
|
|
|
305
|
-
##
|
|
215
|
+
## Basic REPL Mode (without ratatui_ruby)
|
|
306
216
|
|
|
307
|
-
|
|
217
|
+
If you don't need the TUI, or `ratatui_ruby` isn't available, thor-interactive provides a Reline-based REPL. Just omit `ui_mode: :tui`:
|
|
308
218
|
|
|
309
|
-
|
|
310
|
-
|
|
219
|
+
```ruby
|
|
220
|
+
class MyApp < Thor
|
|
221
|
+
include Thor::Interactive::Command
|
|
311
222
|
|
|
312
|
-
|
|
223
|
+
configure_interactive(prompt: "myapp> ")
|
|
313
224
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
225
|
+
desc "hello NAME", "Say hello"
|
|
226
|
+
def hello(name)
|
|
227
|
+
puts "Hello #{name}!"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
317
230
|
```
|
|
318
231
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Thor::Interactive creates a persistent instance of your Thor class and invokes commands on that same instance, preserving any instance variables or class variables between commands. This is different from normal CLI usage where each command starts with a fresh instance.
|
|
232
|
+
Or start programmatically:
|
|
322
233
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
- Proper signal handling (Ctrl+C, Ctrl+D)
|
|
327
|
-
- Help system integration
|
|
328
|
-
- Configurable default handlers for non-commands
|
|
234
|
+
```ruby
|
|
235
|
+
Thor::Interactive.start(MyApp, prompt: "custom> ")
|
|
236
|
+
```
|
|
329
237
|
|
|
330
238
|
## Development
|
|
331
239
|
|
|
332
|
-
### Getting Started
|
|
333
|
-
|
|
334
|
-
After checking out the repo:
|
|
335
|
-
|
|
336
240
|
```bash
|
|
337
241
|
bundle install # Install dependencies
|
|
338
|
-
bundle exec rspec # Run
|
|
242
|
+
bundle exec rspec # Run tests (446 examples)
|
|
339
243
|
bundle exec rake build # Build gem
|
|
340
|
-
open coverage/index.html # View coverage report (after running tests)
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Testing
|
|
344
|
-
|
|
345
|
-
The gem includes comprehensive tests organized into unit and integration test suites with **72%+ code coverage**:
|
|
346
|
-
|
|
347
|
-
```bash
|
|
348
|
-
# Run all tests
|
|
349
|
-
bundle exec rspec
|
|
350
|
-
|
|
351
|
-
# Run with detailed output
|
|
352
|
-
bundle exec rspec --format documentation
|
|
353
|
-
|
|
354
|
-
# View coverage report
|
|
355
|
-
open coverage/index.html # Detailed HTML coverage report
|
|
356
|
-
|
|
357
|
-
# Run specific test suites
|
|
358
|
-
bundle exec rspec spec/unit/ # Unit tests only
|
|
359
|
-
bundle exec rspec spec/integration/ # Integration tests only
|
|
360
|
-
|
|
361
|
-
# Run specific test files
|
|
362
|
-
bundle exec rspec spec/unit/shell_spec.rb
|
|
363
|
-
bundle exec rspec spec/integration/shell_integration_spec.rb
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
#### Test Structure
|
|
367
|
-
|
|
368
|
-
```
|
|
369
|
-
spec/
|
|
370
|
-
├── spec_helper.rb # Test configuration and shared setup
|
|
371
|
-
├── support/
|
|
372
|
-
│ ├── test_thor_apps.rb # Test Thor applications (not packaged)
|
|
373
|
-
│ └── capture_helpers.rb # Test utilities for I/O capture
|
|
374
|
-
├── unit/ # Unit tests for individual components
|
|
375
|
-
│ ├── shell_spec.rb # Thor::Interactive::Shell tests
|
|
376
|
-
│ ├── command_spec.rb # Thor::Interactive::Command mixin tests
|
|
377
|
-
│ └── completion_spec.rb # Completion system tests
|
|
378
|
-
└── integration/ # Integration tests for full workflows
|
|
379
|
-
└── shell_integration_spec.rb # End-to-end interactive shell tests
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
#### Test Applications
|
|
383
|
-
|
|
384
|
-
Tests use dedicated Thor applications in `spec/support/test_thor_apps.rb`:
|
|
385
|
-
|
|
386
|
-
- `SimpleTestApp` - Basic Thor app with simple commands
|
|
387
|
-
- `StatefulTestApp` - App with state persistence and default handlers
|
|
388
|
-
- `SubcommandTestApp` - App with Thor subcommands
|
|
389
|
-
- `OptionsTestApp` - App with various Thor options and arguments
|
|
390
|
-
|
|
391
|
-
These test apps are excluded from the packaged gem but provide comprehensive test coverage.
|
|
392
|
-
|
|
393
|
-
### Example Applications
|
|
394
|
-
|
|
395
|
-
The `examples/` directory contains working examples (these ARE packaged with the gem):
|
|
396
|
-
|
|
397
|
-
#### Running the Sample Application
|
|
398
|
-
|
|
399
|
-
```bash
|
|
400
|
-
cd examples
|
|
401
|
-
|
|
402
|
-
# Run in normal CLI mode
|
|
403
|
-
ruby sample_app.rb help
|
|
404
|
-
ruby sample_app.rb hello World
|
|
405
|
-
ruby sample_app.rb count
|
|
406
|
-
ruby sample_app.rb add "Test item"
|
|
407
|
-
|
|
408
|
-
# Run in interactive mode
|
|
409
|
-
ruby sample_app.rb interactive
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
#### Interactive Session Example
|
|
413
|
-
|
|
414
|
-
```bash
|
|
415
|
-
$ ruby sample_app.rb interactive
|
|
416
|
-
SampleApp Interactive Shell
|
|
417
|
-
Type 'help' for available commands, 'exit' to quit
|
|
418
|
-
|
|
419
|
-
sample> hello Alice
|
|
420
|
-
Hello Alice!
|
|
421
|
-
|
|
422
|
-
sample> count
|
|
423
|
-
Count: 1
|
|
424
|
-
|
|
425
|
-
sample> count
|
|
426
|
-
Count: 2 # Note: state persisted!
|
|
427
|
-
|
|
428
|
-
sample> add "Buy groceries"
|
|
429
|
-
Added: Buy groceries
|
|
430
|
-
|
|
431
|
-
sample> add "Walk the dog"
|
|
432
|
-
Added: Walk the dog
|
|
433
|
-
|
|
434
|
-
sample> list
|
|
435
|
-
1. Buy groceries
|
|
436
|
-
2. Walk the dog
|
|
437
|
-
|
|
438
|
-
sample> status
|
|
439
|
-
Counter: 2, Items: 2
|
|
440
|
-
|
|
441
|
-
sample> This is random text that doesn't match a command
|
|
442
|
-
Echo: This is random text that doesn't match a command
|
|
443
|
-
|
|
444
|
-
sample> help
|
|
445
|
-
Available commands:
|
|
446
|
-
hello Say hello to NAME
|
|
447
|
-
count Show and increment counter (demonstrates state persistence)
|
|
448
|
-
add Add item to list (demonstrates state persistence)
|
|
449
|
-
list Show all items
|
|
450
|
-
clear Clear all items
|
|
451
|
-
echo Echo the text back (used as default handler)
|
|
452
|
-
status Show application status
|
|
453
|
-
interactive Start an interactive REPL for this application
|
|
454
|
-
|
|
455
|
-
Special commands:
|
|
456
|
-
help [COMMAND] Show help for command
|
|
457
|
-
exit/quit/q Exit the REPL
|
|
458
|
-
|
|
459
|
-
sample> exit
|
|
460
|
-
Goodbye!
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
#### Key Features Demonstrated
|
|
464
|
-
|
|
465
|
-
1. **State Persistence**: The counter and items list maintain their values between commands
|
|
466
|
-
2. **Auto-completion**: Try typing `h<TAB>` or `co<TAB>` to see command completion
|
|
467
|
-
3. **Default Handler**: Text that doesn't match a command gets sent to the `echo` command
|
|
468
|
-
4. **Command History**: Use up/down arrows to navigate previous commands
|
|
469
|
-
5. **Error Handling**: Try invalid commands or missing arguments
|
|
470
|
-
6. **Both Modes**: The same application works as traditional CLI and interactive REPL
|
|
471
|
-
|
|
472
|
-
### Performance Testing
|
|
473
|
-
|
|
474
|
-
For applications with expensive initialization (like LLM clients), you can measure the performance benefit:
|
|
475
|
-
|
|
476
|
-
```bash
|
|
477
|
-
# CLI mode - initializes fresh each time
|
|
478
|
-
time ruby sample_app.rb count
|
|
479
|
-
time ruby sample_app.rb count
|
|
480
|
-
time ruby sample_app.rb count
|
|
481
|
-
|
|
482
|
-
# Interactive mode - initializes once, reuses state
|
|
483
|
-
ruby sample_app.rb interactive
|
|
484
|
-
# Then run: count, count, count
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
### Debugging
|
|
488
|
-
|
|
489
|
-
Enable debug mode to see backtraces on errors:
|
|
490
|
-
|
|
491
|
-
```bash
|
|
492
|
-
DEBUG=1 ruby sample_app.rb interactive
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
Or in your application:
|
|
496
|
-
|
|
497
|
-
```ruby
|
|
498
|
-
ENV["DEBUG"] = "1"
|
|
499
|
-
Thor::Interactive.start(MyApp)
|
|
500
244
|
```
|
|
501
245
|
|
|
502
246
|
## Contributing
|
|
Binary file
|
|
Binary file
|