pub mod admin;
pub mod auth;
pub mod dsl;
pub mod emit;
pub mod migration;
pub mod model;
pub mod naming;
pub mod scaffold;
pub mod schema_edit;
pub mod task;
use std::path::{Path, PathBuf};
#[derive(Debug, thiserror::Error)]
pub enum GenerateError {
#[error("would overwrite existing file(s):\n{}", format_collisions(.0))]
Collisions(Vec<PathBuf>),
#[error("invalid resource name '{0}': {1}")]
InvalidName(String, String),
#[error("invalid field '{token}': {reason}")]
InvalidField {
token: String,
reason: String,
},
#[error("not inside an Autumn project (no Cargo.toml found in current directory)")]
NotInProject,
#[error("{0}")]
Io(#[from] std::io::Error),
}
fn format_collisions(paths: &[PathBuf]) -> String {
use std::fmt::Write;
let mut out = String::with_capacity(paths.len() * 60);
for (i, p) in paths.iter().enumerate() {
if i > 0 {
out.push('\n');
}
write!(out, " {}", p.display().to_string().replace('\\', "/")).unwrap();
}
out
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Flags {
pub dry_run: bool,
pub force: bool,
}
pub fn ensure_project_root(dir: &Path) -> Result<(), GenerateError> {
if dir.join("Cargo.toml").is_file() {
Ok(())
} else {
Err(GenerateError::NotInProject)
}
}
pub fn timestamp_now() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
timestamp_from_unix(secs)
}
#[must_use]
pub fn timestamp_from_unix(unix_secs: u64) -> String {
let days = unix_secs / 86_400;
let rem = unix_secs % 86_400;
let hour = rem / 3600;
let minute = (rem % 3600) / 60;
let second = rem % 60;
let (y, m, d) = ymd_from_days(days);
format!("{y:04}{m:02}{d:02}{hour:02}{minute:02}{second:02}")
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
reason = "the input range (Unix seconds within Diesel's reasonable lifetime) is far\
below i64::MAX/2, so the i64↔u64 round-trip stays within bounds."
)]
const fn ymd_from_days(days_since_epoch: u64) -> (u64, u64, u64) {
let z = days_since_epoch as i64 + 719_468;
let era = z.div_euclid(146_097);
let doe = (z - era * 146_097) as u64; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
(y as u64, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timestamp_format_is_14_digits() {
let ts = timestamp_now();
assert_eq!(ts.len(), 14);
assert!(ts.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn timestamp_2026_04_27_known_value() {
let ts = timestamp_from_unix(1_777_248_000);
assert_eq!(ts, "20260427000000");
}
#[test]
fn timestamp_1970_epoch() {
assert_eq!(timestamp_from_unix(0), "19700101000000");
}
#[test]
fn timestamp_handles_leap_year() {
assert_eq!(timestamp_from_unix(1_709_210_096), "20240229123456");
}
#[test]
fn ensure_project_root_succeeds_with_cargo_toml() {
let tmp = tempfile::TempDir::new().unwrap();
std::fs::write(tmp.path().join("Cargo.toml"), "").unwrap();
assert!(ensure_project_root(tmp.path()).is_ok());
}
#[test]
fn ensure_project_root_fails_without_cargo_toml() {
let tmp = tempfile::TempDir::new().unwrap();
assert!(matches!(
ensure_project_root(tmp.path()).unwrap_err(),
GenerateError::NotInProject
));
}
}