mod factory;
mod model;
use proc_macro2::TokenStream;
use quote::quote;
use crate::config::{Config, Dialect};
use crate::error::{GenError, Result};
use crate::resolve::Model;
const HEADER: &str = "// @generated by keelson-gen. DO NOT EDIT.\n\
// Regenerate from the schema instead; hand-written code (hooks included)\n\
// belongs outside this directory.\n";
pub(crate) struct Dial {
pub(crate) krate: TokenStream,
pub(crate) returning: bool,
}
impl Dial {
pub(crate) fn new(dialect: Dialect) -> Result<Dial> {
Ok(match dialect {
Dialect::Psql => Dial {
krate: quote!(keelson_psql),
returning: true,
},
Dialect::Sqlite => Dial {
krate: quote!(keelson_sqlite),
returning: true,
},
Dialect::Mysql => Dial {
krate: quote!(keelson_mysql),
returning: false,
},
})
}
}
pub(crate) fn render(models: &[Model], config: &Config) -> Result<Vec<(String, String)>> {
let dial = Dial::new(config.dialect)?;
let mut files = Vec::with_capacity(models.len() + 2);
files.push(("mod.rs".to_owned(), render_mod_rs(models, config)));
for m in models {
let tokens = model::model_file(m, models, config, &dial)?;
files.push((format!("{}.rs", m.table), render_file(tokens)?));
}
if config.output.factories {
let tokens = factory::factories_file(models, config)?;
files.push(("factories.rs".to_owned(), render_file(tokens)?));
}
Ok(files)
}
fn render_mod_rs(models: &[Model], config: &Config) -> String {
let mut out = String::from(HEADER);
out.push_str("\n//! The generated models, one module per table.\n\n");
for m in models {
let module = crate::names::ident(&m.table);
out.push_str(&format!("pub mod {module};\n"));
}
if config.output.factories {
out.push_str("\n/// The generated test-data factories, one module per writable table.\npub mod factories;\n");
}
out
}
fn render_file(tokens: TokenStream) -> Result<String> {
let file: syn::File = syn::parse2(tokens)
.map_err(|e| GenError::Config(format!("internal: generated tokens do not parse: {e}")))?;
Ok(format!("{HEADER}\n{}", prettyplease::unparse(&file)))
}