diff --git a/apps/app_cli/src/commands.rs b/apps/app_cli/src/commands.rs index ab7645a..cf2be0f 100644 --- a/apps/app_cli/src/commands.rs +++ b/apps/app_cli/src/commands.rs @@ -1,6 +1,7 @@ pub mod decode; pub mod encode; pub mod import_dict; +pub mod list_dicts; use crate::config::AppConfig; use crate::container::Container; @@ -22,6 +23,9 @@ pub enum Command { /// Import dictionary ImportDict(import_dict::ImportDictCmd), + + /// List all dictionaries + ListDicts(list_dicts::ListDictsCmd), } impl Command { @@ -30,6 +34,7 @@ impl Command { Command::Decode(cmd) => Box::new(cmd), Command::Encode(cmd) => Box::new(cmd), Command::ImportDict(cmd) => Box::new(cmd), + Command::ListDicts(cmd) => Box::new(cmd), } } } diff --git a/apps/app_cli/src/commands/list_dicts.rs b/apps/app_cli/src/commands/list_dicts.rs new file mode 100644 index 0000000..e85acb5 --- /dev/null +++ b/apps/app_cli/src/commands/list_dicts.rs @@ -0,0 +1,77 @@ +use crate::commands::{ClapArgs, Configurable, Executable}; +use crate::config::AppConfig; +use crate::container::Container; +use anyhow::Result; +use async_trait::async_trait; +use config::ConfigBuilder; +use config::builder::DefaultState; +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone)] +pub struct Config { + pub show_counts: bool, +} + +#[derive(ClapArgs, Debug, Clone)] +pub struct ListDictsCmd { + #[arg(short, long, help = defaults::HELP_LIST_DICTS_COUNTS)] + pub show_counts: bool, +} + +impl Configurable for ListDictsCmd { + fn apply_defaults( + &self, + builder: ConfigBuilder, + ) -> Result> { + builder + .set_default("list_dicts.show_counts", defaults::SHOW_COUNTS) + .map_err(Into::into) + } + + fn apply_overrides( + &self, + builder: ConfigBuilder, + ) -> Result> { + builder + .set_override("list_dicts.show_counts", self.show_counts) + .map_err(Into::into) + } +} + +#[async_trait] +impl Executable for ListDictsCmd { + async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()> { + let config = config + .list_dicts + .as_ref() + .expect("ListDicts config not set"); + + let repo = container.create_dict_repo("default").await?; + + let dicts = repo.fetch_dicts().await?; + + if dicts.is_empty() { + println!("No dictionaries found."); + return Ok(()); + } + + println!("Dictionaries:"); + for dict_name in &dicts { + if config.show_counts { + let count_repo = container.create_dict_repo(dict_name).await?; + let count = count_repo.count_entries().await?; + println!(" - {} ({} entries)", dict_name, count); + } else { + println!(" - {}", dict_name); + } + } + + Ok(()) + } +} + +mod defaults { + use const_format::formatcp; + pub const SHOW_COUNTS: bool = false; + pub const HELP_LIST_DICTS_COUNTS: &str = formatcp!("Show entry counts for each dictionary"); +} diff --git a/apps/app_cli/src/config.rs b/apps/app_cli/src/config.rs index 23acf15..15d0efa 100644 --- a/apps/app_cli/src/config.rs +++ b/apps/app_cli/src/config.rs @@ -1,5 +1,4 @@ use crate::commands::*; -// use crate::commands::{Configurable, GlobalArgs}; use anyhow::{Context, Result}; use config::{Config, Environment, File}; use serde::Deserialize; @@ -12,6 +11,8 @@ pub struct AppConfig { pub encode: Option, #[serde(default)] pub import_dict: Option, + #[serde(default)] + pub list_dicts: Option, pub log_level: String, } diff --git a/config.toml b/config.toml index db1cf0c..e8b4036 100644 --- a/config.toml +++ b/config.toml @@ -1,4 +1 @@ log_level = "info" - -[decode] -input = "CONFIGTEST" diff --git a/lib/src/dictionary.rs b/lib/src/dictionary.rs index 5886316..6419fd9 100644 --- a/lib/src/dictionary.rs +++ b/lib/src/dictionary.rs @@ -54,6 +54,10 @@ pub trait DictRepository: Send + Sync { fn use_dict(&mut self, name: &str); async fn create_dict(&self) -> Result<(), RepositoryError>; + async fn fetch_dicts(&self) -> Result, RepositoryError>; + + async fn count_entries(&self) -> Result; + /// "Upsert" logic: /// - If entry exists (by text), update metadata. /// - If not, insert new. diff --git a/lib/src/dictionary/infrastructure/sqlite_dict_repository.rs b/lib/src/dictionary/infrastructure/sqlite_dict_repository.rs index 96e74d5..93470b6 100644 --- a/lib/src/dictionary/infrastructure/sqlite_dict_repository.rs +++ b/lib/src/dictionary/infrastructure/sqlite_dict_repository.rs @@ -93,6 +93,10 @@ impl SqliteDictRepository { #[async_trait::async_trait] impl DictRepository for SqliteDictRepository { + fn use_dict(&mut self, name: &str) { + self.dict_name = name.to_string(); + } + async fn create_dict(&self) -> Result<(), RepositoryError> { sqlx::query("INSERT OR IGNORE INTO dictionaries (name) VALUES (?)") .bind(&self.dict_name) @@ -102,8 +106,30 @@ impl DictRepository for SqliteDictRepository { Ok(()) } - fn use_dict(&mut self, name: &str) { - self.dict_name = name.to_string(); + async fn fetch_dicts(&self) -> Result, RepositoryError> { + let rows = sqlx::query("SELECT name FROM dictionaries ORDER BY name") + .fetch_all(&self.pool) + .await + .map_err(|e| RepositoryError::StorageError(e.to_string()))?; + + let dicts = rows.iter().map(|row| row.get("name")).collect(); + + Ok(dicts) + } + + async fn count_entries(&self) -> Result { + let dict_id = self.get_dict_id().await?; + + let row = sqlx::query("SELECT COUNT(*) as count FROM entries WHERE dictionary_id = ?") + .bind(dict_id) + .fetch_optional(&self.pool) + .await + .map_err(|e| RepositoryError::StorageError(e.to_string()))?; + + match row { + Some(r) => Ok(r.get::("count") as u64), + None => Ok(0), + } } async fn save_entries(&self, entries: &[DictEntry]) -> Result<(), RepositoryError> {