mod code;
mod file;
mod parser;
use file::MarkedFile;
use parser::ParsedTableMacro;
pub use parser::FILE_SIGNATURE;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Default, Debug, Clone)]
pub struct TableOptions<'a> {
ignore: Option<bool>,
autogenerated_columns: Option<Vec<&'a str>>,
#[cfg(feature = "tsync")]
tsync: Option<bool>,
}
impl<'a> TableOptions<'a> {
pub fn get_ignore(&self) -> bool {
self.ignore.unwrap_or_default()
}
#[cfg(feature = "tsync")]
pub fn get_tsync(&self) -> bool {
self.tsync.unwrap_or_default()
}
pub fn get_autogenerated_columns(&self) -> &[&'_ str] {
self.autogenerated_columns.as_deref().unwrap_or_default()
}
pub fn ignore(self) -> Self {
Self {
ignore: Some(true),
..self
}
}
#[cfg(feature = "tsync")]
pub fn tsync(self) -> Self {
Self {
tsync: Some(true),
..self
}
}
pub fn autogenerated_columns(self, cols: Vec<&'a str>) -> Self {
Self {
autogenerated_columns: Some(cols.clone()),
..self
}
}
pub fn apply_defaults(&self, other: &TableOptions<'a>) -> Self {
Self {
ignore: self.ignore.or(other.ignore),
#[cfg(feature = "tsync")]
tsync: self.tsync.or(other.tsync),
autogenerated_columns: self
.autogenerated_columns
.clone()
.or_else(|| other.autogenerated_columns.clone()),
}
}
}
#[derive(Debug, Clone)]
pub struct GenerationConfig<'a> {
pub table_options: HashMap<&'a str, TableOptions<'a>>,
pub default_table_options: TableOptions<'a>,
pub connection_type: String,
}
impl GenerationConfig<'_> {
pub fn table(&self, name: &str) -> TableOptions<'_> {
let t = self
.table_options
.get(name)
.unwrap_or(&self.default_table_options);
t.apply_defaults(&self.default_table_options)
}
}
pub fn generate_code(
diesel_schema_file_contents: String,
config: GenerationConfig,
) -> anyhow::Result<Vec<ParsedTableMacro>> {
parser::parse_and_generate_code(diesel_schema_file_contents, &config)
}
pub fn generate_files(
input_diesel_schema_file: PathBuf,
output_models_dir: PathBuf,
config: GenerationConfig,
) {
let input = input_diesel_schema_file;
let output_dir = output_models_dir;
let generated = generate_code(
std::fs::read_to_string(input).expect("Could not read schema file."),
config,
)
.expect("An error occurred.");
if !output_dir.exists() {
std::fs::create_dir(&output_dir)
.unwrap_or_else(|_| panic!("Could not create directory '{output_dir:#?}'"));
} else if !output_dir.is_dir() {
panic!("Expected output argument to be a directory or non-existent.")
}
let mut mod_rs = MarkedFile::new(output_dir.join("mod.rs"));
for table in generated.iter() {
let table_dir = output_dir.join(table.name.to_string());
if !table_dir.exists() {
std::fs::create_dir(&table_dir)
.unwrap_or_else(|_| panic!("Could not create directory '{table_dir:#?}'"));
}
if !table_dir.is_dir() {
panic!("Expected a directory at '{table_dir:#?}'")
}
let mut table_generated_rs = MarkedFile::new(table_dir.join("generated.rs"));
let mut table_mod_rs = MarkedFile::new(table_dir.join("mod.rs"));
table_generated_rs.ensure_file_signature();
table_generated_rs.file_contents = table.generated_code.clone();
table_generated_rs.write();
table_mod_rs.ensure_mod_stmt("generated");
table_mod_rs.ensure_use_stmt("generated::*");
table_mod_rs.write();
mod_rs.ensure_mod_stmt(table.name.to_string().as_str());
}
for item in std::fs::read_dir(&output_dir)
.unwrap_or_else(|_| panic!("Could not read directory '{output_dir:#?}'"))
{
let item = item.unwrap_or_else(|_| panic!("Could not read item in '{output_dir:#?}'"));
let file_type = item
.file_type()
.unwrap_or_else(|_| panic!("Could not determine type of file '{:#?}'", item.path()));
if !file_type.is_dir() {
continue;
}
let generated_rs_path = item.path().join("generated.rs");
if !generated_rs_path.exists()
|| !generated_rs_path.is_file()
|| !MarkedFile::new(generated_rs_path.clone()).has_file_signature()
{
continue;
}
let file_name = item.file_name();
let associated_table_name = file_name
.to_str()
.unwrap_or_else(|| panic!("Could not determine name of file '{:#?}'", item.path()));
let found = generated.iter().find(|g| {
g.name
.to_string()
.eq_ignore_ascii_case(associated_table_name)
});
if found.is_some() {
continue;
}
std::fs::remove_file(&generated_rs_path)
.unwrap_or_else(|_| panic!("Could not delete redundant file '{generated_rs_path:#?}'"));
let table_mod_rs_path = item.path().join("mod.rs");
if table_mod_rs_path.exists() {
let mut table_mod_rs = MarkedFile::new(table_mod_rs_path);
table_mod_rs.remove_mod_stmt("generated");
table_mod_rs.remove_use_stmt("generated::*");
table_mod_rs.write();
if table_mod_rs.file_contents.trim().is_empty() {
table_mod_rs.delete()
} else {
table_mod_rs.write() }
}
let is_empty = item
.path()
.read_dir()
.unwrap_or_else(|_| panic!("Could not read directory {:#?}", item.path()))
.next()
.is_none();
if is_empty {
std::fs::remove_dir(item.path())
.unwrap_or_else(|_| panic!("Could not delete directory '{:#?}'", item.path()));
}
mod_rs.remove_mod_stmt(associated_table_name);
}
mod_rs.write();
}