Browse Source

Added template app bootstrap modules with cli commands

develop-refactor
chodak166 5 months ago
parent
commit
19e80cb072
  1. 50
      app/src/app.rs
  2. 2
      app/src/bootstrap.rs
  3. 6
      app/src/bootstrap/app_config.rs
  4. 38
      app/src/bootstrap/cli.rs
  5. 2
      app/src/commands.rs
  6. 12
      app/src/commands/export.rs
  7. 13
      app/src/commands/server.rs
  8. 64
      app/src/main.rs

50
app/src/app.rs

@ -1,4 +1,6 @@
use crate::bootstrap::cli::Command;
use crate::bootstrap::{AppConfig, CliArgs}; use crate::bootstrap::{AppConfig, CliArgs};
use crate::commands;
use crate::container::Container; use crate::container::Container;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
@ -9,6 +11,7 @@ use tracing::{info, warn};
pub struct Application { pub struct Application {
config: AppConfig, config: AppConfig,
container: Container, container: Container,
command: Command,
} }
impl Application { impl Application {
@ -16,29 +19,44 @@ impl Application {
pub async fn build() -> Result<Self> { pub async fn build() -> Result<Self> {
let args = CliArgs::parse(); let args = CliArgs::parse();
// 1. Load Config // 1. Determine Port Override (only for Server command)
let config = AppConfig::build(&args)?; 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() tracing_subscriber::fmt()
.with_env_filter(&config.log_level) .with_env_filter(&config.log_level)
.init(); .init();
info!("Bootstrapping application..."); info!("Bootstrapping application...");
// 3. Wire Dependencies // 4. Wire Dependencies
let container = Container::new(&config).await?; 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. /// Execution: Starts the main loop and waits for shutdown signal.
pub async fn run(self) -> Result<()> { pub async fn run(self) -> Result<()> {
match self.command {
Command::Server(_) => {
info!("Application started on port {}", self.config.server.port); info!("Application started on port {}", self.config.server.port);
// Example: Spawn a web server or background worker here. // Spawn the server loop
// We pass a clone of the container or specific services. let server_task = tokio::spawn(commands::server::run(
let server_task = tokio::spawn(server_loop(self.config.clone(), self.container.clone())); self.config.clone(),
self.container.clone(),
));
// Wait for Shutdown Signal // Wait for Shutdown Signal
match signal::ctrl_c().await { match signal::ctrl_c().await {
@ -53,18 +71,12 @@ impl Application {
// Graceful Shutdown Logic // Graceful Shutdown Logic
info!("Shutting down..."); info!("Shutting down...");
server_task.abort(); // Or send a specialized shutdown channel message server_task.abort(); // Or send a specialized shutdown channel message
Ok(())
} }
} Command::ExportDict(export_args) => {
commands::export::run(export_args, self.config, self.container).await;
/// 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 Ok(())
// let _ = container.user_service.do_something();
} }
} }

2
app/src/bootstrap.rs

@ -1,5 +1,5 @@
mod app_config; mod app_config;
mod cli; pub mod cli; // Make cli public so we can access subcommands
mod defaults; mod defaults;
pub use self::app_config::AppConfig; pub use self::app_config::AppConfig;

6
app/src/bootstrap/app_config.rs

@ -2,7 +2,7 @@ use anyhow::{Context, Result};
use config::{Config, Environment, File}; use config::{Config, Environment, File};
use serde::Deserialize; use serde::Deserialize;
use super::cli::Args; use super::cli::GlobalArgs;
use super::defaults::set_defaults; use super::defaults::set_defaults;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
@ -25,7 +25,7 @@ pub struct DatabaseConfig {
} }
impl AppConfig { impl AppConfig {
pub fn build(args: &Args) -> Result<Self> { pub fn build(args: &GlobalArgs, port_override: Option<u16>) -> Result<Self> {
let mut builder = Config::builder(); let mut builder = Config::builder();
// Defaults // Defaults
@ -41,7 +41,7 @@ impl AppConfig {
builder = builder.add_source(Environment::with_prefix("APP").separator("__")); builder = builder.add_source(Environment::with_prefix("APP").separator("__"));
// CLI Overrides Layer // CLI Overrides Layer
if let Some(port) = args.port { if let Some(port) = port_override {
builder = builder.set_override("server.port", port)?; builder = builder.set_override("server.port", port)?;
} }
if let Some(ref level) = args.log_level { if let Some(ref level) = args.log_level {

38
app/src/bootstrap/cli.rs

@ -1,17 +1,49 @@
use super::defaults; use super::defaults;
use clap::Parser; use clap::{Args as ClapArgs, Parser, Subcommand};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about)] #[command(author, version, about)]
pub struct Args { pub struct Args {
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Command,
}
#[derive(ClapArgs, Debug)]
pub struct GlobalArgs {
/// Path to config file /// Path to config file
#[arg(short, long, default_value = "config.toml")] #[arg(short, long, default_value = "config.toml")]
pub config: PathBuf, pub config: PathBuf,
#[arg(long, help = defaults::HELP_LOG)]
pub log_level: Option<String>,
}
#[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)] #[arg(short, long, help = defaults::HELP_PORT)]
pub port: Option<u16>, pub port: Option<u16>,
}
#[arg(long, help = defaults::HELP_LOG)] #[derive(ClapArgs, Debug, Clone)]
pub log_level: Option<String>, pub struct ExportDictArgs {
/// Name of the dictionary to export
#[arg(long)]
pub name: String,
/// Output file path
#[arg(long)]
pub output: PathBuf,
} }

2
app/src/commands.rs

@ -0,0 +1,2 @@
pub mod export;
pub mod server;

12
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);
}

13
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();
}
}

64
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<SystemCli> 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<String>,
// /// List supported systems
// #[arg(long)]
// list_systems: bool,
// }
// fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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 app;
mod bootstrap; mod bootstrap;
mod commands;
mod container; mod container;
use anyhow::Result; use anyhow::Result;
use app::Application; use app::Application;

Loading…
Cancel
Save