//! Integration with `clap` //! //! CLI argument structs are defined in this module. Execution logic for each //! command goes in a submodule. use std::path::PathBuf; use clap::{Parser, Subcommand}; use crate::{ config::{default_tracing_filter, EnvFilterClone, LogFormat}, error, observability, }; mod check_config; mod get_room_states; mod serve; /// Command line arguments #[derive(Parser)] #[clap( about, version = crate::version(), )] pub(crate) struct Args { #[clap(subcommand)] pub(crate) command: Command, } #[derive(Subcommand)] pub(crate) enum Command { /// Run the server. Serve(ServeArgs), /// Check the configuration file for syntax and semantic errors. CheckConfig(CheckConfigArgs), /// Write the state of all rooms as JSON to stdout. /// /// This is primarily useful for debugging. GetRoomStates(GetRoomStatesArgs), } #[derive(clap::Args)] pub(crate) struct CheckConfigArgs { #[clap(flatten)] config: ConfigArg, #[clap(flatten)] observability: ObservabilityArgs, } /// Wrapper for the `--config` arg. /// /// This exists to centralize the `mut_arg` code that sets the help value based /// on runtime information. #[derive(clap::Args)] #[clap(mut_arg("config", |x| { let help = "Set the path to the configuration file"; x.help(help).long_help(format!( "{}\n\nIf this option is specified, the provided value is used \ as-is.\n\nIf this option is not specified, then the XDG Base \ Directory Specification is followed, searching for the path `{}` \ in the configuration directories. ", help, crate::config::DEFAULT_PATH.display(), )) }))] pub(crate) struct ConfigArg { /// Path to the configuration file #[clap(long, short)] pub(crate) config: Option, } /// Observability arguments for CLI subcommands #[derive(clap::Args)] struct ObservabilityArgs { /// Log format #[clap(long, default_value_t = LogFormat::Full)] log_format: LogFormat, /// Log filter /// /// For information about the syntax, see here: /// #[clap(long, default_value_t = default_tracing_filter())] log_filter: EnvFilterClone, } #[derive(clap::Args)] pub(crate) struct ServeArgs { #[clap(flatten)] pub(crate) config: ConfigArg, } #[derive(clap::Args)] pub(crate) struct GetRoomStatesArgs { #[clap(flatten)] pub(crate) config: ConfigArg, #[clap(flatten)] observability: ObservabilityArgs, } impl Args { pub(crate) async fn run(self) -> Result<(), error::Main> { if let Some((format, filter)) = self.command.cli_observability_args() { observability::init_for_cli(format, filter.into())?; } match self.command { Command::Serve(args) => serve::run(args).await?, Command::CheckConfig(args) => { check_config::run(args.config).await?; } Command::GetRoomStates(args) => get_room_states::run(args).await?, } Ok(()) } } impl Command { fn cli_observability_args(&self) -> Option<(LogFormat, EnvFilterClone)> { // All subcommands other than `serve` should return `Some`. Keep these // match arms sorted by the enum variant name. match self { Command::CheckConfig(args) => Some(( args.observability.log_format, args.observability.log_filter.clone(), )), Command::GetRoomStates(args) => Some(( args.observability.log_format, args.observability.log_filter.clone(), )), Command::Serve(_) => None, } } }