diff --git a/app/src/app.rs b/app/src/app.rs index dfca7f7..ae5b817 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -1,4 +1,6 @@ +use crate::bootstrap::cli::Command; use crate::bootstrap::{AppConfig, CliArgs}; +use crate::commands; use crate::container::Container; use anyhow::Result; use clap::Parser; @@ -9,6 +11,7 @@ use tracing::{info, warn}; pub struct Application { config: AppConfig, container: Container, + command: Command, } impl Application { @@ -16,55 +19,64 @@ impl Application { pub async fn build() -> Result { let args = CliArgs::parse(); - // 1. Load Config - let config = AppConfig::build(&args)?; + // 1. Determine Port Override (only for Server command) + let port_override = if let Command::Server(ref server_args) = args.command { + server_args.port + } else { + None + }; - // 2. Init Logging (Tracing) + // 2. Load Config + let config = AppConfig::build(&args.global, port_override)?; + + // 3. Init Logging (Tracing) tracing_subscriber::fmt() .with_env_filter(&config.log_level) .init(); info!("Bootstrapping application..."); - // 3. Wire Dependencies + // 4. Wire Dependencies let container = Container::new(&config).await?; - Ok(Self { config, container }) + Ok(Self { + config, + container, + command: args.command, + }) } /// Execution: Starts the main loop and waits for shutdown signal. pub async fn run(self) -> Result<()> { - info!("Application started on port {}", self.config.server.port); + match self.command { + Command::Server(_) => { + info!("Application started on port {}", self.config.server.port); + + // Spawn the server loop + let server_task = tokio::spawn(commands::server::run( + self.config.clone(), + self.container.clone(), + )); - // Example: Spawn a web server or background worker here. - // We pass a clone of the container or specific services. - let server_task = tokio::spawn(server_loop(self.config.clone(), self.container.clone())); + // Wait for Shutdown Signal + match signal::ctrl_c().await { + Ok(()) => { + info!("Received shutdown signal (SIGINT/SIGTERM)"); + } + Err(err) => { + warn!("Failed to listen for shutdown signal: {}", err); + } + } - // Wait for Shutdown Signal - match signal::ctrl_c().await { - Ok(()) => { - info!("Received shutdown signal (SIGINT/SIGTERM)"); + // Graceful Shutdown Logic + info!("Shutting down..."); + server_task.abort(); // Or send a specialized shutdown channel message } - Err(err) => { - warn!("Failed to listen for shutdown signal: {}", err); + Command::ExportDict(export_args) => { + commands::export::run(export_args, self.config, self.container).await; } } - // Graceful Shutdown Logic - info!("Shutting down..."); - server_task.abort(); // Or send a specialized shutdown channel message - Ok(()) } } - -/// Simulated long-running process (e.g., HTTP Server) -async fn server_loop(config: AppConfig, container: Container) { - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - info!("Health check... DB URL: {}", config.database.url); - - // Example usage of the container - // let _ = container.user_service.do_something(); - } -} diff --git a/app/src/bootstrap.rs b/app/src/bootstrap.rs index fb3e05f..2510748 100644 --- a/app/src/bootstrap.rs +++ b/app/src/bootstrap.rs @@ -1,5 +1,5 @@ mod app_config; -mod cli; +pub mod cli; // Make cli public so we can access subcommands mod defaults; pub use self::app_config::AppConfig; diff --git a/app/src/bootstrap/app_config.rs b/app/src/bootstrap/app_config.rs index a9f6562..8d680b2 100644 --- a/app/src/bootstrap/app_config.rs +++ b/app/src/bootstrap/app_config.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use config::{Config, Environment, File}; use serde::Deserialize; -use super::cli::Args; +use super::cli::GlobalArgs; use super::defaults::set_defaults; #[derive(Debug, Deserialize, Clone)] @@ -25,7 +25,7 @@ pub struct DatabaseConfig { } impl AppConfig { - pub fn build(args: &Args) -> Result { + pub fn build(args: &GlobalArgs, port_override: Option) -> Result { let mut builder = Config::builder(); // Defaults @@ -41,7 +41,7 @@ impl AppConfig { builder = builder.add_source(Environment::with_prefix("APP").separator("__")); // CLI Overrides Layer - if let Some(port) = args.port { + if let Some(port) = port_override { builder = builder.set_override("server.port", port)?; } if let Some(ref level) = args.log_level { diff --git a/app/src/bootstrap/cli.rs b/app/src/bootstrap/cli.rs index 5e40b06..0f5df14 100644 --- a/app/src/bootstrap/cli.rs +++ b/app/src/bootstrap/cli.rs @@ -1,17 +1,49 @@ use super::defaults; -use clap::Parser; +use clap::{Args as ClapArgs, Parser, Subcommand}; use std::path::PathBuf; #[derive(Parser, Debug)] #[command(author, version, about)] pub struct Args { + #[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, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum Command { + /// Start the application server + Server(ServerArgs), + + /// Export dictionary to file + ExportDict(ExportDictArgs), +} + +#[derive(ClapArgs, Debug, Clone)] +pub struct ServerArgs { #[arg(short, long, help = defaults::HELP_PORT)] pub port: Option, +} - #[arg(long, help = defaults::HELP_LOG)] - pub log_level: Option, +#[derive(ClapArgs, Debug, Clone)] +pub struct ExportDictArgs { + /// Name of the dictionary to export + #[arg(long)] + pub name: String, + + /// Output file path + #[arg(long)] + pub output: PathBuf, } diff --git a/app/src/commands.rs b/app/src/commands.rs new file mode 100644 index 0000000..8df7eb2 --- /dev/null +++ b/app/src/commands.rs @@ -0,0 +1,2 @@ +pub mod export; +pub mod server; diff --git a/app/src/commands/export.rs b/app/src/commands/export.rs new file mode 100644 index 0000000..71bbecd --- /dev/null +++ b/app/src/commands/export.rs @@ -0,0 +1,12 @@ +use crate::bootstrap::AppConfig; +use crate::bootstrap::cli::ExportDictArgs; +use crate::container::Container; +use tracing::info; + +pub async fn run(args: ExportDictArgs, _config: AppConfig, _container: Container) { + info!("Exporting dictionary '{}' to {:?}", args.name, args.output); + + // Logic for export would go here + // e.g. let dict = container.dict_service.get(args.name); + // encoder.write(dict, args.output); +} diff --git a/app/src/commands/server.rs b/app/src/commands/server.rs new file mode 100644 index 0000000..14d8337 --- /dev/null +++ b/app/src/commands/server.rs @@ -0,0 +1,13 @@ +use crate::bootstrap::AppConfig; +use crate::container::Container; +use tracing::info; + +pub async fn run(config: AppConfig, container: Container) { + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + info!("Health check... DB URL: {}", config.database.url); + + // Example usage of the container + // let _ = container.user_service.do_something(); + } +} diff --git a/app/src/main.rs b/app/src/main.rs index 3fc04e4..d2dc8a5 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -1,68 +1,8 @@ -// use clap::{Parser, ValueEnum}; -// use libmnemor::{System, create_encoder}; - -// #[derive(ValueEnum, Debug, Clone, Copy)] -// #[clap(rename_all = "kebab-case")] -// enum SystemCli { -// MajorEn, -// MajorPl, -// } - -// impl From for System { -// fn from(cli_system: SystemCli) -> Self { -// match cli_system { -// SystemCli::MajorEn => System::MajorEn, -// SystemCli::MajorPl => System::MajorPl, -// } -// } -// } - -// #[derive(Parser)] -// #[command(author, version, about)] -// struct CliArgs { -// /// System name -// #[arg(short, long, default_value = "major-pl")] -// system: SystemCli, - -// /// Encode given word -// #[arg(short, long)] -// encode: Option, - -// /// List supported systems -// #[arg(long)] -// list_systems: bool, -// } - -// fn main() -> Result<(), Box> { -// let args = CliArgs::parse(); - -// if args.list_systems { -// println!("Supported systems:"); -// for system in SystemCli::value_variants() { -// if let Some(possible_value) = system.to_possible_value() { -// println!("- {}", possible_value.get_name()); -// } -// } -// return Ok(()); -// } - -// if let Some(word) = args.encode { -// println!( -// "Encoding {} with system {:?}...", -// word, -// args.system.to_possible_value().unwrap().get_name() -// ); -// let encoder = create_encoder(&args.system.into()); -// let encoded_word = encoder.encode(&word); -// println!("Encoded: [{}]", encoded_word); -// } - -// Ok(()) -// } - mod app; mod bootstrap; +mod commands; mod container; + use anyhow::Result; use app::Application;