Skip to main content

split_modules/
apply.rs

1//! Write a [`SplitPlan`] to disk with a snapshot that supports full rollback.
2
3use std::path::PathBuf;
4
5use anyhow::{bail, Context, Result};
6
7use crate::model::{Config, SplitPlan};
8use crate::plan::{child_path, render_child};
9
10/// A split that has been written to disk and can be rolled back.
11pub struct Applied {
12    pub source_path: PathBuf,
13    parent_backup: String,
14    created_files: Vec<PathBuf>,
15    created_dir: Option<PathBuf>,
16    /// Files that make up the split, for reporting.
17    pub files: Vec<PathBuf>,
18}
19
20impl Applied {
21    /// Undo the split: restore the parent file, delete generated files, and remove the
22    /// generated directory if we created it and it is now empty.
23    pub fn rollback(&self) -> Result<()> {
24        std::fs::write(&self.source_path, &self.parent_backup)
25            .with_context(|| format!("rollback: restoring {}", self.source_path.display()))?;
26        for f in &self.created_files {
27            let _ = std::fs::remove_file(f);
28        }
29        if let Some(dir) = &self.created_dir {
30            // Only remove if empty (avoid clobbering pre-existing sibling modules).
31            let _ = std::fs::remove_dir(dir);
32        }
33        Ok(())
34    }
35}
36
37/// Write the plan to disk. Performs a conflict check first and never overwrites an
38/// existing module file.
39pub fn write_plan(plan: &SplitPlan, _config: &Config) -> Result<Applied> {
40    // Conflict check: a generated child file must not already exist.
41    for file in &plan.files {
42        let path = child_path(plan, &file.stem);
43        if path.exists() {
44            bail!(
45                "target file {} already exists; refusing to overwrite",
46                path.display()
47            );
48        }
49    }
50
51    let parent_backup = std::fs::read_to_string(&plan.source_path)
52        .with_context(|| format!("reading {}", plan.source_path.display()))?;
53
54    // Create output directory if needed (record whether we created it).
55    let created_dir = if !plan.out_dir.exists() {
56        std::fs::create_dir_all(&plan.out_dir)
57            .with_context(|| format!("creating {}", plan.out_dir.display()))?;
58        Some(plan.out_dir.clone())
59    } else {
60        None
61    };
62
63    let mut created_files = Vec::new();
64    let mut all_files = Vec::new();
65    for file in &plan.files {
66        let path = child_path(plan, &file.stem);
67        let contents = render_child(plan, file);
68        std::fs::write(&path, contents)
69            .with_context(|| format!("writing {}", path.display()))?;
70        created_files.push(path.clone());
71        all_files.push(path);
72    }
73
74    std::fs::write(&plan.source_path, &plan.parent_contents)
75        .with_context(|| format!("writing {}", plan.source_path.display()))?;
76    all_files.push(plan.source_path.clone());
77
78    Ok(Applied {
79        source_path: plan.source_path.clone(),
80        parent_backup,
81        created_files,
82        created_dir,
83        files: all_files,
84    })
85}
86
87/// Best-effort `rustfmt` on the given files; ignores all failures.
88pub fn format_files(files: &[PathBuf], edition: &str) {
89    if files.is_empty() {
90        return;
91    }
92    let mut cmd = std::process::Command::new("rustfmt");
93    cmd.arg("--edition").arg(edition);
94    for f in files {
95        cmd.arg(f);
96    }
97    let _ = cmd.output();
98}