mod code;
pub mod error;
mod file;
mod global;
mod parser;
pub use global::{
BytesType, GenerationConfig, GenerationConfigOpts, StringType, TableOptions,
DEFAULT_MODEL_PATH, DEFAULT_SCHEMA_PATH,
};
use error::IOErrorToError;
pub use error::{Error, Result};
use file::MarkedFile;
use heck::ToSnakeCase;
use parser::ParsedTableMacro;
pub use parser::FILE_SIGNATURE;
use std::fmt::Display;
use std::path::{Path, PathBuf};
pub fn generate_code(
diesel_schema_file_contents: &str,
config: &GenerationConfig,
) -> Result<Vec<ParsedTableMacro>> {
parser::parse_and_generate_code(diesel_schema_file_contents, config)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileChangeStatus {
Unchanged,
Modified,
Deleted,
}
impl Display for FileChangeStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
FileChangeStatus::Unchanged => "Unchanged",
FileChangeStatus::Modified => "Modified",
FileChangeStatus::Deleted => "Deleted",
}
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileChange {
pub file: PathBuf,
pub status: FileChangeStatus,
}
impl FileChange {
pub fn new<P: AsRef<std::path::Path>>(path: P, status: FileChangeStatus) -> Self {
Self {
file: path.as_ref().to_owned(),
status,
}
}
}
impl From<&MarkedFile> for FileChange {
fn from(value: &MarkedFile) -> Self {
if value.is_modified() {
Self::new(&value.path, FileChangeStatus::Modified)
} else {
Self::new(&value.path, FileChangeStatus::Unchanged)
}
}
}
fn get_table_module_name(table_name: &str) -> String {
table_name.to_snake_case().to_lowercase()
}
pub fn generate_files(
input_diesel_schema_file: &Path,
output_models_dir: &Path,
config: GenerationConfig,
) -> Result<Vec<FileChange>> {
global::validate_config(&config)?;
let generated = generate_code(
&std::fs::read_to_string(input_diesel_schema_file)
.attach_path_err(input_diesel_schema_file)?,
&config,
)?;
if !output_models_dir.exists() {
std::fs::create_dir(output_models_dir).attach_path_err(output_models_dir)?;
} else if !output_models_dir.is_dir() {
return Err(Error::not_a_directory(
"Expected output argument to be a directory or non-existent.",
output_models_dir,
));
}
let mut file_changes = Vec::with_capacity(generated.len());
let mut mod_rs = MarkedFile::new(output_models_dir.join("mod.rs"))?;
if config.any_once_option() {
let mut common_file = MarkedFile::new(output_models_dir.join("common.rs"))?;
common_file.ensure_file_signature()?;
common_file.change_file_contents({
let mut tmp = format!("{FILE_SIGNATURE}\n");
if config.get_once_common_structs() {
tmp.push_str(&code::generate_common_structs(
config.get_default_table_options(),
));
}
if config.get_once_connection_type() {
tmp.push('\n');
tmp.push_str(&code::generate_connection_type(&config));
tmp.push('\n');
}
tmp
});
common_file.write()?;
file_changes.push(FileChange::from(&common_file));
mod_rs.ensure_mod_stmt("common");
}
for table in generated.iter() {
if config.get_once_common_structs() && table.name == "common" {
return Err(Error::other("Cannot have a table named \"common\" while having option \"once_common_structs\" enabled"));
}
let table_name = table.name.to_string();
let table_filename = get_table_module_name(&table_name);
let table_config = config.table(&table_name);
let table_dir = if table_config.get_single_model_file() {
output_models_dir.to_owned()
} else {
output_models_dir.join(&table_filename)
};
if !table_dir.exists() {
std::fs::create_dir(&table_dir).attach_path_err(&table_dir)?;
}
if !table_dir.is_dir() {
return Err(Error::not_a_directory("Expected a directory", table_dir));
}
let table_file_name = if table_config.get_single_model_file() {
let mut table_name = table_name;
table_name.push_str(".rs");
table_name
} else {
"generated.rs".into()
};
let mut table_generated_rs = MarkedFile::new(table_dir.join(table_file_name))?;
let mut table_mod_rs = MarkedFile::new(table_dir.join("mod.rs"))?;
table_generated_rs.ensure_file_signature()?;
table_generated_rs.change_file_contents(table.generated_code.clone());
table_generated_rs.write()?;
file_changes.push(FileChange::from(&table_generated_rs));
if !table_config.get_single_model_file() {
table_mod_rs.ensure_mod_stmt("generated");
table_mod_rs.ensure_use_stmt("generated::*");
table_mod_rs.write()?;
file_changes.push(FileChange::from(&table_mod_rs));
}
mod_rs.ensure_mod_stmt(&table_filename);
}
for item in std::fs::read_dir(output_models_dir).attach_path_err(output_models_dir)? {
let item = item.attach_path_err(output_models_dir)?;
let file_type = item
.file_type()
.attach_path_msg(item.path(), "Could not determine type of file")?;
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().ok_or(Error::other(format!(
"Could not determine name of file '{:#?}'",
item.path()
)))?;
let found = generated.iter().find(|g| {
get_table_module_name(&g.name.to_string()).eq_ignore_ascii_case(associated_table_name)
});
if found.is_some() {
continue;
}
std::fs::remove_file(&generated_rs_path).attach_path_err(&generated_rs_path)?;
file_changes.push(FileChange::new(
&generated_rs_path,
FileChangeStatus::Deleted,
));
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.get_file_contents().trim().is_empty() {
let table_mod_rs = table_mod_rs.delete()?;
file_changes.push(FileChange::new(table_mod_rs, FileChangeStatus::Deleted));
} else {
table_mod_rs.write()?; file_changes.push(FileChange::from(&table_mod_rs));
}
}
let is_empty = item
.path()
.read_dir()
.attach_path_err(item.path())?
.next()
.is_none();
if is_empty {
std::fs::remove_dir(item.path()).attach_path_err(item.path())?;
}
mod_rs.remove_mod_stmt(associated_table_name);
}
mod_rs.write()?;
file_changes.push(FileChange::from(&mod_rs));
Ok(file_changes)
}