1mod code;
16pub mod error;
17mod file;
18mod global;
19mod parser;
20
21pub use global::{
22 BytesType, GenerationConfig, GenerationConfigOpts, StringType, TableOptions,
23 DEFAULT_MODEL_PATH, DEFAULT_SCHEMA_PATH,
24};
25
26use error::IOErrorToError;
27pub use error::{Error, Result};
28use file::MarkedFile;
29use heck::ToSnakeCase;
30use parser::ParsedTableMacro;
31pub use parser::FILE_SIGNATURE;
32use std::fmt::Display;
33use std::path::{Path, PathBuf};
34
35pub fn generate_code(
39 diesel_schema_file_contents: &str,
40 config: &GenerationConfig,
41) -> Result<Vec<ParsedTableMacro>> {
42 parser::parse_and_generate_code(diesel_schema_file_contents, config)
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum FileChangeStatus {
48 Unchanged,
50 Modified,
52 Deleted,
54}
55
56impl Display for FileChangeStatus {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(
59 f,
60 "{}",
61 match self {
62 FileChangeStatus::Unchanged => "Unchanged",
63 FileChangeStatus::Modified => "Modified",
64 FileChangeStatus::Deleted => "Deleted",
65 }
66 )
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct FileChange {
73 pub file: PathBuf,
75 pub status: FileChangeStatus,
77}
78
79impl FileChange {
80 pub fn new<P: AsRef<std::path::Path>>(path: P, status: FileChangeStatus) -> Self {
81 Self {
82 file: path.as_ref().to_owned(),
83 status,
84 }
85 }
86}
87
88impl From<&MarkedFile> for FileChange {
90 fn from(value: &MarkedFile) -> Self {
91 if value.is_modified() {
92 Self::new(&value.path, FileChangeStatus::Modified)
93 } else {
94 Self::new(&value.path, FileChangeStatus::Unchanged)
95 }
96 }
97}
98
99fn get_table_module_name(table_name: &str) -> String {
104 table_name.to_snake_case().to_lowercase()
105}
106
107pub fn generate_files(
111 input_diesel_schema_file: &Path,
112 output_models_dir: &Path,
113 config: GenerationConfig,
114) -> Result<Vec<FileChange>> {
115 global::validate_config(&config)?;
116
117 let generated = generate_code(
118 &std::fs::read_to_string(input_diesel_schema_file)
119 .attach_path_err(input_diesel_schema_file)?,
120 &config,
121 )?;
122
123 if !output_models_dir.exists() {
124 std::fs::create_dir(output_models_dir).attach_path_err(output_models_dir)?;
125 } else if !output_models_dir.is_dir() {
126 return Err(Error::not_a_directory(
127 "Expected output argument to be a directory or non-existent.",
128 output_models_dir,
129 ));
130 }
131
132 let mut file_changes = Vec::with_capacity(generated.len());
134
135 let mut mod_rs = MarkedFile::new(output_models_dir.join("mod.rs"))?;
137
138 if config.any_once_option() {
139 let mut common_file = MarkedFile::new(output_models_dir.join("common.rs"))?;
140 common_file.ensure_file_signature()?;
141 common_file.change_file_contents({
142 let mut tmp = format!("{FILE_SIGNATURE}\n");
143 if config.get_once_common_structs() {
144 tmp.push_str(&code::generate_common_structs(
145 config.get_default_table_options(),
146 ));
147 }
148 if config.get_once_connection_type() {
149 tmp.push('\n');
150 tmp.push_str(&code::generate_connection_type(&config));
151
152 tmp.push('\n');
155 }
156
157 tmp
158 });
159 common_file.write()?;
160 file_changes.push(FileChange::from(&common_file));
161
162 mod_rs.ensure_mod_stmt("common");
163 }
164
165 for table in generated.iter() {
167 if config.get_once_common_structs() && table.name == "common" {
168 return Err(Error::other("Cannot have a table named \"common\" while having option \"once_common_structs\" enabled"));
169 }
170 let table_name = table.name.to_string();
171 let table_filename = get_table_module_name(&table_name);
172 let table_config = config.table(&table_name);
173 let table_dir = if table_config.get_single_model_file() {
174 output_models_dir.to_owned()
175 } else {
176 output_models_dir.join(&table_filename)
177 };
178
179 if !table_dir.exists() {
180 std::fs::create_dir(&table_dir).attach_path_err(&table_dir)?;
181 }
182
183 if !table_dir.is_dir() {
184 return Err(Error::not_a_directory("Expected a directory", table_dir));
185 }
186
187 let table_file_name = if table_config.get_single_model_file() {
188 let mut table_name = table_name;
189 table_name.push_str(".rs");
190 table_name
191 } else {
192 "generated.rs".into()
193 };
194
195 let mut table_generated_rs = MarkedFile::new(table_dir.join(table_file_name))?;
196 let mut table_mod_rs = MarkedFile::new(table_dir.join("mod.rs"))?;
197
198 table_generated_rs.ensure_file_signature()?;
199 table_generated_rs.change_file_contents(table.generated_code.clone());
200 table_generated_rs.write()?;
201
202 file_changes.push(FileChange::from(&table_generated_rs));
203
204 if !table_config.get_single_model_file() {
205 table_mod_rs.ensure_mod_stmt("generated");
206 table_mod_rs.ensure_use_stmt("generated::*");
207 table_mod_rs.write()?;
208 file_changes.push(FileChange::from(&table_mod_rs));
209 }
210
211 mod_rs.ensure_mod_stmt(&table_filename);
212 }
213
214 for item in std::fs::read_dir(output_models_dir).attach_path_err(output_models_dir)? {
216 let item = item.attach_path_err(output_models_dir)?;
218
219 let file_type = item
221 .file_type()
222 .attach_path_msg(item.path(), "Could not determine type of file")?;
223 if !file_type.is_dir() {
224 continue;
225 }
226
227 let generated_rs_path = item.path().join("generated.rs");
229 if !generated_rs_path.exists()
230 || !generated_rs_path.is_file()
231 || !MarkedFile::new(generated_rs_path.clone())?.has_file_signature()
232 {
233 continue;
234 }
235
236 let file_name = item.file_name();
238 let associated_table_name = file_name.to_str().ok_or(Error::other(format!(
239 "Could not determine name of file '{:#?}'",
240 item.path()
241 )))?;
242 let found = generated.iter().find(|g| {
243 get_table_module_name(&g.name.to_string()).eq_ignore_ascii_case(associated_table_name)
244 });
245 if found.is_some() {
246 continue;
247 }
248
249 std::fs::remove_file(&generated_rs_path).attach_path_err(&generated_rs_path)?;
251 file_changes.push(FileChange::new(
252 &generated_rs_path,
253 FileChangeStatus::Deleted,
254 ));
255
256 let table_mod_rs_path = item.path().join("mod.rs");
258 if table_mod_rs_path.exists() {
259 let mut table_mod_rs = MarkedFile::new(table_mod_rs_path)?;
260
261 table_mod_rs.remove_mod_stmt("generated");
262 table_mod_rs.remove_use_stmt("generated::*");
263 table_mod_rs.write()?;
264
265 if table_mod_rs.get_file_contents().trim().is_empty() {
266 let table_mod_rs = table_mod_rs.delete()?;
267 file_changes.push(FileChange::new(table_mod_rs, FileChangeStatus::Deleted));
268 } else {
269 table_mod_rs.write()?; file_changes.push(FileChange::from(&table_mod_rs));
271 }
272 }
273
274 let is_empty = item
276 .path()
277 .read_dir()
278 .attach_path_err(item.path())?
279 .next()
280 .is_none();
281 if is_empty {
282 std::fs::remove_dir(item.path()).attach_path_err(item.path())?;
283 }
284
285 mod_rs.remove_mod_stmt(associated_table_name);
287 }
288
289 mod_rs.write()?;
290 file_changes.push(FileChange::from(&mod_rs));
291
292 Ok(file_changes)
293}