38 changed files with 426 additions and 262 deletions
@ -1,6 +0,0 @@
|
||||
mod app_config; |
||||
pub mod cli; // Make cli public so we can access subcommands
|
||||
mod defaults; |
||||
|
||||
pub use self::app_config::AppConfig; |
||||
pub use self::cli::Args as CliArgs; |
||||
@ -1,12 +0,0 @@
|
||||
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);
|
||||
} |
||||
@ -1,13 +0,0 @@
|
||||
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();
|
||||
} |
||||
} |
||||
@ -1,24 +1,10 @@
|
||||
use crate::bootstrap::AppConfig; |
||||
// use std::sync::Arc;
|
||||
use crate::config::AppConfig; |
||||
|
||||
#[derive(Clone)] |
||||
pub struct Container { |
||||
// pub user_service: Arc<UserService>,
|
||||
// Add other services here (e.g., EmailService, MetricsService)
|
||||
} |
||||
pub struct Container {} |
||||
|
||||
impl Container { |
||||
pub async fn new(config: &AppConfig) -> anyhow::Result<Self> { |
||||
// 1. Infrastructure / Adapters
|
||||
// Using the config to initialize connections
|
||||
// let user_repo = PostgresUserRepository::new(&AppConfig.database.url).await?;
|
||||
// let user_repo = Arc::new(user_repo);
|
||||
|
||||
// // 2. Application / Domain Services
|
||||
// // Injecting the repository into the service
|
||||
// let user_service = UserService::new(user_repo);
|
||||
// let user_service = Arc::new(user_service);
|
||||
|
||||
pub async fn new(_: &AppConfig) -> anyhow::Result<Self> { |
||||
Ok(Self {}) |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,6 @@
|
||||
# Logging configuration |
||||
log_level = "info" |
||||
|
||||
# Server configuration |
||||
[server] |
||||
port = 8080 |
||||
@ -1,7 +1,18 @@
|
||||
[package] |
||||
name = "libmnemor" |
||||
name = "applib" |
||||
version = "0.1.0" |
||||
edition = "2024" |
||||
|
||||
[dependencies] |
||||
once_cell = "1.21.3" |
||||
clap = { version = "4.5", features = ["derive", "env"] } |
||||
const_format = "0.2.35" |
||||
config = "0.15.19" |
||||
tracing = "0.1" |
||||
tokio = { version = "1.48", features = ["full"] } |
||||
anyhow = "1.0" |
||||
serde = { version = "1.0", features = ["derive"] } |
||||
chrono = { version = "0.4", features = ["serde"] } |
||||
thiserror = "1.0" |
||||
async-trait = "0.1" |
||||
parking_lot = "0.12" |
||||
|
||||
@ -0,0 +1,4 @@
|
||||
pub mod config; |
||||
pub mod errors; |
||||
pub mod services; |
||||
pub mod traits; |
||||
@ -0,0 +1,13 @@
|
||||
use crate::core::system::System; |
||||
use serde::Deserialize; |
||||
|
||||
#[derive(Debug, Deserialize, Clone)] |
||||
pub struct ServerConfig { |
||||
pub port: u16, |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize, Clone)] |
||||
pub struct EncoderConfig { |
||||
pub system: System, |
||||
pub input: String, |
||||
} |
||||
@ -0,0 +1,13 @@
|
||||
#[derive(Debug)] |
||||
pub enum RepositoryError { |
||||
NotFound, |
||||
ConnectionFailed, |
||||
InvalidData(String), |
||||
Unexpected(String), |
||||
} |
||||
|
||||
impl std::fmt::Display for RepositoryError { |
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
write!(f, "{:?}", self) |
||||
} |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
use crate::application::traits::{DictRepository, DictSource}; |
||||
use crate::core::entities::{Dict, DictEntry}; |
||||
|
||||
pub struct DictImporter<'a, R> { |
||||
repo: &'a R, |
||||
batch_size: usize, |
||||
} |
||||
|
||||
impl<'a, R: DictRepository> DictImporter<'a, R> { |
||||
pub fn new(repo: &'a R) -> Self { |
||||
Self { |
||||
repo, |
||||
batch_size: 1000, // reasonable default
|
||||
} |
||||
} |
||||
|
||||
pub fn import(&self, name: &str, mut source: impl DictSource) -> Result<(), anyhow::Error> { |
||||
// 1. Ensure Dict exists (Logic: Create if new, or maybe clear existing?)
|
||||
self.repo.create(name)?; |
||||
|
||||
let mut batch = Vec::with_capacity(self.batch_size); |
||||
|
||||
// 2. Stream data
|
||||
while let Some(result) = source.next_entry() { |
||||
match result { |
||||
Ok(entry) => { |
||||
// Optional: Domain Validation logic could go here
|
||||
// if entry.text.is_empty() { continue; }
|
||||
|
||||
batch.push(entry); |
||||
|
||||
// 3. Batch Write
|
||||
if batch.len() >= self.batch_size { |
||||
self.repo.save_entries(name, &batch)?; |
||||
batch.clear(); |
||||
} |
||||
} |
||||
Err(e) => { |
||||
// Logic: Do we abort on malformed JSON or log and continue?
|
||||
// Here we abort for safety.
|
||||
return Err(e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 4. Flush remaining
|
||||
if !batch.is_empty() { |
||||
self.repo.save_entries(name, &batch)?; |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
} |
||||
@ -0,0 +1,21 @@
|
||||
use crate::{ |
||||
application::errors::RepositoryError, |
||||
core::entities::{Dict, DictEntry}, |
||||
}; |
||||
|
||||
pub trait DictRepository { |
||||
fn create(&self, name: &str) -> Result<(), RepositoryError>; |
||||
// Batch saving is usually much faster than 1-by-1 for SQL
|
||||
fn save_entries(&self, dict_name: &str, entries: &[DictEntry]) -> Result<(), RepositoryError>; |
||||
|
||||
fn fetch_many( |
||||
&self, |
||||
name: &str, |
||||
limit: Option<u32>, |
||||
offset: Option<u32>, |
||||
) -> Result<Dict, RepositoryError>; |
||||
} |
||||
|
||||
pub trait DictSource { |
||||
fn next_entry(&mut self) -> Option<Result<DictEntry, anyhow::Error>>; |
||||
} |
||||
@ -1,11 +1,11 @@
|
||||
pub mod entities; |
||||
pub mod errors; |
||||
pub mod major; |
||||
pub mod sys_major; |
||||
pub mod system; |
||||
pub mod traits; |
||||
|
||||
// pub use self::major::*;
|
||||
pub use self::entities::*; |
||||
pub use self::errors::*; |
||||
pub use self::system::*; |
||||
// pub use self::entities::*;
|
||||
// pub use self::errors::*;
|
||||
// pub use self::system::*;
|
||||
pub use self::traits::*; |
||||
@ -1,45 +1,38 @@
|
||||
use std::collections::HashMap; |
||||
|
||||
#[derive(Debug, Default, Clone)] |
||||
pub struct DictEntry { |
||||
pub phoneme_in: String, |
||||
pub phoneme_out: String, |
||||
|
||||
pub not_before: Vec<String>, |
||||
pub not_after: Vec<String>, |
||||
pub type DictEntryId = u32; |
||||
|
||||
pub only_before: Vec<String>, |
||||
pub only_after: Vec<String>, |
||||
#[derive(Debug, Clone, PartialEq)] |
||||
pub struct DictEntry { |
||||
pub id: DictEntryId, |
||||
pub text: String, |
||||
pub metadata: HashMap<String, String>, |
||||
} |
||||
|
||||
impl DictEntry { |
||||
pub fn into_lowercase(self) -> Self { |
||||
pub fn new(id: DictEntryId, text: String) -> Self { |
||||
DictEntry { |
||||
phoneme_in: self.phoneme_in.to_lowercase(), |
||||
phoneme_out: self.phoneme_out.to_lowercase(), |
||||
not_before: Self::lower_vec(self.not_before), |
||||
not_after: Self::lower_vec(self.not_after), |
||||
only_before: Self::lower_vec(self.only_before), |
||||
only_after: Self::lower_vec(self.only_after), |
||||
id, |
||||
text, |
||||
metadata: HashMap::new(), |
||||
} |
||||
} |
||||
|
||||
fn lower_vec(vec: Vec<String>) -> Vec<String> { |
||||
vec.into_iter().map(|s| s.to_lowercase()).collect() |
||||
} |
||||
} |
||||
|
||||
pub type DictEntries = Vec<DictEntry>; |
||||
pub struct Dict { |
||||
name: String, |
||||
entries: DictEntries, |
||||
pub name: String, |
||||
pub entries: HashMap<DictEntryId, DictEntry>, |
||||
} |
||||
|
||||
pub type WordEntryId = u32; |
||||
impl Dict { |
||||
pub fn new(name: String) -> Self { |
||||
Dict { |
||||
name, |
||||
entries: HashMap::new(), |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct WordEntry { |
||||
id: Option<WordEntryId>, |
||||
word: String, |
||||
metadata: HashMap<String, String>, |
||||
pub fn add_entry(&mut self, entry: DictEntry) { |
||||
self.entries.insert(entry.id, entry); |
||||
} |
||||
} |
||||
|
||||
@ -1,15 +1,15 @@
|
||||
use std::fmt; |
||||
// use std::fmt;
|
||||
|
||||
#[derive(Debug)] |
||||
pub enum RepositoryError { |
||||
NotFound, |
||||
ConnectionFailed, |
||||
InvalidData(String), |
||||
Unexpected(String), |
||||
} |
||||
// #[derive(Debug)]
|
||||
// pub enum RepositoryError {
|
||||
// NotFound,
|
||||
// ConnectionFailed,
|
||||
// InvalidData(String),
|
||||
// Unexpected(String),
|
||||
// }
|
||||
|
||||
impl fmt::Display for RepositoryError { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{:?}", self) |
||||
} |
||||
} |
||||
// impl fmt::Display for RepositoryError {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// write!(f, "{:?}", self)
|
||||
// }
|
||||
// }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
pub mod dict_en; |
||||
pub mod dict_pl; |
||||
mod encoder; |
||||
pub mod rules_en; |
||||
pub mod rules_pl; |
||||
|
||||
#[cfg(test)] |
||||
mod encoder_tests; |
||||
@ -1,8 +1,8 @@
|
||||
use crate::core::entities::{DictEntries, DictEntry}; |
||||
use super::encoder::{Rule, Rules}; |
||||
|
||||
pub fn get_dict() -> DictEntries { |
||||
pub fn get_rules() -> Rules { |
||||
vec![ |
||||
DictEntry { |
||||
Rule { |
||||
phoneme_in: "EN".to_string(), |
||||
phoneme_out: "2".to_string(), |
||||
not_after: vec!["Y".to_string()], |
||||
@ -1,8 +1,8 @@
|
||||
use crate::core::entities::{DictEntries, DictEntry}; |
||||
use super::encoder::{Rule, Rules}; |
||||
|
||||
pub fn get_dict() -> DictEntries { |
||||
pub fn get_rules() -> Rules { |
||||
vec![ |
||||
DictEntry { |
||||
Rule { |
||||
phoneme_in: "PL".to_string(), |
||||
phoneme_out: "2".to_string(), |
||||
not_after: vec!["Y".to_string()], |
||||
@ -1,15 +1,30 @@
|
||||
use serde::Deserialize; |
||||
|
||||
use crate::core::SystemEncoder; |
||||
use crate::core::major; |
||||
use crate::core::sys_major as major; |
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] |
||||
pub enum System { |
||||
#[serde(rename = "major_en")] |
||||
MajorEn, |
||||
#[serde(rename = "major_pl")] |
||||
MajorPl, |
||||
} |
||||
|
||||
// from:
|
||||
impl From<&str> for System { |
||||
fn from(s: &str) -> Self { |
||||
match s { |
||||
"major_en" => System::MajorEn, |
||||
"major_pl" => System::MajorPl, |
||||
_ => panic!("Unknown system: {}", s), |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn create_encoder(system: &System) -> Box<dyn SystemEncoder> { |
||||
match system { |
||||
System::MajorPl => Box::new(major::Encoder::new(major::dict_pl::get_dict())), |
||||
System::MajorEn => Box::new(major::Encoder::new(major::dict_en::get_dict())), |
||||
System::MajorPl => Box::new(major::Encoder::new(major::rules_pl::get_rules())), |
||||
System::MajorEn => Box::new(major::Encoder::new(major::rules_en::get_rules())), |
||||
} |
||||
} |
||||
|
||||
@ -1,18 +1,15 @@
|
||||
use crate::core::entities::{Dict, WordEntry, WordEntryId}; |
||||
use crate::core::errors::RepositoryError; |
||||
|
||||
pub trait SystemEncoder { |
||||
fn encode(&self, word: &str) -> String; |
||||
} |
||||
|
||||
pub trait WordRepository { |
||||
fn save(word: &WordEntry) -> Result<WordEntryId, RepositoryError>; |
||||
fn save_many(words: &Vec<WordEntry>) -> Result<(), RepositoryError>; |
||||
fn fetch(id: WordEntryId) -> Result<WordEntry, RepositoryError>; |
||||
fn fetch_many(ids: &Vec<WordEntryId>) -> Result<Vec<WordEntry>, RepositoryError>; |
||||
} |
||||
// pub trait WordRepository {
|
||||
// fn save(word: &WordEntry) -> Result<WordEntryId, RepositoryError>;
|
||||
// fn save_many(words: &Vec<WordEntry>) -> Result<(), RepositoryError>;
|
||||
// fn fetch(id: WordEntryId) -> Result<WordEntry, RepositoryError>;
|
||||
// fn fetch_many(ids: &Vec<WordEntryId>) -> Result<Vec<WordEntry>, RepositoryError>;
|
||||
// }
|
||||
|
||||
pub trait DictRepository { |
||||
fn save(dict: &Dict) -> Result<(), RepositoryError>; |
||||
fn fetch(name: &str) -> Result<Dict, RepositoryError>; |
||||
} |
||||
// pub trait DictRepository {
|
||||
// fn save(dict: &Dict) -> Result<(), RepositoryError>;
|
||||
// fn fetch(name: &str) -> Result<Dict, RepositoryError>;
|
||||
// }
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
use crate::application::ports::DictSource; |
||||
use crate::core::entities::{DictEntry, DictEntryId}; |
||||
use serde::Deserialize; |
||||
use std::fs::File; |
||||
use std::io::BufReader; |
||||
use std::path::Path; |
||||
|
||||
// The "Wire Format".
|
||||
// It exists ONLY here to map external JSON names to internal Entity names.
|
||||
#[derive(Deserialize)] |
||||
struct JsonEntry { |
||||
id: u32, |
||||
word: String, // "word" in JSON, "text" in Entity
|
||||
// If JSON has extra fields we don't care about, Serde ignores them.
|
||||
} |
||||
|
||||
pub struct JsonFileDictSource { |
||||
// Helper iterator from Serde
|
||||
iter: |
||||
serde_json::StreamDeserializer<'static, serde_json::de::IoRead<BufReader<File>>, JsonEntry>, |
||||
} |
||||
|
||||
impl JsonFileDictSource { |
||||
pub fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> { |
||||
let file = File::open(path)?; |
||||
let reader = BufReader::new(file); |
||||
let iter = serde_json::Deserializer::from_reader(reader).into_iter::<JsonEntry>(); |
||||
Ok(Self { iter }) |
||||
} |
||||
} |
||||
|
||||
impl DictSource for JsonFileDictSource { |
||||
fn next_entry(&mut self) -> Option<Result<DictEntry, anyhow::Error>> { |
||||
self.iter.next().map(|res| { |
||||
match res { |
||||
Ok(json) => { |
||||
// MAPPING HAPPENS HERE.
|
||||
// This is type-safe. If DictEntry::new signature changes, this breaks.
|
||||
Ok(DictEntry::new(json.id as DictEntryId, json.word)) |
||||
} |
||||
Err(e) => Err(anyhow::Error::new(e)), |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
@ -1,36 +1,9 @@
|
||||
// pub mod core;
|
||||
|
||||
mod application; |
||||
mod core; |
||||
pub mod infrastructure; |
||||
mod presentation; |
||||
|
||||
pub use self::core::System; |
||||
pub use self::core::SystemEncoder; |
||||
pub use self::core::create_encoder; |
||||
|
||||
// pub struct TextProcessor {
|
||||
// prefix: String,
|
||||
// }
|
||||
|
||||
// impl TextProcessor {
|
||||
// /// Create a new processor with custom prefix
|
||||
// pub fn new(prefix: &str) -> Self {
|
||||
// Self {
|
||||
// prefix: prefix.to_string(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Process input text by adding prefix and converting to uppercase
|
||||
// pub fn process(&self, input: &str) -> String {
|
||||
// format!("{} {}", self.prefix, input.to_uppercase())
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn test_processing() {
|
||||
// let processor = TextProcessor::new(">> ");
|
||||
// assert_eq!(processor.process("hello"), ">> HELLO");
|
||||
// }
|
||||
// }
|
||||
pub use self::application::config; |
||||
pub use self::core::system; |
||||
pub use self::core::traits::SystemEncoder; |
||||
pub use self::presentation::cli; |
||||
|
||||
@ -0,0 +1,7 @@
|
||||
pub mod cli_args; |
||||
pub mod commands; |
||||
pub mod defaults; |
||||
|
||||
pub use self::cli_args::CliArgs; |
||||
pub use self::cli_args::Command; |
||||
pub use self::cli_args::GlobalArgs; |
||||
@ -1,2 +1,2 @@
|
||||
pub mod export; |
||||
pub mod encode; |
||||
pub mod server; |
||||
@ -0,0 +1,10 @@
|
||||
use crate::application::config::EncoderConfig; |
||||
use crate::core::system; |
||||
use tracing::debug; |
||||
|
||||
pub async fn run(config: EncoderConfig) { |
||||
debug!("Running greeter with config {:?}", config); |
||||
let encoder = system::create_encoder(&config.system); |
||||
let result = encoder.encode(&config.input); |
||||
println!("{}", result); |
||||
} |
||||
@ -0,0 +1,20 @@
|
||||
use crate::application::config::ServerConfig; |
||||
use tracing::info; |
||||
|
||||
pub async fn run(config: ServerConfig, blocker: impl std::future::Future<Output = ()>) { |
||||
info!("Running server with config: {:#?}", config); |
||||
|
||||
tokio::select! { |
||||
_ = server_loop() => {}, |
||||
_ = blocker => { |
||||
info!("Shutting down server..."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
async fn server_loop() { |
||||
loop { |
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; |
||||
info!("Health check... "); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue