//! Handles configuration for the bot. //! //! Both command line, and configuration file options are handled here. use clap::Parser; use color_eyre::{Result, eyre::WrapErr}; use config::Config; use directories::ProjectDirs; use std::path::PathBuf; use tracing::{info, instrument}; // TODO: use [clap(long, short, help_heading = Some(section))] /// Struct of potential arguments. #[derive(Clone, Debug, Parser)] #[command(about, version)] pub struct Args { #[arg(short, long)] /// API Key for the LLM in use. pub api_key: Option, #[arg(short, long, default_value = "https://api.openai.com")] /// Base URL for the LLM API to use. pub base_url: Option, /// Directory to use for chroot (recommended). #[arg(long)] pub chroot_dir: Option, /// Root directory for file based command structure. #[arg(long)] pub command_dir: Option, #[arg(long)] /// Instructions to the model on how to behave. pub instruct: Option, #[arg(long)] /// Name of the model to use. E.g. 'deepseek-chat' pub model: Option, #[arg(long)] /// List of IRC channels to join. pub channels: Option>, #[arg(short, long)] /// Custom configuration file location if need be. pub config_file: Option, #[arg(short, long, default_value = "irc.libera.chat")] /// IRC server. pub server: Option, #[arg(short, long, default_value = "6697")] /// Port of the IRC server. pub port: Option, #[arg(long)] /// IRC Nickname. pub nickname: Option, #[arg(long)] /// IRC Nick Password pub nick_password: Option, #[arg(long)] /// IRC Username pub username: Option, #[arg(long)] /// Whether or not to use TLS when connecting to the IRC server. pub use_tls: Option, } /// Handle for interacting with the bot configuration. pub struct Setup { /// Handle for the configuration file options. pub config: Config, } #[instrument] /// Initialize a new [`Setup`] instance. /// /// This reads the settings file which becomes the bot's default configuration. /// These settings shall be overridden by any command line options. pub async fn init() -> Result { // Get arguments. These overrule configuration file, and environment // variables if applicable. let args = Args::parse(); // Use default config location unless specified. let config_location: PathBuf = if let Some(ref path) = args.config_file { path.to_owned() } else { ProjectDirs::from("", "", env!("CARGO_PKG_NAME")) .unwrap() .config_dir() .to_owned() .join(r"config.toml") }; info!("Starting."); let settings = Config::builder() .add_source(config::File::with_name(&config_location.to_string_lossy()).required(false)) .add_source(config::Environment::with_prefix("BOT")) // Doing all of these overrides provides a unified access point for options, // but a derive macro could do this a bit better if this becomes too large. .set_override_option("api-key", args.api_key.clone())? .set_override_option("base-url", args.base_url.clone())? .set_override_option("chroot-dir", args.chroot_dir.clone())? .set_override_option("command-path", args.command_dir.clone())? .set_override_option("model", args.model.clone())? .set_override_option("instruct", args.instruct.clone())? .set_override_option("channels", args.channels.clone())? .set_override_option("server", args.server.clone())? .set_override_option("port", args.port.clone())? // FIXME: Make this a default here not in clap. .set_override_option("nickname", args.nickname.clone())? .set_override_option("username", args.username.clone())? .set_override_option("use_tls", args.use_tls)? .build() .wrap_err("Couldn't read configuration settings.")?; Ok(Setup { config: settings }) }