rustorm_migrate/
parser.rs1use std::fs;
2use std::path::{Path, PathBuf};
3
4use crate::runner::MigrationFile;
5
6pub fn parse_migration_dir(dir: &Path) -> Result<Vec<MigrationFile>, String> {
8 if !dir.exists() {
9 return Ok(vec![]);
10 }
11
12 let mut files: Vec<MigrationFile> = vec![];
13
14 let entries = fs::read_dir(dir)
15 .map_err(|e| format!("Не удалось прочитать директорию {:?}: {}", dir, e))?;
16
17 for entry in entries.flatten() {
18 let path = entry.path();
19 if path.extension().and_then(|e| e.to_str()) != Some("sql") {
20 continue;
21 }
22
23 let filename = path
24 .file_stem()
25 .and_then(|s| s.to_str())
26 .unwrap_or("")
27 .to_string();
28
29 let (version, name) = parse_filename(&filename)?;
31
32 let content = fs::read_to_string(&path)
33 .map_err(|e| format!("Не удалось прочитать {:?}: {}", path, e))?;
34
35 let (up_sql, down_sql) = split_up_down(&content);
36
37 files.push(MigrationFile {
38 version,
39 name,
40 up_sql,
41 down_sql,
42 path,
43 });
44 }
45
46 files.sort_by(|a, b| a.version.cmp(&b.version));
48 Ok(files)
49}
50
51fn parse_filename(filename: &str) -> Result<(String, String), String> {
53 let parts: Vec<&str> = filename.splitn(2, '_').collect();
54 if parts.len() < 2 {
55 return Ok((filename.to_string(), filename.to_string()));
57 }
58 Ok((parts[0].to_string(), parts[1..].join("_")))
59}
60
61pub fn split_up_down(content: &str) -> (String, String) {
64 let up_marker = "-- === UP ===";
65 let down_marker = "-- === DOWN ===";
66
67 if let Some(up_pos) = content.find(up_marker) {
68 let after_up = &content[up_pos + up_marker.len()..];
69
70 if let Some(down_pos) = after_up.find(down_marker) {
71 let up_sql = after_up[..down_pos].trim().to_string();
72 let down_sql = after_up[down_pos + down_marker.len()..].trim().to_string();
73 return (up_sql, down_sql);
74 }
75 return (after_up.trim().to_string(), String::new());
77 }
78
79 (content.trim().to_string(), String::new())
81}
82
83pub fn next_migration_filename(existing: &[MigrationFile], description: &str) -> String {
85 let next_num = existing.len() + 1;
86 let desc_slug = description
87 .to_lowercase()
88 .replace(' ', "_")
89 .chars()
90 .filter(|c| c.is_alphanumeric() || *c == '_')
91 .collect::<String>();
92 format!("{:04}_{}", next_num, desc_slug)
93}