1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
use clap::builder::styling::{Ansi256Color, Color, Style, Styles};
use crate::color::ColorMode;
/// Help styling: bold headers, the brand pink for literals. clap only
/// applies it on a tty and honors NO_COLOR.
const HELP_STYLES: Styles = Styles::styled()
.header(Style::new().bold())
.usage(Style::new().bold())
.literal(Style::new().fg_color(Some(Color::Ansi256(Ansi256Color(212)))))
.placeholder(Style::new().dimmed());
#[derive(clap::Parser)]
#[command(
name = "rat",
version,
about = "Ratatui-powered primitives for shell dashboards",
styles = HELP_STYLES
)]
pub struct Cli {
#[arg(long, value_enum, global = true, default_value_t = ColorMode::Auto)]
pub color: ColorMode,
/// Which built-in palette to use; `auto` asks the terminal
#[arg(
long,
value_enum,
global = true,
default_value_t = crate::theme::AppearanceMode::Auto,
env = "RAT_APPEARANCE"
)]
pub appearance: crate::theme::AppearanceMode,
#[command(subcommand)]
pub command: Command,
}
#[derive(clap::Subcommand)]
pub enum Command {
/// Apply colors and text attributes to text
Style(StyleArgs),
/// Render one-shot progress bars
Bar(BarArgs),
/// Align delimiter-separated rows into columns
Table(TableArgs),
/// Place text blocks side by side or stacked
Join(JoinArgs),
/// Format and parse durations
Duration(DurationArgs),
/// Parse, format, and diff timestamps portably
Date(DateArgs),
/// Render a sparkline from numbers
Spark(SparkArgs),
/// Print a styled log line to stderr
Log(LogArgs),
/// Repaint a frame of output in place, flicker-free
Frame(FrameArgs),
/// Run a command on an interval and repaint its output flicker-free
Watch(WatchArgs),
/// Run a multi-pane dashboard declared in a file
Dashboard(DashboardArgs),
/// Diagnose terminal capabilities
Doctor(DoctorArgs),
/// Pick one or more items from a list
Choose(ChooseArgs),
/// Ask a yes/no question
Confirm(ConfirmArgs),
/// Prompt for a line of input
Input(InputArgs),
/// Fuzzy-filter lines from stdin
Filter(FilterArgs),
/// Show a spinner while a command runs
Spin(SpinArgs),
/// Generate shell completions
Completion(CompletionArgs),
/// Test harness: exit with the given code through AppError mapping.
#[cfg(debug_assertions)]
#[command(name = "__exitcode", hide = true)]
ExitCode(ExitCodeArgs),
/// Test harness: print an environment variable's value, or "unset".
#[cfg(debug_assertions)]
#[command(name = "__env", hide = true)]
Env(EnvArgs),
/// Test harness: print a file's bytes — a portable cat for watch
/// children whose output must track a file's content.
#[cfg(debug_assertions)]
#[command(name = "__cat", hide = true)]
Cat(CatArgs),
/// Test harness: sleep, then print — a portable slow child for
/// staggered-completion fixtures (the cli suite is sh-free).
#[cfg(debug_assertions)]
#[command(name = "__sleep", hide = true)]
Sleep(SleepArgs),
/// Test harness: print the decimals `0..count`, one per line — a
/// portable child that outruns a retention bound.
#[cfg(debug_assertions)]
#[command(name = "__lines", hide = true)]
Lines(LinesArgs),
}
#[cfg(debug_assertions)]
#[derive(clap::Args)]
pub struct LinesArgs {
/// How many lines to print.
pub count: usize,
}
#[cfg(debug_assertions)]
#[derive(clap::Args)]
pub struct SleepArgs {
/// Milliseconds to sleep before printing.
pub millis: u64,
/// What to print after the sleep.
pub text: Option<String>,
}
#[cfg(debug_assertions)]
#[derive(clap::Args)]
pub struct CatArgs {
pub file: std::path::PathBuf,
}
#[cfg(debug_assertions)]
#[derive(clap::Args)]
pub struct EnvArgs {
pub name: String,
}
#[cfg(debug_assertions)]
#[derive(clap::Args)]
pub struct ExitCodeArgs {
pub code: i32,
/// Optional message printed to stderr first
pub stderr_msg: Option<String>,
}
#[derive(clap::Args)]
pub struct StyleArgs {
/// Text to style; reads stdin when omitted. Multiple args join with newlines.
pub text: Vec<String>,
#[arg(long)]
pub bold: bool,
#[arg(long)]
pub faint: bool,
#[arg(long)]
pub italic: bool,
#[arg(long)]
pub underline: bool,
#[arg(long)]
pub strikethrough: bool,
/// Foreground color: name, 256 index, or #rrggbb
#[arg(long, env = "FOREGROUND")]
pub foreground: Option<String>,
/// Background color: name, 256 index, or #rrggbb
#[arg(long, env = "BACKGROUND")]
pub background: Option<String>,
/// Trim whitespace from each line
#[arg(long)]
pub trim: bool,
/// Strip ANSI escapes from input before styling (default)
#[arg(long, overrides_with = "no_strip_ansi")]
pub strip_ansi: bool,
/// Keep ANSI escapes present in the input
#[arg(long)]
pub no_strip_ansi: bool,
/// Draw a border around the text
#[arg(long, value_enum, default_value_t = crate::core::box_model::BorderPreset::None)]
pub border: crate::core::box_model::BorderPreset,
/// Border color: name, 256 index, or #rrggbb
#[arg(long, env = "BORDER_FOREGROUND")]
pub border_color: Option<String>,
/// Title inserted into the top border (may be pre-styled)
#[arg(long)]
pub title: Option<String>,
/// Padding inside the border: "1" | "1 2" | "1 2 3" | "1 2 3 4"
#[arg(long)]
pub padding: Option<String>,
/// Margin outside the border, same shorthand as --padding
#[arg(long)]
pub margin: Option<String>,
/// Content column width in display cells; longer lines truncate
#[arg(long)]
pub width: Option<u16>,
/// Content alignment inside the column
#[arg(long, value_enum, default_value_t = crate::core::measure::Align::Left)]
pub align: crate::core::measure::Align,
/// Marker appended to a truncated line
#[arg(long, default_value = "…")]
pub ellipsis: String,
}
#[derive(clap::Args)]
pub struct BarArgs {
/// Current value
#[arg(long, allow_negative_numbers = true)]
pub value: Option<f64>,
#[arg(long, default_value_t = 100.0, allow_negative_numbers = true)]
pub total: f64,
/// Bar width in cells
#[arg(long, default_value_t = 32)]
pub width: u16,
#[arg(long)]
pub label: Option<String>,
/// Label column width in display cells [default: 34, or the widest batch label]
#[arg(long)]
pub label_width: Option<u16>,
#[arg(long, value_enum, default_value_t = crate::core::bar::BarPreset::Blocks)]
pub preset: crate::core::bar::BarPreset,
/// Override the fill character
#[arg(long)]
pub fill: Option<char>,
/// Override the empty character
#[arg(long)]
pub empty: Option<char>,
/// Fill color; wins over --thresholds when given (default: accent)
#[arg(long)]
pub fill_color: Option<String>,
#[arg(long, default_value = "muted")]
pub empty_color: String,
/// State word appended after the annotations
#[arg(long)]
pub state: Option<String>,
#[arg(long, value_enum, default_value_t = crate::core::bar::Annotation::Both)]
pub annotation: crate::core::bar::Annotation,
/// Color the fill by percentage band, e.g. "33:196,66:214,100:42"
#[arg(long)]
pub thresholds: Option<String>,
/// Field delimiter for stdin batch rows
#[arg(long, default_value_t = '\t')]
pub delimiter: char,
/// Render a moving block instead of progress (unknown total)
#[arg(long)]
pub indeterminate: bool,
/// Animation step for --indeterminate
#[arg(long, default_value_t = 0)]
pub tick: u64,
}
#[derive(clap::Args)]
pub struct TableArgs {
/// Field delimiter for stdin rows
#[arg(long, default_value_t = '\t')]
pub delimiter: char,
/// Per-column widths in display cells by position; empty entries auto-size ("27,,8")
#[arg(long)]
pub widths: Option<String>,
/// Per-column alignment by position: l, r, or c ("l,r,r")
#[arg(long)]
pub align: Option<String>,
/// Per-column overflow by position: truncate or wrap ("truncate,wrap")
#[arg(long)]
pub overflow: Option<String>,
/// Text placed between columns
#[arg(long, default_value = " ")]
pub separator: String,
/// Marker appended to a truncated cell
#[arg(long, default_value = "…")]
pub ellipsis: String,
}
#[derive(clap::Args)]
pub struct JoinArgs {
/// Text blocks to join, one positional argument each
#[arg(required_unless_present = "file", conflicts_with = "file")]
pub blocks: Vec<String>,
/// Read a block from a file (repeatable; - reads stdin)
#[arg(long, short = 'f')]
pub file: Vec<std::path::PathBuf>,
/// Join side by side (default)
#[arg(long, conflicts_with = "vertical")]
pub horizontal: bool,
/// Stack blocks instead of joining side by side
#[arg(long)]
pub vertical: bool,
/// Spaces (horizontal) or blank lines (vertical) between blocks
#[arg(long, default_value_t = 0)]
pub gap: u16,
/// Block alignment: top/middle/bottom beside, left/center/right stacked
#[arg(long, value_enum)]
pub align: Option<crate::core::join::JoinAlign>,
/// Stack vertically when the joined width exceeds the available width
/// (RAT_WIDTH, then the terminal; no signal keeps blocks beside)
#[arg(long, conflicts_with = "vertical")]
pub fit: bool,
/// Available width for --fit in display cells (implies --fit)
#[arg(long, conflicts_with = "vertical")]
pub max_width: Option<u16>,
}
#[derive(clap::Args)]
pub struct DurationArgs {
/// Seconds to format, or a duration string with --seconds
pub value: String,
/// Parse a duration string ("1h33m") and print integer seconds
#[arg(long, conflicts_with_all = ["ms", "format"])]
pub seconds: bool,
/// Treat the value as milliseconds
#[arg(long)]
pub ms: bool,
#[arg(long, value_enum, default_value_t = crate::core::duration::DurationFormat::Compact)]
pub format: crate::core::duration::DurationFormat,
}
#[derive(clap::Args)]
pub struct DateArgs {
/// "now" (default), epoch seconds, or an RFC3339 timestamp
pub value: Option<String>,
/// Print epoch seconds
#[arg(long, conflicts_with_all = ["format", "relative", "since", "until"])]
pub epoch: bool,
/// strftime output format
#[arg(long)]
pub format: Option<String>,
/// Use UTC instead of the local zone
#[arg(long)]
pub utc: bool,
/// Phrase the timestamp relative to now
#[arg(long, conflicts_with_all = ["format", "since", "until"])]
pub relative: bool,
/// Seconds elapsed from this timestamp to the value
#[arg(long, conflicts_with = "until")]
pub since: Option<String>,
/// Seconds remaining from the value to this timestamp
#[arg(long)]
pub until: Option<String>,
}
#[derive(clap::Args)]
pub struct SparkArgs {
/// Numbers to plot; reads stdin when omitted
#[arg(allow_negative_numbers = true)]
pub values: Vec<String>,
/// Clamp the lower bound
#[arg(long, allow_negative_numbers = true)]
pub min: Option<f64>,
/// Clamp the upper bound
#[arg(long, allow_negative_numbers = true)]
pub max: Option<f64>,
/// Color for the sparkline
#[arg(long = "spark-color", visible_alias = "color-fg")]
pub spark_color: Option<String>,
}
#[derive(clap::Args)]
pub struct LogArgs {
/// Message text; joined with single spaces
pub text: Vec<String>,
/// Log level tag
#[arg(short = 'l', long, value_enum)]
pub level: Option<LogLevel>,
/// Minimum level to emit (lower levels are dropped)
#[arg(long, value_enum, env = "RAT_LOG_LEVEL")]
pub min_level: Option<LogLevel>,
/// Prefix with the current time in this strftime format
#[arg(short = 't', long)]
pub time: Option<String>,
/// Append plain text to this file instead of stderr
#[arg(short = 'o', long)]
pub file: Option<std::path::PathBuf>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, clap::ValueEnum)]
pub enum LogLevel {
Debug,
Info,
Warn,
Error,
Fatal,
}
#[derive(clap::Args)]
pub struct FrameArgs {
#[command(subcommand)]
pub action: Option<FrameAction>,
/// State file path (defaults to a per-terminal temp file)
#[arg(long)]
pub state: Option<std::path::PathBuf>,
/// Override the detected terminal width
#[arg(long)]
pub width: Option<u16>,
/// Skip synchronized-output escapes
#[arg(long)]
pub no_sync: bool,
/// Leave the cursor visible while painting
#[arg(long)]
pub no_hide_cursor: bool,
/// Forget the previous frame without painting
#[arg(long, conflicts_with_all = ["finish", "clear"])]
pub reset: bool,
/// Show the cursor, close any open frame, and forget state
#[arg(long, conflicts_with = "clear")]
pub finish: bool,
/// Erase the painted frame, show the cursor, and forget state
#[arg(long)]
pub clear: bool,
}
#[derive(clap::Subcommand)]
pub enum FrameAction {
/// Emit the begin-synchronized-update escape
Begin,
/// Emit the end-synchronized-update escape
End,
}
#[derive(clap::Args)]
pub struct WatchArgs {
/// Refresh interval, e.g. "2s", "500ms", "1m" (default 2s; omit
/// beside --trigger for trigger-only refresh)
#[arg(short = 'n', long)]
pub interval: Option<String>,
/// Refresh on an external event: fifo:PATH, file:PATH, or fd:N
/// (repeatable)
#[arg(long, value_name = "SPEC", conflicts_with = "once")]
pub trigger: Vec<String>,
/// Collapse trigger fires into one refresh per window
#[arg(
long,
value_name = "DURATION",
default_value = "250ms",
requires = "trigger"
)]
pub trigger_debounce: String,
/// Run one tick and exit
#[arg(long)]
pub once: bool,
/// Clear the screen before the first frame (atomically, inside it)
#[arg(long)]
pub clear: bool,
/// Leave the cursor visible
#[arg(long)]
pub no_hide_cursor: bool,
/// Skip synchronized-output escapes
#[arg(long)]
pub no_sync: bool,
/// Run the command through `sh -c`
#[arg(long)]
pub shell: bool,
/// Bold title line above the output
#[arg(long)]
pub title: Option<String>,
/// Cap the painted height (defaults to terminal height minus two)
#[arg(long)]
pub max_height: Option<u16>,
/// Directory the `S` key writes snapshots into (defaults to the
/// current directory)
#[arg(long, env = "RAT_SNAPSHOT_DIR", value_name = "DIR")]
pub snapshot_dir: Option<std::path::PathBuf>,
/// Keep escape sequences in snapshots instead of stripping them
#[arg(long)]
pub snapshot_ansi: bool,
/// Chop long lines instead of wrapping (toggle at runtime with `w`)
#[arg(long)]
pub no_wrap: bool,
/// Command to run each tick (after --)
#[arg(last = true, required = true)]
pub command: Vec<String>,
}
#[derive(clap::Args)]
pub struct DashboardArgs {
/// Dashboard declaration file (KDL)
pub file: std::path::PathBuf,
/// Run every pane once and exit
#[arg(long)]
pub once: bool,
/// Clear the screen before the first frame (atomically, inside it)
#[arg(long)]
pub clear: bool,
/// Leave the cursor visible
#[arg(long)]
pub no_hide_cursor: bool,
/// Skip synchronized-output escapes
#[arg(long)]
pub no_sync: bool,
/// Cap the painted height (defaults to terminal height minus two)
#[arg(long)]
pub max_height: Option<u16>,
/// Directory the `S` key writes snapshots into (defaults to the
/// current directory)
#[arg(long, env = "RAT_SNAPSHOT_DIR", value_name = "DIR")]
pub snapshot_dir: Option<std::path::PathBuf>,
/// Keep escape sequences in snapshots instead of stripping them
#[arg(long)]
pub snapshot_ansi: bool,
}
#[derive(clap::Args)]
pub struct DoctorArgs {
/// Machine-readable output
#[arg(long)]
pub json: bool,
}
#[derive(clap::Args)]
pub struct ChooseArgs {
/// Options to pick from; reads stdin when omitted
pub options: Vec<String>,
/// Maximum selections (single-select by default)
#[arg(long, default_value_t = 1, conflicts_with = "no_limit")]
pub limit: usize,
/// Allow any number of selections
#[arg(long)]
pub no_limit: bool,
/// Return results in list order instead of selection order
#[arg(long)]
pub ordered: bool,
/// Visible list height
#[arg(long, default_value_t = 10)]
pub height: u16,
/// Cursor marker
#[arg(long, default_value = "> ")]
pub cursor: String,
/// Header shown above the list
#[arg(long, default_value = "Choose:")]
pub header: String,
/// Prefix for selected items (multi-select)
#[arg(long, default_value = "✓ ")]
pub selected_prefix: String,
/// Prefix for unselected items (multi-select)
#[arg(long, default_value = "• ")]
pub unselected_prefix: String,
/// Preselect these options
#[arg(long)]
pub selected: Vec<String>,
/// Auto-select when only one option exists
#[arg(long)]
pub select_if_one: bool,
/// Delimiter for stdin options
#[arg(long, default_value = "\n")]
pub input_delimiter: String,
/// Delimiter joining printed results
#[arg(long, default_value = "\n")]
pub output_delimiter: String,
/// Show the key help footer
#[arg(long, overrides_with = "no_show_help")]
pub show_help: bool,
#[arg(long)]
pub no_show_help: bool,
/// Give up after this long (exit 124)
#[arg(long)]
pub timeout: Option<String>,
}
#[derive(clap::Args)]
pub struct ConfirmArgs {
/// The question to ask
#[arg(default_value = "Are you sure?")]
pub prompt: String,
/// Initially selected answer
#[arg(long = "default", default_value_t = true, action = clap::ArgAction::Set)]
pub default_yes: bool,
/// Label for the affirmative answer
#[arg(long, default_value = "Yes")]
pub affirmative: String,
/// Label for the negative answer
#[arg(long, default_value = "No")]
pub negative: String,
/// Print the chosen label to stdout
#[arg(long)]
pub show_output: bool,
/// Give up after this long (exit 124)
#[arg(long)]
pub timeout: Option<String>,
}
#[derive(clap::Args)]
pub struct InputArgs {
/// Placeholder shown while empty
#[arg(long, default_value = "Type something...")]
pub placeholder: String,
/// Prompt prefix
#[arg(long, default_value = "> ")]
pub prompt: String,
/// Initial value
#[arg(long, default_value = "")]
pub value: String,
/// Mask the input
#[arg(long)]
pub password: bool,
/// Maximum input length
#[arg(long, default_value_t = 400)]
pub char_limit: usize,
/// Header line above the prompt
#[arg(long)]
pub header: Option<String>,
/// Give up after this long (exit 124)
#[arg(long)]
pub timeout: Option<String>,
}
#[derive(clap::Args)]
pub struct FilterArgs {
/// Maximum selections (single-select by default)
#[arg(long, default_value_t = 1, conflicts_with = "no_limit")]
pub limit: usize,
/// Allow any number of selections
#[arg(long)]
pub no_limit: bool,
/// Placeholder shown while the query is empty
#[arg(long, default_value = "Filter...")]
pub placeholder: String,
/// Prompt prefix
#[arg(long, default_value = "> ")]
pub prompt: String,
/// Initial query
#[arg(long, default_value = "")]
pub value: String,
/// Visible list height
#[arg(long, default_value_t = 10)]
pub height: u16,
/// With no matches, print nothing instead of the query
#[arg(long, overrides_with = "no_strict", default_value_t = true)]
pub strict: bool,
#[arg(long)]
pub no_strict: bool,
/// Fuzzy matching (subsequences) instead of substring
#[arg(long, overrides_with = "no_fuzzy", default_value_t = true)]
pub fuzzy: bool,
#[arg(long)]
pub no_fuzzy: bool,
/// Rank matches by score
#[arg(long, overrides_with = "no_fuzzy_sort", default_value_t = true)]
pub fuzzy_sort: bool,
#[arg(long)]
pub no_fuzzy_sort: bool,
/// Cursor indicator
#[arg(long, default_value = "• ")]
pub indicator: String,
/// Prefix for selected items (multi-select)
#[arg(long, default_value = "◉ ")]
pub selected_prefix: String,
/// Prefix for unselected items (multi-select)
#[arg(long, default_value = "○ ")]
pub unselected_prefix: String,
/// Header line above the prompt
#[arg(long)]
pub header: Option<String>,
/// Auto-select when only one candidate exists
#[arg(long)]
pub select_if_one: bool,
/// Delimiter for stdin candidates
#[arg(long, default_value = "\n")]
pub input_delimiter: String,
/// Delimiter joining printed results
#[arg(long, default_value = "\n")]
pub output_delimiter: String,
/// Give up after this long (exit 124)
#[arg(long)]
pub timeout: Option<String>,
}
#[derive(clap::Args)]
pub struct SpinArgs {
/// Spinner charset
#[arg(short = 's', long, value_enum, default_value_t = crate::ui::spin::Spinner::Dot)]
pub spinner: crate::ui::spin::Spinner,
/// Text shown beside the spinner
#[arg(long, default_value = "Loading...")]
pub title: String,
/// Show the child's stdout and stderr when it finishes
#[arg(long)]
pub show_output: bool,
/// Show only the child's stdout
#[arg(long)]
pub show_stdout: bool,
/// Show only the child's stderr
#[arg(long)]
pub show_stderr: bool,
/// Show the child's output only when it fails
#[arg(long)]
pub show_error: bool,
/// Kill the child after this long (exit 124)
#[arg(long)]
pub timeout: Option<String>,
/// Command to run (after --)
#[arg(last = true, required = true)]
pub command: Vec<String>,
}
#[derive(clap::Args)]
pub struct CompletionArgs {
/// Shell to generate completions for
#[arg(value_enum)]
pub shell: clap_complete::Shell,
}
#[cfg(test)]
mod tests {
use clap::{CommandFactory, Parser};
#[test]
fn cli_is_well_formed() {
super::Cli::command().debug_assert();
}
#[test]
fn the_appearance_flag_parses_every_mode() {
// An explicit flag outranks the environment, so this is stable no
// matter what the developer's shell exports.
for (arg, expected) in [
("auto", crate::theme::AppearanceMode::Auto),
("light", crate::theme::AppearanceMode::Light),
("dark", crate::theme::AppearanceMode::Dark),
] {
let cli = super::Cli::parse_from(["rat", "--appearance", arg, "style", "x"]);
assert_eq!(cli.appearance, expected);
}
}
#[test]
fn table_ellipsis_default_matches_the_measure_constant() {
// clap's default_value wants a literal; this keeps the two in sync.
let cli = super::Cli::parse_from(["rat", "table"]);
let super::Command::Table(args) = cli.command else {
panic!("expected the table subcommand");
};
assert_eq!(args.ellipsis, crate::core::measure::ELLIPSIS);
}
}