diff --git a/app/src/app.rs b/app/src/app.rs index e6fabf6..48975a9 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -1,8 +1,8 @@ +use crate::cli::args::{CliArgs, Command}; +use crate::cli::handlers::resolve_command; use crate::config::AppConfig; use crate::container::Container; -use crate::handlers::resolve_command; use anyhow::Result; -use applib::cli::{CliArgs, Command}; use clap::Parser; use tracing::debug; diff --git a/app/src/cli.rs b/app/src/cli.rs new file mode 100644 index 0000000..79a457e --- /dev/null +++ b/app/src/cli.rs @@ -0,0 +1,5 @@ +pub mod args; +pub mod commands; +pub mod defaults; +pub mod handlers; +pub mod traits; diff --git a/lib/src/presentation/cli/cli_args.rs b/app/src/cli/args.rs similarity index 98% rename from lib/src/presentation/cli/cli_args.rs rename to app/src/cli/args.rs index d9c5e5c..e7a0f38 100644 --- a/lib/src/presentation/cli/cli_args.rs +++ b/app/src/cli/args.rs @@ -1,4 +1,5 @@ -use crate::cli::{ConfigurableCommand, defaults}; +use crate::cli::defaults; +use crate::cli::traits::ConfigurableCommand; use anyhow::Result; use clap::{Args as ClapArgs, Parser, Subcommand}; use config::ConfigBuilder; diff --git a/lib/src/presentation/cli/commands.rs b/app/src/cli/commands.rs similarity index 100% rename from lib/src/presentation/cli/commands.rs rename to app/src/cli/commands.rs diff --git a/app/src/cli/commands/decode.rs b/app/src/cli/commands/decode.rs new file mode 100644 index 0000000..e492d8f --- /dev/null +++ b/app/src/cli/commands/decode.rs @@ -0,0 +1,22 @@ +use applib::application::config::DecoderConfig; +use applib::core::sys_major::decoder::Decoder; +use applib::core::sys_major::{self as major, LenValueMap}; +use applib::core::traits::SystemDecoder; +use applib::system::System; +use std::sync::Arc; + +pub async fn run(config: DecoderConfig) { + let decoder: Box = match config.system { + System::MajorPl => Box::new(Decoder::new(major::rules_pl::get_rules())), + System::MajorEn => Box::new(Decoder::new(major::rules_en::get_rules())), + // _ => { + // eprintln!("Unknown system: {:?}", config.system); + // return; + // } + }; + + match decoder.decode(&config.input) { + Ok(result) => println!("{:?}", result), + Err(e) => eprintln!("Error: {}", e), + } +} diff --git a/app/src/cli/commands/encode.rs b/app/src/cli/commands/encode.rs new file mode 100644 index 0000000..9ecb49f --- /dev/null +++ b/app/src/cli/commands/encode.rs @@ -0,0 +1,36 @@ +use applib::application::config::EncoderConfig; +use applib::core::sys_major::encoder::Encoder; +use applib::core::sys_major::{self as major, LenValueMap}; +use applib::core::traits::{DictRepository, SystemEncoder}; +use applib::system::System; +use tracing::debug; + +pub async fn run(config: EncoderConfig, dict: &dyn DictRepository) { + debug!("Running encoder with config {:?}", config); + + let system_encoder: Box = match config.system { + System::MajorPl | System::MajorEn => { + let decoder = major::Decoder::new(match config.system { + System::MajorPl => major::rules_pl::get_rules(), + System::MajorEn => major::rules_en::get_rules(), + // _ => unreachable!(), + }); + let stream = dict.stream_batches(100).await.unwrap(); + let lvmap = LenValueMap::from_stream(stream, &decoder).await.unwrap(); + Box::new(Encoder::new(lvmap)) + } // _ => { + // eprintln!("Unknown system: {:?}", config.system); + // return; + // } + }; + + let result = system_encoder.encode(&config.input); + + match result { + Ok(res) => { + let json = serde_json::to_string_pretty(&res).expect("JSON serialization failed"); + println!("{}", json); + } + Err(e) => eprintln!("Error encoding: {:?}", e), + } +} diff --git a/app/src/cli/commands/import_dict.rs b/app/src/cli/commands/import_dict.rs new file mode 100644 index 0000000..057be2c --- /dev/null +++ b/app/src/cli/commands/import_dict.rs @@ -0,0 +1,12 @@ +use anyhow::Result; +use applib::DictImporter; +use applib::application::config::ImportDictConfig; + +pub async fn run(config: ImportDictConfig, importer: DictImporter) -> Result<()> { + // Importer expects an impl DictSource + // We need to create a DictSource from the path + use applib::infrastructure::json_file_dict_source::JsonFileDictSource; + let source = JsonFileDictSource::new(&config.path)?; + importer.import(source).await?; + Ok(()) +} diff --git a/app/src/cli/commands/server.rs b/app/src/cli/commands/server.rs new file mode 100644 index 0000000..d5fa916 --- /dev/null +++ b/app/src/cli/commands/server.rs @@ -0,0 +1,6 @@ +use applib::application::config::ServerConfig; +use std::future::Future; + +pub async fn run(config: ServerConfig, signal: impl Future + Send + 'static) { + applib::presentation::server::run(config, signal).await; +} diff --git a/lib/src/presentation/cli/defaults.rs b/app/src/cli/defaults.rs similarity index 100% rename from lib/src/presentation/cli/defaults.rs rename to app/src/cli/defaults.rs diff --git a/app/src/cli/handlers.rs b/app/src/cli/handlers.rs new file mode 100644 index 0000000..4423c40 --- /dev/null +++ b/app/src/cli/handlers.rs @@ -0,0 +1,81 @@ +use crate::cli::args::{Command, DecodeArgs, EncodeArgs, ImportDictArgs, ServerArgs}; +use crate::cli::commands; +use crate::cli::traits::ConfigurableCommand; +use crate::config::AppConfig; +use crate::container::Container; +use anyhow::Result; +use async_trait::async_trait; +use tokio::signal; +use tracing::{info, warn}; + +#[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 {} + +pub fn resolve_command(command: &Command) -> &dyn AppCommand { + match command { + Command::Server(args) => args, + Command::Decode(args) => args, + Command::Encode(args) => args, + Command::ImportDict(args) => args, + } +} + +#[async_trait] +impl CommandExecutor for ServerArgs { + async fn execute(&self, config: &AppConfig, _container: &Container) -> Result<()> { + let config = config.server.as_ref().expect("Server config not set"); + commands::server::run(config.clone(), wait_for_shutdown_signal()).await; + Ok(()) + } +} + +#[async_trait] +impl CommandExecutor for DecodeArgs { + async fn execute(&self, config: &AppConfig, _container: &Container) -> Result<()> { + let config = config.decoder.as_ref().expect("Decoder config not set"); + commands::decode::run(config.clone()).await; + Ok(()) + } +} + +#[async_trait] +impl CommandExecutor for EncodeArgs { + async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()> { + let config = config.encoder.as_ref().expect("Encoder config not set"); + let repo = container.create_dict_repo("demo_pl").await?; + commands::encode::run(config.clone(), repo.as_ref()).await; + Ok(()) + } +} + +#[async_trait] +impl CommandExecutor for ImportDictArgs { + async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()> { + let config = config + .import_dict + .as_ref() + .expect("ImportDict config not set"); + let importer = container.create_dict_importer(&config.name).await?; + commands::import_dict::run(config.clone(), importer).await?; + Ok(()) + } +} + +async fn 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); + } + } +} diff --git a/app/src/cli/traits.rs b/app/src/cli/traits.rs new file mode 100644 index 0000000..18f4de6 --- /dev/null +++ b/app/src/cli/traits.rs @@ -0,0 +1,14 @@ +use anyhow::Result; +pub use config::ConfigBuilder; +pub use config::builder::DefaultState; + +pub trait ConfigurableCommand { + fn apply_defaults( + &self, + builder: ConfigBuilder, + ) -> Result>; + fn apply_overrides( + &self, + builder: ConfigBuilder, + ) -> Result>; +} diff --git a/app/src/config.rs b/app/src/config.rs index 2470884..3ab080a 100644 --- a/app/src/config.rs +++ b/app/src/config.rs @@ -2,8 +2,9 @@ use anyhow::{Context, Result}; use config::{Config, Environment, File}; use serde::Deserialize; -use applib::cli::{ConfigurableCommand, GlobalArgs}; -use applib::config::*; +use crate::cli::args::GlobalArgs; +use crate::cli::traits::ConfigurableCommand; +use applib::application::config::*; #[derive(Debug, Deserialize, Clone)] pub struct AppConfig { diff --git a/app/src/main.rs b/app/src/main.rs index ab9b63f..fee53f5 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -1,8 +1,7 @@ mod app; +pub mod cli; mod config; mod container; -mod handlers; -mod traits; use anyhow::Result; use app::Application; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index e36e612..a62f2e5 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] once_cell = "1.21.3" -clap = { version = "4.5", features = ["derive", "env"] } +# clap = { version = "4.5", features = ["derive", "env"] } # Removed const_format = "0.2.35" config = "0.15.19" tracing = "0.1" diff --git a/lib/src/core/sys_major.rs b/lib/src/core/sys_major.rs index 41402c1..7678c9e 100644 --- a/lib/src/core/sys_major.rs +++ b/lib/src/core/sys_major.rs @@ -1,5 +1,5 @@ -mod decoder; -mod encoder; +pub mod decoder; +pub mod encoder; mod lvmap; pub mod rules_en; pub mod rules_pl; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index bb90b6d..6f98201 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,10 +1,10 @@ pub mod application; -mod core; +pub mod core; pub mod infrastructure; -mod presentation; +pub mod presentation; pub use self::application::config; pub use self::application::services::DictImporter; pub use self::core::system; pub use self::core::traits; -pub use self::presentation::cli; +// pub use self::presentation::cli; // Removed as we are deleting it diff --git a/lib/src/presentation.rs b/lib/src/presentation.rs index 4f77372..74f47ad 100644 --- a/lib/src/presentation.rs +++ b/lib/src/presentation.rs @@ -1 +1 @@ -pub mod cli; +pub mod server; diff --git a/lib/src/presentation/cli/commands/decode.rs b/lib/src/presentation/cli/commands/decode.rs deleted file mode 100644 index a95cda5..0000000 --- a/lib/src/presentation/cli/commands/decode.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::application::config::DecoderConfig; -use crate::core::system; -use tracing::debug; - -pub async fn run(config: DecoderConfig) { - debug!("Running decoder with config {:?}", config); - let decoder = system::create_decoder(&config.system); - let result = decoder.decode(&config.input).unwrap(); - println!("{}", result.as_str()); -} diff --git a/lib/src/presentation/cli/commands/encode.rs b/lib/src/presentation/cli/commands/encode.rs deleted file mode 100644 index 3750542..0000000 --- a/lib/src/presentation/cli/commands/encode.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::application::config::EncoderConfig; -use crate::core::{DictRepository, system}; -use tracing::debug; - -pub async fn run(config: EncoderConfig, dict: &dyn DictRepository) { - debug!("Running encoder with config {:?}", config); - let encoder = system::create_encoder(&config.system, dict).await; - let result = encoder.encode(&config.input).unwrap(); - - let json = serde_json::to_string_pretty(&result).expect("JSON serialization failed"); - - println!("{}", json); -} diff --git a/lib/src/presentation/cli/commands/import_dict.rs b/lib/src/presentation/cli/commands/import_dict.rs deleted file mode 100644 index 40dc935..0000000 --- a/lib/src/presentation/cli/commands/import_dict.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::application::{config::ImportDictConfig, services::DictImporter}; -use crate::infrastructure::json_file_dict_source::JsonFileDictSource; -use tracing::{debug, error, info}; - -pub async fn run(config: ImportDictConfig, importer: DictImporter) -> Result<(), anyhow::Error> { - debug!("Importing dict with config {:?}", config); - - info!( - "Starting import of dictionary '{}' from file '{}'", - config.name, config.path - ); - - // Create the JSON file source (will auto-generate IDs starting from 1) - let source = JsonFileDictSource::new(&config.path)?; - - // Perform the import (this will call create() first) - match importer.import(source).await { - Ok(()) => { - info!("Successfully imported dictionary '{}'", config.name); - Ok(()) - } - Err(e) => { - error!("Failed to import dictionary '{}': {}", config.name, e); - Err(e) - } - } -} diff --git a/lib/src/presentation/cli/commands/server.rs b/lib/src/presentation/server.rs similarity index 100% rename from lib/src/presentation/cli/commands/server.rs rename to lib/src/presentation/server.rs