use attribute_derive::FromAttr; use std::{ffi::OsStr, path::Path}; use anyhow::{anyhow, Result}; use argh::FromArgs; use crate::generators::{SourceNode, SourceNodeContainer}; pub mod generators; pub mod models; pub mod parse_models; #[derive(FromAttr, PartialEq, Debug, Default)] #[attribute(ident = sql_generator_model)] pub struct SqlGeneratorModelAttr { table_name: Option, } #[derive(FromAttr, PartialEq, Debug, Default)] #[attribute(ident = sql_generator_field)] pub struct SqlGeneratorFieldAttr { is_primary: Option, is_unique: Option, reverse_relation_name: Option, /// to indicate that this field will be used to obtains entities /// our framework will generate methods for all fields that is an entrypoint is_query_entrypoint: Option, } #[derive(FromArgs, PartialEq, Debug)] /// Generate SQL CREATE TABLE migrations #[argh(subcommand, name = "gen-migrations")] struct GenerateMigration { /// path of file where to write all in one generated SQL migration #[argh(option, short = 'o')] output: Option, } #[derive(FromArgs, PartialEq, Debug)] /// Generate Rust SQLx repositories code #[argh(subcommand, name = "gen-repositories")] struct GenerateRepositories { /// path of the directory that contains repositories #[argh(option, short = 'o')] output: Option, } #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand)] enum GeneratorArgsSubCommands { GenerateMigration(GenerateMigration), GenerateRepositories(GenerateRepositories), } #[derive(FromArgs)] /// SQLX Generator args struct GeneratorArgs { /// path where to find Cargo.toml #[argh(option)] project_root: Option, /// path of the directory containing models #[argh(option, short = 'm')] models_path: Option, #[argh(subcommand)] nested: GeneratorArgsSubCommands, } fn write_source_code(base_path: &Path, snc: SourceNodeContainer) -> Result<()> { let path = base_path.join(snc.name); match snc.inner { SourceNode::File(code) => { println!("Writing file {:?}.", path); std::fs::write(path, code)?; } SourceNode::Directory(dir) => { for node in dir { write_source_code(&path, node)?; } } } Ok(()) } pub fn main() -> Result<()> { let args: GeneratorArgs = argh::from_env(); let project_root = &args.project_root.unwrap_or(".".to_string()); let project_root_path = Path::new(&project_root); eprintln!( "Using project root at: {:?}", &project_root_path.canonicalize()? ); if !project_root_path.exists() { return Err(anyhow!("Could not resolve project root path.")); } // check Cargo.toml let main_manifest_location = "Cargo.toml"; let main_manifest_path = project_root_path.join(main_manifest_location); if !main_manifest_path.exists() { return Err(anyhow!("Could not find Cargo.toml in project root.")); } // search for a models modules let models_mod_location = "src/models.rs"; let mut models_mod_path = project_root_path.join(models_mod_location); if let Some(models_path) = args.models_path { models_mod_path = project_root_path.join(models_path); } if !models_mod_path.exists() { let models_mod_location = "src/models/mod.rs"; models_mod_path = project_root_path.join(models_mod_location); } if !models_mod_path.exists() { return Err(anyhow!("Could not resolve models modules.")); } if models_mod_path .file_name() .map(|x| x == OsStr::new("mod.rs")) .unwrap_or(false) { models_mod_path.pop(); } eprintln!("Found models in project, parsing models"); let models = parse_models::parse_models_from_module(&models_mod_path)?; eprintln!( "Found and parsed a grand total of {} sqlxgentools compatible models.", models.len() ); match args.nested { GeneratorArgsSubCommands::GenerateRepositories(opts) => { eprintln!("Generating repositories…"); // search for a repository module let repositories_mod_location = opts.output.unwrap_or("src/repositories".to_string()); let repositories_mod_path = project_root_path.join(repositories_mod_location); if !repositories_mod_path.exists() { return Err(anyhow!("Could not resolve repositories modules.")); } let snc = generators::repositories::generate_repositories_source_files(&models)?; write_source_code(&repositories_mod_path, snc)?; } GeneratorArgsSubCommands::GenerateMigration(opts) => { eprintln!("Generating migrations…"); let sql_code = generators::migrations::generate_create_table_sql(&models)?; if let Some(out_location) = opts.output { let output_path = Path::new(&out_location); let _write_res = std::fs::write(output_path, sql_code); // TODO: check if write result is an error and return error message. } else { println!("{}", sql_code); } } } Ok(()) }