use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MigrationFile {
pub version: u64,
pub width: usize,
pub description: String,
pub filename: String,
pub path: PathBuf,
}
#[derive(Debug, Clone)]
pub struct MigrationDir {
pub role: String,
pub path: PathBuf,
pub files: Vec<MigrationFile>,
}
pub fn parse_filename(name: &str) -> Option<(u64, usize, &str)> {
let stem = name.strip_suffix(".sql")?;
let digits: String = stem.chars().take_while(|c| c.is_ascii_digit()).collect();
if digits.is_empty() {
return None;
}
let rest = &stem[digits.len()..];
let desc = rest.strip_prefix('_')?;
let version = digits.parse::<u64>().ok()?;
Some((version, digits.len(), desc))
}
pub fn scan_dir(role: &str, path: &Path) -> std::io::Result<MigrationDir> {
let mut files = Vec::new();
if path.exists() {
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let name = entry.file_name().to_string_lossy().into_owned();
if let Some((version, width, desc)) = parse_filename(&name) {
files.push(MigrationFile {
version,
width,
description: desc.to_string(),
filename: name,
path: entry.path(),
});
}
}
}
files.sort_by_key(|f| f.version);
Ok(MigrationDir {
role: role.to_string(),
path: path.to_path_buf(),
files,
})
}
pub mod apply;
pub mod config;
pub mod error;
pub mod git;
pub mod model;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_zero_padded() {
assert_eq!(
parse_filename("0286_captable.sql"),
Some((286, 4, "captable"))
);
}
#[test]
fn parses_multi_underscore_description() {
assert_eq!(
parse_filename("0012_add_forms_index.sql"),
Some((12, 4, "add_forms_index"))
);
}
#[test]
fn rejects_no_leading_digits() {
assert_eq!(parse_filename("captable_0286.sql"), None);
}
#[test]
fn rejects_missing_underscore() {
assert_eq!(parse_filename("0286captable.sql"), None);
}
#[test]
fn rejects_non_sql() {
assert_eq!(parse_filename("0286_x.txt"), None);
}
#[test]
fn scan_missing_dir_is_empty() {
let d = scan_dir("platform", Path::new("/no/such/dir")).unwrap();
assert!(d.files.is_empty());
}
#[test]
fn scan_reads_and_sorts() {
let tmp = tempfile::tempdir().unwrap();
for n in ["0002_b.sql", "0001_a.sql", "notes.md"] {
std::fs::write(tmp.path().join(n), "").unwrap();
}
let d = scan_dir("platform", tmp.path()).unwrap();
assert_eq!(d.files.len(), 2);
assert_eq!(d.files[0].version, 1);
assert_eq!(d.files[1].version, 2);
}
}