pub mod decode; pub mod encode; pub mod import_dict; use crate::config::AppConfig; use crate::container::Container; use anyhow::Result; use async_trait::async_trait; use clap::Subcommand; use clap::{Args as ClapArgs, Parser}; use config::ConfigBuilder; use config::builder::DefaultState; use std::path::PathBuf; #[derive(Subcommand, Debug, Clone)] pub enum Command { /// Decode a word using given system Decode(decode::DecodeArgs), /// Encode a number using given system Encode(encode::EncodeArgs), /// Import dictionary ImportDict(import_dict::ImportDictArgs), } pub fn resolve_command(command: &Command) -> &dyn AppCommand { match command { Command::Decode(args) => args, Command::Encode(args) => args, Command::ImportDict(args) => args, } } #[derive(Parser, Debug)] #[command(author, version, about)] pub struct CliArgs { #[command(flatten)] pub global: GlobalArgs, #[command(subcommand)] pub command: Command, } #[derive(ClapArgs, Debug)] pub struct GlobalArgs { /// Path to config file #[arg(short, long, default_value = "config.toml")] pub config: PathBuf, #[arg(long, help = defaults::HELP_LOG)] pub log_level: Option, } pub trait ConfigurableCommand { fn apply_defaults( &self, builder: ConfigBuilder, ) -> Result>; fn apply_overrides( &self, builder: ConfigBuilder, ) -> Result>; } #[async_trait] pub trait CommandExecutor { async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()>; } // AppCommand must be dyn-compatible. ConfigurableCommand is already dyn-compatible. // CommandExecutor is dyn-compatible because of #[async_trait]. pub trait AppCommand: ConfigurableCommand + CommandExecutor {} impl AppCommand for T {} mod defaults { use const_format::formatcp; pub const LOG_LEVEL: &str = "info"; pub const HELP_LOG: &str = formatcp!("Override Log Level [default: {}]", LOG_LEVEL); }