diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index be304f0..797faf6 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -16,6 +16,7 @@ use dt_core::{ scheduler::ParserCandidateScheduler, }; use std::{ + collections::{HashMap, HashSet}, fs::File, io::{prelude::*, BufReader}, path::PathBuf, @@ -340,7 +341,10 @@ impl Project { Ok(()) } - pub fn add_module(&self, symbol_dependency: &SymbolDependency) -> anyhow::Result<()> { + pub fn add_module( + &self, + symbol_dependency: &SymbolDependency, + ) -> anyhow::Result { let module = self.project.add_module( &self.db.conn, &self.remove_prefix(&symbol_dependency.canonical_path), @@ -351,6 +355,46 @@ impl Project { self.handle_default_export(&module, symbol_dependency)?; self.handle_re_export_star_from(&module, symbol_dependency)?; + Ok(module) + } + + pub fn add_translation( + &self, + translation_json: &HashMap, + ) -> anyhow::Result<()> { + for (key, value) in translation_json.iter() { + self.project.add_translation(&self.db.conn, key, value)?; + } + Ok(()) + } + + pub fn add_i18n_usage( + &self, + module: &models::Module, + i18n_usage: &HashMap>, + ) -> anyhow::Result<()> { + for (symbol_name, i18n_keys) in i18n_usage.iter() { + let symbol = module + .get_symbol( + &self.db.conn, + models::SymbolVariant::LocalVariable, + &symbol_name, + ) + .context(format!( + "try to add i18n keys for symbol {}, but symbol doesn't exist", + symbol_name, + ))?; + for key in i18n_keys.iter() { + let translation = + self.project + .get_translation(&self.db.conn, key) + .context(format!( + "try to add translation for symbol {}, but translation {} doesn't exist", + symbol_name, key + ))?; + models::TranslationUsage::create(&self.db.conn, &translation, &symbol)?; + } + } Ok(()) } } @@ -359,23 +403,39 @@ fn main() -> anyhow::Result<()> { let cli = Cli::parse(); let project_root = PathBuf::from(&cli.input).to_canonical_string()?; let project = Project::new(&project_root, "./database/1010.db3")?; - let translation_json = File::open(&cli.translation_path)?; - let translation_json_reader = BufReader::new(translation_json); + let translation_file = File::open(&cli.translation_path)?; + let translation_json_reader = BufReader::new(translation_file); let mut scheduler = ParserCandidateScheduler::new(&project_root); let mut depend_on_graph = DependOnGraph::new(&project_root); let mut symbol_to_route = SymbolToRoutes::new(); let mut i18n_to_symbol = I18nToSymbol::new(); + let translation_json: HashMap = + serde_json::from_reader(translation_json_reader)?; + project + .add_translation(&translation_json) + .context("add translation to project")?; + loop { match scheduler.get_one_candidate() { Some(c) => { let module_src = c.to_str().context(format!("to_str() failed: {:?}", c))?; let module_ast = Input::Path(module_src).get_module_ast()?; let symbol_dependency = collect_symbol_dependency(&module_ast, module_src)?; - i18n_to_symbol.collect_i18n_usage(module_src, &module_ast)?; + let i18n_usage = i18n_to_symbol.collect_i18n_usage(module_src, &module_ast)?; symbol_to_route.collect_route_dependency(&module_ast, &symbol_dependency)?; - project.add_module(&symbol_dependency)?; + let module = project.add_module(&symbol_dependency).context(format!( + "add module {} to project", + symbol_dependency.canonical_path + ))?; + project + .add_i18n_usage(&module, &i18n_usage) + .context(format!( + "add i18n usage of module {} to project", + symbol_dependency.canonical_path + ))?; + depend_on_graph.add_symbol_dependency(symbol_dependency)?; scheduler.mark_candidate_as_parsed(c); } @@ -385,7 +445,7 @@ fn main() -> anyhow::Result<()> { let portable = Portable::new( project_root.to_owned(), - serde_json::from_reader(translation_json_reader)?, + translation_json, i18n_to_symbol.table, symbol_to_route.table, UsedByGraph::from(&depend_on_graph), diff --git a/crates/dt_database/src/models.rs b/crates/dt_database/src/models.rs index fc147aa..619dbae 100644 --- a/crates/dt_database/src/models.rs +++ b/crates/dt_database/src/models.rs @@ -56,6 +56,19 @@ impl Project { Err(_) => self.add_module(conn, path), } } + + pub fn add_translation( + &self, + conn: &Connection, + key: &str, + value: &str, + ) -> anyhow::Result { + Translation::create(conn, self, key, value) + } + + pub fn get_translation(&self, conn: &Connection, key: &str) -> anyhow::Result { + Translation::retrieve(conn, self, key) + } } #[derive(Debug)] @@ -119,6 +132,15 @@ impl Module { Symbol::create(conn, self, variant, name) } + pub fn get_symbol( + &self, + conn: &Connection, + variant: SymbolVariant, + name: &str, + ) -> anyhow::Result { + Symbol::retrieve(conn, self, variant, name) + } + /// single thread only: retrieve then create pub fn get_or_create_symbol( &self, @@ -291,6 +313,7 @@ impl SymbolDependency { #[derive(Debug)] pub struct Translation { pub id: usize, + pub project_id: usize, pub key: String, pub value: String, } @@ -299,9 +322,10 @@ impl Model for Translation { fn table() -> String { " translation ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key TEXT NOT NULL, - value TEXT NOT NULL + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER REFERENCES project(id) ON DELETE CASCADE, + key TEXT NOT NULL, + value TEXT NOT NULL ) " .to_string() @@ -312,10 +336,39 @@ impl Translation { pub fn from_row(row: &Row) -> rusqlite::Result { Ok(Self { id: row.get(0)?, - key: row.get(1)?, - value: row.get(2)?, + project_id: row.get(1)?, + key: row.get(2)?, + value: row.get(3)?, }) } + + /// single thread only: last_insert_rowid() + pub fn create( + conn: &Connection, + project: &Project, + key: &str, + value: &str, + ) -> anyhow::Result { + conn.execute( + "INSERT INTO translation (project_id, key, value) VALUES (?1, ?2, ?3)", + params![project.id, key, value], + )?; + let translation = conn.query_row( + "SELECT * FROM translation WHERE id=last_insert_rowid()", + (), + Self::from_row, + )?; + Ok(translation) + } + + pub fn retrieve(conn: &Connection, project: &Project, key: &str) -> anyhow::Result { + let translation = conn.query_row( + "SELECT * FROM translation WHERE (project_id, key) = (?1, ?2)", + params![project.id, key], + Self::from_row, + )?; + Ok(translation) + } } // Join Table @@ -347,6 +400,24 @@ impl TranslationUsage { symbol_id: row.get(2)?, }) } + + /// single thread only: last_insert_rowid() + pub fn create( + conn: &Connection, + translation: &Translation, + symbol: &Symbol, + ) -> anyhow::Result { + conn.execute( + "INSERT INTO translation_usage (translation_id, symbol_id) VALUES (?1, ?2)", + params![translation.id, symbol.id], + )?; + let translation = conn.query_row( + "SELECT * FROM translation_usage WHERE id=last_insert_rowid()", + (), + Self::from_row, + )?; + Ok(translation) + } } #[derive(Debug)] diff --git a/crates/dt_i18n/src/collect.rs b/crates/dt_i18n/src/collect.rs index 2381b7f..83170dc 100644 --- a/crates/dt_i18n/src/collect.rs +++ b/crates/dt_i18n/src/collect.rs @@ -17,9 +17,9 @@ impl I18nToSymbol { &mut self, module_path: &str, module_ast: &Module, - ) -> anyhow::Result<()> { + ) -> anyhow::Result>> { let i18n_usage = core::collect_translation(module_ast)?; - for (symbol, i18n_keys) in i18n_usage { + for (symbol, i18n_keys) in i18n_usage.iter() { for i18n_key in i18n_keys.iter() { if !self.table.contains_key(i18n_key) { self.table.insert(i18n_key.to_owned(), HashMap::new()); @@ -38,6 +38,6 @@ impl I18nToSymbol { .insert(symbol.to_owned()); } } - Ok(()) + Ok(i18n_usage) } }