Browse Source

WIP: import/export

develop-refactor
chodak166 5 months ago
parent
commit
6d481552d7
  1. 1
      Cargo.toml
  2. 6
      app/Cargo.toml
  3. 62
      app/src/app.rs
  4. 6
      app/src/bootstrap.rs
  5. 12
      app/src/commands/export.rs
  6. 13
      app/src/commands/server.rs
  7. 41
      app/src/config.rs
  8. 20
      app/src/container.rs
  9. 13
      app/src/main.rs
  10. 6
      config.toml
  11. 13
      lib/Cargo.toml
  12. 4
      lib/src/application.rs
  13. 13
      lib/src/application/config.rs
  14. 13
      lib/src/application/errors.rs
  15. 53
      lib/src/application/services.rs
  16. 21
      lib/src/application/traits.rs
  17. 8
      lib/src/core.rs
  18. 51
      lib/src/core/entities.rs
  19. 26
      lib/src/core/errors.rs
  20. 4
      lib/src/core/sys_major.rs
  21. 57
      lib/src/core/sys_major/encoder.rs
  22. 17
      lib/src/core/sys_major/encoder_tests.rs
  23. 6
      lib/src/core/sys_major/rules_en.rs
  24. 6
      lib/src/core/sys_major/rules_pl.rs
  25. 23
      lib/src/core/system.rs
  26. 23
      lib/src/core/traits.rs
  27. 1
      lib/src/infrastructure.rs
  28. 1
      lib/src/infrastructure/errors.rs
  29. 45
      lib/src/infrastructure/json_file_dict_source.rs
  30. 0
      lib/src/infrastructure/sqlite_dict_repository.rs
  31. 41
      lib/src/lib.rs
  32. 1
      lib/src/presentation.rs
  33. 7
      lib/src/presentation/cli.rs
  34. 31
      lib/src/presentation/cli/cli_args.rs
  35. 2
      lib/src/presentation/cli/commands.rs
  36. 10
      lib/src/presentation/cli/commands/encode.rs
  37. 20
      lib/src/presentation/cli/commands/server.rs
  38. 11
      lib/src/presentation/cli/defaults.rs

1
Cargo.toml

@ -8,7 +8,6 @@ resolver = "3"
[workspace.package]
edition = "2024"
authors = ["chodak166 <chodak166@op.pl>"]
license = "MIT"
[profile.release]
lto = true # Link Time Optimization: Analyzes entire program for optimizations

6
app/Cargo.toml

@ -1,11 +1,11 @@
[package]
name = "mnemo-r"
name = "phomnemic"
version = "0.1.0"
edition = "2024"
[dependencies]
# Internal
libmnemor = { path = "../lib" }
applib = { path = "../lib" }
# Runtime & Async
@ -20,4 +20,4 @@ serde_json = "1.0"
toml = "0.9.8"
clap = { version = "4.5", features = ["derive", "env"] }
config = "0.15.19"
const_format = "0.2.35"
const_format = "0.2.35"

62
app/src/app.rs

@ -1,13 +1,11 @@
use crate::bootstrap::cli::Command;
use crate::bootstrap::{AppConfig, CliArgs};
use crate::commands;
use crate::config::AppConfig;
use crate::container::Container;
use anyhow::Result;
use applib::cli::{CliArgs, Command, commands};
use clap::Parser;
use tokio::signal;
use tracing::{info, warn};
use tracing::{debug, info, warn};
/// The Main Application entry point.
pub struct Application {
config: AppConfig,
container: Container,
@ -15,28 +13,17 @@ pub struct Application {
}
impl Application {
/// Bootstrap: Loads config, initializes logging, wires dependencies.
pub async fn build() -> Result<Self> {
let args = CliArgs::parse();
// 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
};
let config = AppConfig::build(&args.global, &args.command)?;
// 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...");
debug!("Bootstrapping application...");
// 4. Wire Dependencies
let container = Container::new(&config).await?;
Ok(Self {
@ -46,37 +33,26 @@ impl Application {
})
}
/// Execution: Starts the main loop and waits for shutdown signal.
pub async fn run(self) -> Result<()> {
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(),
));
// 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);
}
}
// Graceful Shutdown Logic
info!("Shutting down...");
server_task.abort(); // Or send a specialized shutdown channel message
commands::server::run(self.config.server, Self::wait_for_shutdown_signal()).await;
}
Command::ExportDict(export_args) => {
commands::export::run(export_args, self.config, self.container).await;
Command::Encode(_) => {
commands::encode::run(self.config.encoder).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);
}
}
}
}

6
app/src/bootstrap.rs

@ -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;

12
app/src/commands/export.rs

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

13
app/src/commands/server.rs

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

41
app/src/bootstrap/app_config.rs → app/src/config.rs

@ -2,30 +2,18 @@ use anyhow::{Context, Result};
use config::{Config, Environment, File};
use serde::Deserialize;
use super::cli::GlobalArgs;
use super::defaults::set_defaults;
use applib::cli::{Command, GlobalArgs, defaults::set_defaults};
use applib::config::{EncoderConfig, ServerConfig};
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub encoder: EncoderConfig,
pub log_level: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
}
#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
}
impl AppConfig {
pub fn build(args: &GlobalArgs, port_override: Option<u16>) -> Result<Self> {
pub fn build(args: &GlobalArgs, command: &Command) -> Result<Self> {
let mut builder = Config::builder();
// Defaults
@ -37,17 +25,28 @@ impl AppConfig {
builder = builder.add_source(File::from(config_path.as_path()).required(!is_default_path));
// Environment Layer (APP__SERVER__PORT)
builder = builder.add_source(Environment::with_prefix("APP").separator("__"));
// Environment Layer (APP_SERVER_PORT)
builder = builder.add_source(Environment::with_prefix("APP").separator("_"));
// CLI Overrides Layer
if let Some(port) = port_override {
builder = builder.set_override("server.port", port)?;
}
if let Some(ref level) = args.log_level {
builder = builder.set_override("log_level", level.clone())?;
}
match command {
Command::Server(cmd_args) => {
if let Some(port) = cmd_args.port {
builder = builder.set_override("server.port", port)?;
}
}
Command::Encode(cmd_args) => {
if let Some(name) = &cmd_args.system {
builder = builder.set_override("encoder.system", name.as_str())?;
}
builder = builder.set_override("encoder.input", cmd_args.input.clone())?;
}
}
builder
.build()
.context("Failed to build configuration layers")?

20
app/src/container.rs

@ -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 {})
}
}

13
app/src/main.rs

@ -1,6 +1,5 @@
mod app;
mod bootstrap;
mod commands;
mod config;
mod container;
use anyhow::Result;
@ -12,3 +11,13 @@ async fn main() -> Result<()> {
app.run().await?;
Ok(())
}
// use applib::Greeter;
// use applib::infrastructure::std_greeting_printer::StdGreetingPrinter;
// use std::sync::Arc;
// fn main() {
// let printer = Arc::new(StdGreetingPrinter::new());
// let greeter = Greeter::new(printer);
// greeter.say_hello("John");
// }

6
config.toml

@ -0,0 +1,6 @@
# Logging configuration
log_level = "info"
# Server configuration
[server]
port = 8080

13
lib/Cargo.toml

@ -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"

4
lib/src/application.rs

@ -0,0 +1,4 @@
pub mod config;
pub mod errors;
pub mod services;
pub mod traits;

13
lib/src/application/config.rs

@ -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,
}

13
lib/src/application/errors.rs

@ -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)
}
}

53
lib/src/application/services.rs

@ -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(())
}
}

21
lib/src/application/traits.rs

@ -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>>;
}

8
lib/src/core/mod.rs → lib/src/core.rs

@ -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::*;

51
lib/src/core/entities.rs

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

26
lib/src/core/errors.rs

@ -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)
// }
// }

4
lib/src/core/major.rs → lib/src/core/sys_major.rs

@ -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;

57
lib/src/core/major/encoder.rs → lib/src/core/sys_major/encoder.rs

@ -1,34 +1,69 @@
use crate::core::entities::{DictEntries, DictEntry};
use crate::core::traits::SystemEncoder;
#[derive(Debug, Default, Clone)]
pub struct Rule {
pub phoneme_in: String,
pub phoneme_out: String,
pub not_before: Vec<String>,
pub not_after: Vec<String>,
pub only_before: Vec<String>,
pub only_after: Vec<String>,
}
impl Rule {
pub fn into_lowercase(self) -> Self {
Rule {
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),
}
}
fn lower_vec(vec: Vec<String>) -> Vec<String> {
vec.into_iter().map(|s| s.to_lowercase()).collect()
}
}
pub type Rules = Vec<Rule>;
// pub struct rules {
// name: String,
// entries: Rules,
// }
/// (index, encoded value)
type DictMatches = Vec<(usize, String)>;
type RuleMatches = Vec<(usize, String)>;
pub struct Encoder {
dict: DictEntries,
rules: Rules,
}
impl Encoder {
pub fn new(dict: DictEntries) -> Self {
pub fn new(rules: Rules) -> Self {
Encoder {
dict: Encoder::to_lower_dict(dict),
rules: Encoder::to_lower_rules(rules),
}
}
fn to_lower_dict(dict: DictEntries) -> DictEntries {
dict.into_iter()
fn to_lower_rules(rules: Rules) -> Rules {
rules
.into_iter()
.map(|entry| entry.into_lowercase())
.collect()
}
fn match_entry(&self, entry: &DictEntry, word: &str) -> DictMatches {
fn match_entry(&self, entry: &Rule, word: &str) -> RuleMatches {
word.match_indices(&entry.phoneme_in)
.filter(|(index, _)| self.is_context_matched(&entry, &word, *index))
.map(|(index, _)| (index, entry.phoneme_out.clone()))
.collect()
}
fn is_context_matched(&self, entry: &DictEntry, word: &str, index: usize) -> bool {
fn is_context_matched(&self, entry: &Rule, word: &str, index: usize) -> bool {
let before_context = &word[..index];
let after_context = &word[index + entry.phoneme_in.len()..];
dbg!(&before_context);
@ -74,8 +109,8 @@ impl Encoder {
impl SystemEncoder for Encoder {
fn encode(&self, word: &str) -> String {
let mut matches: DictMatches = self
.dict
let mut matches: RuleMatches = self
.rules
.iter()
.flat_map(|entry| self.match_entry(&entry, &word.to_lowercase()))
.collect();

17
lib/src/core/major/encoder_tests.rs → lib/src/core/sys_major/encoder_tests.rs

@ -1,13 +1,12 @@
use crate::core::entities::{DictEntries, DictEntry};
use crate::core::major::*;
use super::encoder::{Encoder, Rule, Rules};
use crate::core::traits::SystemEncoder;
#[cfg(test)]
mod tests {
use super::*;
fn create_single_dict() -> DictEntries {
vec![DictEntry {
fn create_single_dict() -> Rules {
vec![Rule {
phoneme_in: "B".to_string(),
phoneme_out: "2".to_string(),
not_after: vec!["Y".to_string()],
@ -17,17 +16,17 @@ mod tests {
}]
}
fn create_single_dict_min() -> DictEntries {
vec![DictEntry {
fn create_single_dict_min() -> Rules {
vec![Rule {
phoneme_in: "B".to_string(),
phoneme_out: "2".to_string(),
..Default::default()
}]
}
fn create_double_dict() -> DictEntries {
fn create_double_dict() -> Rules {
vec![
DictEntry {
Rule {
phoneme_in: "CD".to_string(),
phoneme_out: "2".to_string(),
not_after: vec!["00".to_string(), "YZ".to_string()],
@ -35,7 +34,7 @@ mod tests {
only_after: vec!["22".to_string(), "AB".to_string()],
only_before: vec!["33".to_string(), "EF".to_string()],
},
DictEntry {
Rule {
phoneme_in: "MN".to_string(),
phoneme_out: "3".to_string(),
..Default::default()

6
lib/src/core/major/dict_en.rs → lib/src/core/sys_major/rules_en.rs

@ -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()],

6
lib/src/core/major/dict_pl.rs → lib/src/core/sys_major/rules_pl.rs

@ -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()],

23
lib/src/core/system.rs

@ -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())),
}
}

23
lib/src/core/traits.rs

@ -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>;
// }

1
lib/src/infrastructure.rs

@ -0,0 +1 @@
pub mod errors;

1
lib/src/infrastructure/errors.rs

@ -0,0 +1 @@

45
lib/src/infrastructure/json_file_dict_source.rs

@ -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)),
}
})
}
}

0
lib/src/core/major/indexer.rs → lib/src/infrastructure/sqlite_dict_repository.rs

41
lib/src/lib.rs

@ -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;

1
lib/src/presentation.rs

@ -0,0 +1 @@
pub mod cli;

7
lib/src/presentation/cli.rs

@ -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;

31
app/src/bootstrap/cli.rs → lib/src/presentation/cli/cli_args.rs

@ -1,10 +1,10 @@
use super::defaults;
use crate::cli::defaults;
use clap::{Args as ClapArgs, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(author, version, about)]
pub struct Args {
pub struct CliArgs {
#[command(flatten)]
pub global: GlobalArgs,
@ -27,8 +27,8 @@ pub enum Command {
/// Start the application server
Server(ServerArgs),
/// Export dictionary to file
ExportDict(ExportDictArgs),
/// Encode a word using given system
Encode(EncodeArgs),
}
#[derive(ClapArgs, Debug, Clone)]
@ -38,12 +38,19 @@ pub struct ServerArgs {
}
#[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,
pub struct EncodeArgs {
#[arg(long, help = defaults::HELP_ENC_SYSTEM)]
pub system: Option<String>,
#[arg(long, help = defaults::HELP_ENC_INPUT)]
pub input: String,
}
#[derive(ClapArgs, Debug, Clone)]
pub struct ImportDictArgs {
#[arg(long, help = defaults::HELP_IMPORT_DICT_NAME)]
pub name: Option<String>,
#[arg(long, help = defaults::HELP_IMPORT_DICT_INPUT)]
pub path: String,
}

2
app/src/commands.rs → lib/src/presentation/cli/commands.rs

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

10
lib/src/presentation/cli/commands/encode.rs

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

20
lib/src/presentation/cli/commands/server.rs

@ -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... ");
}
}

11
app/src/bootstrap/defaults.rs → lib/src/presentation/cli/defaults.rs

@ -6,11 +6,12 @@ use const_format::formatcp;
pub const HOST: &str = "127.0.0.1";
pub const PORT: u16 = 8080;
pub const LOG_LEVEL: &str = "info";
pub const DB_URL: &str = "postgres://localhost:5432/myapp";
pub const DB_MAX_CONNS: u32 = 5;
pub const SYSTEM_NAME: &str = "major_pl";
pub const HELP_PORT: &str = formatcp!("Override Port [default: {}]", PORT);
pub const HELP_LOG: &str = formatcp!("Override Log Level [default: {}]", LOG_LEVEL);
pub const HELP_ENC_SYSTEM: &str = formatcp!("System to use [default: {}]", SYSTEM_NAME);
pub const HELP_ENC_INPUT: &str = formatcp!("Text to encode");
pub fn set_defaults(
builder: ConfigBuilder<config::builder::DefaultState>,
@ -20,8 +21,8 @@ pub fn set_defaults(
// Server
.set_default("server.host", HOST)?
.set_default("server.port", PORT)?
// Database
.set_default("database.url", DB_URL)?
.set_default("database.max_connections", DB_MAX_CONNS)
// Encode
.set_default("encode.system", SYSTEM_NAME)
// Wrapping in Result
.map_err(|e| e.into())
}
Loading…
Cancel
Save