diff --git a/README.md b/README.md index 7a1f8c7..a1d04ca 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ flowchart TD reexport all the library crates: +- database - graph - i18n - parser @@ -88,6 +89,12 @@ reexport all the library crates: - scheduler - tracker +### Database + +`Database` defines the models using in the `cli` and `api_server` crate. + +![ERD](./assets/erd.jpeg) + ### Graph `DependOnGraph` takes the `SymbolDependency` one by one to construct a DAG. You have to add the `SymbolDependency` by topological order so that `DependOnGraph` can handle the wildcard import and export for you. @@ -203,11 +210,58 @@ let paths = dt.trace("", TraceTarget::LocalVar("variable_name")).un ### Demo -See the `demo` crate. You can run `cargo run --bin demo -- -s ./test-project/everybodyyyy -d ~/tmp`. +See the `demo` crate. -### Portable +``` +Track fine-grained symbol dependency graph + +Usage: demo -s -d + +Options: + -s Path of project to trace + -d Path of the output folder + -h, --help Print help + -V, --version Print version +``` + +### CLI + +See the `cli` crate. + +``` +Parse a project and serialize its output -See the `cli` crate. You can run `cargo run --bin cli -- -i -o `. +Usage: cli + +Commands: + portable Parse and export the project in portable format + database Parse and export the project in database format + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help + -V, --version Print version +``` + +Usage: + +- `cli portable -i -t -o ` +- `cli database -i -t -o ` + +### API Server + +see the `api_server` crate. The database is the one generated by CLI with `database` command. + +``` +Start the server to provide search API + +Usage: api_server --db + +Options: + --db The path of your database + -h, --help Print help + -V, --version Print version +``` ## Client diff --git a/assets/erd.jpeg b/assets/erd.jpeg new file mode 100644 index 0000000..52b16fe Binary files /dev/null and b/assets/erd.jpeg differ diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e2b1ce9..c3d419a 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,8 +1,9 @@ use anyhow::Context; -use clap::Parser; +use clap::{Parser, Subcommand}; use dt_core::{ database::{models, Database, SqliteDb}, - i18n::collect_translation, + graph::{depend_on_graph::DependOnGraph, used_by_graph::UsedByGraph}, + i18n::{collect_translation, I18nToSymbol}, parser::{ anonymous_default_export::SYMBOL_NAME_FOR_ANONYMOUS_DEFAULT_EXPORT, collect_symbol_dependency, @@ -10,42 +11,124 @@ use dt_core::{ Input, }, path_resolver::{PathResolver, ToCanonicalString}, - route::{collect_route_dependency, Route}, + portable::Portable, + route::{collect_route_dependency, Route, SymbolToRoutes}, scheduler::ParserCandidateScheduler, }; use std::{ collections::{HashMap, HashSet}, fs::File, - io::BufReader, + io::{BufReader, Write}, path::PathBuf, }; #[derive(Parser)] #[command(version, about = "Parse a project and serialize its output", long_about = None)] struct Cli { - /// Input path - #[arg(short)] - input: String, + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Parse and export the project in portable format + Portable { + /// Input path + #[arg(short)] + input: String, + + /// translation.json path + #[arg(short)] + translation_path: String, + + /// Output path + #[arg(short)] + output: String, + }, + + /// Parse and export the project in database format + Database { + /// Input path + #[arg(short)] + input: String, - /// translation.json path - #[arg(short)] - translation_path: String, + /// translation.json path + #[arg(short)] + translation_path: String, - /// Output path - #[arg(short)] - output: String, + /// Output path + #[arg(short)] + output: String, + }, } fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); + match Cli::parse().command { + Command::Portable { + input, + translation_path, + output, + } => { + parse_and_export_project_to_portable(&input, &output, &translation_path) + .context("parse and export project to portable")?; + } + Command::Database { + input, + translation_path, + output, + } => { + parse_and_export_project_to_database(&input, &output, &translation_path) + .context("parse and export project to database")?; + } + } + Ok(()) +} + +fn parse_and_export_project_to_portable( + project_root: &str, + output_portable_path: &str, + translation_file_path: &str, +) -> anyhow::Result<()> { + let project_root = PathBuf::from(project_root).to_canonical_string()?; + let translation_json = File::open(&translation_file_path)?; + let translation_json_reader = BufReader::new(translation_json); + + 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(); + 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)?; + symbol_to_route.collect_route_dependency(&module_ast, &symbol_dependency)?; + + depend_on_graph.add_symbol_dependency(symbol_dependency)?; + scheduler.mark_candidate_as_parsed(c); + } + None => break, + } + } + + let portable = Portable::new( + project_root.to_owned(), + serde_json::from_reader(translation_json_reader)?, + i18n_to_symbol.table, + symbol_to_route.table, + UsedByGraph::from(&depend_on_graph), + ); - parse_export_project_to_database(&cli.input, &cli.output, &cli.translation_path) - .context("parse and export project to database")?; + let serialized = portable.export()?; + let mut file = File::create(&output_portable_path)?; + file.write_all(serialized.as_bytes())?; Ok(()) } -fn parse_export_project_to_database( +fn parse_and_export_project_to_database( project_root: &str, output_database_path: &str, translation_file_path: &str,