1use clap::{Arg, ArgAction, Command};
10
11pub use bel7_cli::CompletionShell;
12
13use crate::commands::{CONFIG_FILES, EtcFile, RABBITMQ_TOOLS};
14use crate::shell::Shell;
15
16pub fn build_cli() -> Command {
17 Command::new("frm")
18 .version(env!("CARGO_PKG_VERSION"))
19 .disable_version_flag(true)
20 .author("Michael S. Klishin")
21 .about("Frakking RabbitMQ version Manager")
22 .help_template("{name} {version}\n{about}\n\n{usage-heading} {usage}\n\n{all-args}")
23 .arg_required_else_help(true)
24 .subcommand(status_command())
25 .subcommand(releases_command())
26 .subcommand(alphas_command())
27 .subcommand(tanzu_command())
28 .subcommand(conf_command())
29 .subcommand(default_command())
30 .subcommand(cli_command())
31 .subcommand(fg_command())
32 .subcommand(inspect_command())
33 .subcommand(shell_command())
34}
35
36fn status_command() -> Command {
37 Command::new("status")
38 .about("Show frm status: active version, default, installed versions")
39 .long_about(
40 "Show frm status: active version, default, installed versions.\n\n\
41 🟢 active in current shell (via 'frm releases use', 'frm alphas use', or 'frm tanzu use')\n\
42 ⚪ default version",
43 )
44}
45
46fn releases_command() -> Command {
47 Command::new("releases")
48 .about("Install or manage RabbitMQ releases (GA, RCs, betas); for alphas, see the 'alphas' command group")
49 .arg_required_else_help(true)
50 .subcommand(releases_list_command())
51 .subcommand(releases_path_command())
52 .subcommand(releases_logs_command())
53 .subcommand(releases_install_command())
54 .subcommand(releases_reinstall_command())
55 .subcommand(releases_uninstall_command())
56 .subcommand(releases_use_command())
57 .subcommand(releases_cp_etc_file_command())
58 .subcommand(releases_completions_command())
59}
60
61fn releases_use_command() -> Command {
62 const HELP: &str = "Version to use (e.g., 4.2.3 or 'latest')";
63 Command::new("use")
64 .about("Output shell commands to use a specific release version")
65 .long_about(
66 "Output shell commands to use a specific release version.\n\n\
67 Use 'latest' to select the most recent installed GA version.\n\n\
68 bash/zsh: eval \"$(frm releases use [version])\"\n\
69 nushell: Use 'frm shell env nu' init script, then call 'frm-use [version]'",
70 )
71 .arg(positional_version_arg(HELP))
72 .arg(version_opt_arg(HELP))
73 .arg(
74 Arg::new("shell")
75 .long("shell")
76 .short('s')
77 .help("Shell type (bash, zsh, nu)")
78 .value_parser(clap::value_parser!(Shell)),
79 )
80}
81
82fn releases_completions_command() -> Command {
83 Command::new("completions")
84 .about("Output installed release versions for shell completion")
85 .hide(true)
86 .arg(
87 Arg::new("shell")
88 .long("shell")
89 .short('s')
90 .help("Shell type (bash, zsh, nu)")
91 .value_parser(clap::value_parser!(Shell)),
92 )
93}
94
95fn releases_list_command() -> Command {
96 Command::new("list")
97 .visible_alias("ls")
98 .about("List installed stable RabbitMQ releases")
99}
100
101fn releases_path_command() -> Command {
102 Command::new("path")
103 .about("Show the local path of an installed release")
104 .arg(version_arg())
105}
106
107fn releases_logs_command() -> Command {
108 Command::new("logs")
109 .about("Show RabbitMQ log file information for a release")
110 .arg_required_else_help(true)
111 .subcommand(
112 Command::new("path")
113 .about("Show the path to the log file")
114 .arg(version_arg()),
115 )
116 .subcommand(
117 Command::new("tail")
118 .about("Show the last lines of the log file")
119 .arg(version_arg())
120 .arg(
121 Arg::new("lines")
122 .long("lines")
123 .short('n')
124 .help("Number of lines to show")
125 .default_value("10")
126 .value_parser(clap::value_parser!(usize)),
127 ),
128 )
129}
130
131fn releases_install_command() -> Command {
132 const HELP: &str = "Version to install (e.g., 4.2.3 or 4.2.0-rc.1)";
133 Command::new("install")
134 .visible_alias("i")
135 .about("Install a stable RabbitMQ release")
136 .long_about(
137 "Install a stable RabbitMQ release (beta, rc, or GA).\n\n\
138 Alpha versions are not allowed; use 'frm alphas install' instead.",
139 )
140 .arg(positional_version_arg(HELP))
141 .arg(version_opt_arg(HELP))
142 .arg(
143 Arg::new("force")
144 .long("force")
145 .short('f')
146 .help("Force reinstallation if version exists")
147 .action(ArgAction::SetTrue),
148 )
149}
150
151fn releases_reinstall_command() -> Command {
152 const HELP: &str = "Version to reinstall (e.g., 4.2.3)";
153 Command::new("reinstall")
154 .about("Reinstall a stable RabbitMQ release")
155 .long_about(
156 "Reinstall a stable RabbitMQ release.\n\n\
157 Removes the existing installation and downloads a fresh copy.",
158 )
159 .arg(positional_version_arg(HELP))
160 .arg(version_opt_arg(HELP))
161}
162
163fn releases_uninstall_command() -> Command {
164 const HELP: &str = "Version to uninstall (e.g., 4.2.3 or 'latest')";
165 Command::new("uninstall")
166 .visible_alias("rm")
167 .about("Uninstall a stable RabbitMQ release")
168 .long_about(
169 "Uninstall a stable RabbitMQ release.\n\n\
170 Use 'latest' to uninstall the most recent installed GA version.",
171 )
172 .arg(positional_version_arg(HELP))
173 .arg(version_opt_arg(HELP))
174}
175
176fn releases_cp_etc_file_command() -> Command {
177 cp_etc_file_command("Copy a configuration file to a stable release's etc/rabbitmq directory")
178}
179
180fn alphas_cp_etc_file_command() -> Command {
181 cp_etc_file_command("Copy a configuration file to an alpha release's etc/rabbitmq directory")
182}
183
184fn cp_etc_file_command(about: &'static str) -> Command {
185 Command::new("cp-etc-file")
186 .about(about)
187 .long_about(format!(
188 "{}\n\n\
189 Copies a local file to the version's etc/rabbitmq directory.\n\n\
190 Supported files: {}",
191 about,
192 EtcFile::all_names().join(", ")
193 ))
194 .arg(
195 Arg::new("local_file_path")
196 .long("local-file-path")
197 .help("Path to the local file to copy")
198 .required(true)
199 .value_name("PATH"),
200 )
201 .arg(
202 Arg::new("etc_file")
203 .long("etc-file")
204 .help("Target configuration file name")
205 .required(true)
206 .value_name("FILE")
207 .value_parser(EtcFile::all_names()),
208 )
209 .arg(version_arg())
210}
211
212fn alphas_command() -> Command {
213 Command::new("alphas")
214 .about("Install, manage, rotate alpha RabbitMQ releases")
215 .arg_required_else_help(true)
216 .subcommand(alphas_list_command())
217 .subcommand(alphas_path_command())
218 .subcommand(alphas_logs_command())
219 .subcommand(alphas_install_command())
220 .subcommand(alphas_reinstall_command())
221 .subcommand(alphas_uninstall_command())
222 .subcommand(alphas_use_command())
223 .subcommand(alphas_cp_etc_file_command())
224 .subcommand(alphas_prune_command())
225 .subcommand(alphas_clean_command())
226 .subcommand(alphas_completions_command())
227}
228
229fn alphas_use_command() -> Command {
230 const HELP: &str = "Alpha version to use (e.g., 4.3.0-alpha.132057c7 or 'latest')";
231 Command::new("use")
232 .about("Output shell commands to use a specific alpha version")
233 .long_about(
234 "Output shell commands to use a specific alpha version.\n\n\
235 Use 'latest' to select the most recent installed alpha version.\n\n\
236 bash/zsh: eval \"$(frm alphas use [version])\"\n\
237 nushell: Use 'frm shell env nu' init script, then call 'frm-use [version]'",
238 )
239 .arg(positional_version_arg(HELP))
240 .arg(version_opt_arg(HELP))
241 .arg(
242 Arg::new("shell")
243 .long("shell")
244 .short('s')
245 .help("Shell type (bash, zsh, nu)")
246 .value_parser(clap::value_parser!(Shell)),
247 )
248}
249
250fn alphas_completions_command() -> Command {
251 Command::new("completions")
252 .about("Output installed alpha versions for shell completion")
253 .hide(true)
254 .arg(
255 Arg::new("shell")
256 .long("shell")
257 .short('s')
258 .help("Shell type (bash, zsh, nu)")
259 .value_parser(clap::value_parser!(Shell)),
260 )
261}
262
263fn alphas_list_command() -> Command {
264 Command::new("list")
265 .visible_alias("ls")
266 .about("List installed alpha RabbitMQ releases")
267}
268
269fn alphas_path_command() -> Command {
270 Command::new("path")
271 .about("Show the local path of an installed alpha release")
272 .arg(version_arg())
273}
274
275fn alphas_logs_command() -> Command {
276 Command::new("logs")
277 .about("Show RabbitMQ log file information for an alpha release")
278 .arg_required_else_help(true)
279 .subcommand(
280 Command::new("path")
281 .about("Show the path to the log file")
282 .arg(version_arg()),
283 )
284 .subcommand(
285 Command::new("tail")
286 .about("Show the last lines of the log file")
287 .arg(version_arg())
288 .arg(
289 Arg::new("lines")
290 .long("lines")
291 .short('n')
292 .help("Number of lines to show")
293 .default_value("10")
294 .value_parser(clap::value_parser!(usize)),
295 ),
296 )
297}
298
299fn alphas_install_command() -> Command {
300 const HELP: &str = "Alpha version to install (e.g., 4.3.0-alpha.132057c7 or 'latest')";
301 Command::new("install")
302 .visible_alias("i")
303 .about("Install an alpha RabbitMQ release")
304 .long_about(
305 "Install an alpha RabbitMQ release from rabbitmq/server-packages.\n\n\
306 Use 'latest' to automatically install the most recent alpha release.",
307 )
308 .arg(positional_version_arg(HELP))
309 .arg(version_opt_arg(HELP))
310 .arg(
311 Arg::new("force")
312 .long("force")
313 .short('f')
314 .help("Force reinstallation if version exists")
315 .action(ArgAction::SetTrue),
316 )
317}
318
319fn alphas_reinstall_command() -> Command {
320 const HELP: &str = "Alpha version to reinstall (e.g., 4.3.0-alpha.132057c7 or 'latest')";
321 Command::new("reinstall")
322 .about("Reinstall an alpha RabbitMQ release")
323 .long_about(
324 "Reinstall an alpha RabbitMQ release.\n\n\
325 Removes the existing installation and downloads a fresh copy.\n\n\
326 Use 'latest' to reinstall the most recent installed alpha version.",
327 )
328 .arg(positional_version_arg(HELP))
329 .arg(version_opt_arg(HELP))
330}
331
332fn alphas_uninstall_command() -> Command {
333 const HELP: &str = "Alpha version to uninstall (e.g., 4.3.0-alpha.132057c7 or 'latest')";
334 Command::new("uninstall")
335 .visible_alias("rm")
336 .about("Uninstall an alpha RabbitMQ release")
337 .long_about(
338 "Uninstall an alpha RabbitMQ release.\n\n\
339 Use 'latest' to uninstall the most recent installed alpha version.",
340 )
341 .arg(positional_version_arg(HELP))
342 .arg(version_opt_arg(HELP))
343}
344
345fn alphas_prune_command() -> Command {
346 Command::new("prune")
347 .about("Remove all installed alpha releases")
348 .long_about("Remove all installed alpha releases to free up disk space.")
349}
350
351fn alphas_clean_command() -> Command {
352 Command::new("clean")
353 .about("Remove alpha releases older than a specified time")
354 .long_about(
355 "Remove alpha releases older than a specified time.\n\n\
356 The --older-than flag accepts human-readable time strings like:\n\
357 - \"2 weeks ago\"\n\
358 - \"1 month ago\"\n\
359 - \"yesterday\"\n\
360 - \"2025-01-01\" (absolute date)",
361 )
362 .arg(
363 Arg::new("older_than")
364 .long("older-than")
365 .help("Remove alphas installed before this time (e.g., \"2 weeks ago\")")
366 .required(true)
367 .value_name("TIME"),
368 )
369}
370
371fn tanzu_command() -> Command {
372 Command::new("tanzu")
373 .about("Install and manage Tanzu RabbitMQ from local tarballs")
374 .arg_required_else_help(true)
375 .subcommand(tanzu_install_command())
376 .subcommand(tanzu_use_command())
377}
378
379fn tanzu_use_command() -> Command {
380 const HELP: &str = "Version to use (e.g., 4.2.3 or 'latest')";
381 Command::new("use")
382 .about("Output shell commands to use a specific Tanzu RabbitMQ version")
383 .long_about(
384 "Output shell commands to use a specific Tanzu RabbitMQ version.\n\n\
385 Use 'latest' to select the most recent installed GA version.\n\n\
386 bash/zsh: eval \"$(frm tanzu use [version])\"\n\
387 nushell: Use 'frm shell env nu' init script, then call 'frm-use [version]'",
388 )
389 .arg(positional_version_arg(HELP))
390 .arg(version_opt_arg(HELP))
391 .arg(
392 Arg::new("shell")
393 .long("shell")
394 .short('s')
395 .help("Shell type (bash, zsh, nu)")
396 .value_parser(clap::value_parser!(Shell)),
397 )
398}
399
400fn tanzu_install_command() -> Command {
401 Command::new("install")
402 .visible_alias("i")
403 .about("Install Tanzu RabbitMQ from a local tarball")
404 .long_about(
405 "Install Tanzu RabbitMQ from a local tarball.\n\n\
406 Requires both the tarball path and the expected version.\n\
407 The version in the tarball filename must match the specified version.\n\n\
408 Supported formats: .tar.xz, .tar.gz, .tgz",
409 )
410 .arg(
411 Arg::new("tarball_path")
412 .long("local-tanzu-rabbitmq-tarball-path")
413 .help("Path to the local Tanzu RabbitMQ tarball")
414 .required(true)
415 .value_name("PATH"),
416 )
417 .arg(
418 Arg::new("version")
419 .long("version")
420 .short('V')
421 .help("Expected RabbitMQ version (e.g., 4.2.3 or 4.2.3-rc.1)")
422 .required(true)
423 .value_name("VERSION"),
424 )
425 .arg(
426 Arg::new("force")
427 .long("force")
428 .short('f')
429 .help("Force reinstallation if version exists")
430 .action(ArgAction::SetTrue),
431 )
432}
433
434fn conf_command() -> Command {
435 Command::new("conf")
436 .about("Manage RabbitMQ configuration files")
437 .arg_required_else_help(true)
438 .subcommand(conf_get_key_command())
439 .subcommand(conf_set_key_command())
440}
441
442fn conf_get_key_command() -> Command {
443 Command::new("get-key")
444 .about("Get a configuration key value from rabbitmq.conf")
445 .long_about(
446 "Get a configuration key value from rabbitmq.conf.\n\n\
447 Supports pattern matching with * as a wildcard for a single segment:\n\n \
448 * `listeners.tcp.*` matches `listeners.tcp.default`, `listeners.tcp.amqp`, etc.\n \
449 * `log.*.level` matches `log.console.level`, `log.file.level`, etc.",
450 )
451 .arg(
452 Arg::new("key")
453 .help("Configuration key or pattern (e.g., listeners.tcp.* or heartbeat)")
454 .required(true)
455 .index(1),
456 )
457 .arg(version_arg())
458}
459
460fn conf_set_key_command() -> Command {
461 Command::new("set-key")
462 .about("Set a configuration key value in rabbitmq.conf")
463 .long_about(
464 "Set a configuration key value in rabbitmq.conf.\n\n\
465 Keys are validated against the known RabbitMQ configuration schema.\n\
466 Use --force to set unknown keys.",
467 )
468 .arg(
469 Arg::new("key")
470 .help("Configuration key (e.g., listeners.tcp.default)")
471 .required(true)
472 .index(1),
473 )
474 .arg(
475 Arg::new("value")
476 .help("Value to set")
477 .required(true)
478 .index(2),
479 )
480 .arg(version_arg())
481 .arg(
482 Arg::new("force")
483 .long("force")
484 .short('f')
485 .help("Set the key even if it's not recognized")
486 .action(ArgAction::SetTrue),
487 )
488}
489
490fn default_command() -> Command {
491 const HELP: &str = "Version to set as default (e.g., 4.2.3 or 'latest')";
492 Command::new("default")
493 .about("Set the default RabbitMQ version")
494 .long_about(
495 "Set the default RabbitMQ version.\n\n\
496 Use 'latest' to select the most recent installed GA version.",
497 )
498 .arg(positional_version_arg(HELP))
499 .arg(version_opt_arg(HELP))
500}
501
502fn shell_command() -> Command {
503 Command::new("shell")
504 .about("Shell-related operations")
505 .arg_required_else_help(true)
506 .subcommand(shell_completions_command())
507 .subcommand(shell_env_command())
508}
509
510fn shell_env_command() -> Command {
511 Command::new("env")
512 .about("Output shell initialization script")
513 .long_about(
514 "Output shell initialization script.\n\n\
515 Add to your shell profile:\n\
516 - bash: eval \"$(frm shell env bash)\" in ~/.bashrc\n\
517 - zsh: eval \"$(frm shell env zsh)\" in ~/.zshrc\n\
518 - nu: frm shell env nu | save -f ~/.local/frm/env.nu, then source in config.nu\n\n\
519 After setup, use 'frm-use <version>' to switch versions.",
520 )
521 .arg(
522 Arg::new("shell")
523 .help("Shell type (bash, zsh, nu)")
524 .required(true)
525 .index(1)
526 .value_parser(clap::value_parser!(Shell)),
527 )
528}
529
530fn shell_completions_command() -> Command {
531 Command::new("completions")
532 .about("Generate shell completions")
533 .long_about(
534 "Generate shell completions.\n\n\
535 If no shell is specified, attempts to detect the current shell from \
536 environment variables (SHELL, NU_VERSION, etc.).",
537 )
538 .arg(
539 Arg::new("shell")
540 .help("Target shell (bash, elvish, fish, nushell, powershell, zsh); auto-detected if omitted")
541 .index(1)
542 .value_parser(clap::value_parser!(CompletionShell)),
543 )
544}
545
546fn cli_command() -> Command {
547 Command::new("cli")
548 .about("Run a RabbitMQ CLI tool")
549 .long_about(format!(
550 "Run a RabbitMQ CLI tool from the specified version.\n\n\
551 Available tools: {}\n\n\
552 Use -- to separate tool arguments from frm options:\n\
553 frm cli rabbitmqctl -V 4.2.3 -- status",
554 RABBITMQ_TOOLS.join(", ")
555 ))
556 .trailing_var_arg(true)
557 .arg(Arg::new("tool").help("Tool to run").required(true).index(1))
558 .arg(version_arg())
559 .arg(
560 Arg::new("args")
561 .help("Arguments to pass to the tool (after --)")
562 .num_args(1..)
563 .index(2),
564 )
565}
566
567fn fg_command() -> Command {
568 Command::new("fg")
569 .about("Run RabbitMQ nodes in foreground")
570 .arg_required_else_help(true)
571 .subcommand(
572 Command::new("node")
573 .about("Start RabbitMQ server in foreground")
574 .arg(version_arg()),
575 )
576}
577
578fn inspect_command() -> Command {
579 Command::new("inspect")
580 .about("Inspect a RabbitMQ configuration file")
581 .long_about(format!(
582 "Inspect a RabbitMQ configuration file from the specified version.\n\n\
583 Available files: {}",
584 CONFIG_FILES.join(", ")
585 ))
586 .arg(
587 Arg::new("file")
588 .help("Configuration file to inspect")
589 .required(true)
590 .index(1),
591 )
592 .arg(version_arg())
593}
594
595fn version_arg() -> Arg {
596 Arg::new("version")
597 .long("version")
598 .short('V')
599 .help("RabbitMQ version to use")
600 .value_name("VERSION")
601}
602
603fn positional_version_arg(help: &'static str) -> Arg {
604 Arg::new("version").help(help).index(1)
605}
606
607fn version_opt_arg(help: &'static str) -> Arg {
608 Arg::new("version_opt")
609 .long("version")
610 .short('V')
611 .help(format!("{}; equivalent to the positional argument", help))
612 .value_name("VERSION")
613}
614
615pub fn get_version_arg(matches: &clap::ArgMatches) -> Option<&String> {
616 matches
617 .get_one::<String>("version")
618 .or_else(|| matches.get_one::<String>("version_opt"))
619}