Skip to main content

gen_circleci_orb/output_writer/
mod.rs

1use anyhow::Result;
2use std::collections::HashMap;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6/// Summary of what the writer did.
7#[derive(Debug, Default, PartialEq)]
8pub struct WriteReport {
9    pub created: usize,
10    pub updated: usize,
11    pub unchanged: usize,
12}
13
14/// Write `files` (relative path → content) under `root`.
15///
16/// Diff-aware: files whose content is identical to what's on disk are skipped.
17/// When `dry_run` is true nothing is written; instead a summary is printed to stderr.
18pub fn write_tree(
19    root: &Path,
20    files: &HashMap<PathBuf, String>,
21    dry_run: bool,
22) -> Result<WriteReport> {
23    let mut report = WriteReport::default();
24
25    for (rel_path, content) in files {
26        let abs_path = root.join(rel_path);
27
28        let existing = if abs_path.exists() {
29            Some(fs::read_to_string(&abs_path)?)
30        } else {
31            None
32        };
33
34        match &existing {
35            Some(current) if current == content => {
36                report.unchanged += 1;
37            }
38            Some(_) => {
39                if dry_run {
40                    eprintln!("[dry-run] would update: {}", rel_path.display());
41                } else {
42                    if let Some(parent) = abs_path.parent() {
43                        fs::create_dir_all(parent)?;
44                    }
45                    fs::write(&abs_path, content)?;
46                }
47                report.updated += 1;
48            }
49            None => {
50                if dry_run {
51                    eprintln!("[dry-run] would create: {}", rel_path.display());
52                } else {
53                    if let Some(parent) = abs_path.parent() {
54                        fs::create_dir_all(parent)?;
55                    }
56                    fs::write(&abs_path, content)?;
57                }
58                report.created += 1;
59            }
60        }
61    }
62
63    Ok(report)
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use tempfile::TempDir;
70
71    fn single_file(path: &str, content: &str) -> HashMap<PathBuf, String> {
72        let mut m = HashMap::new();
73        m.insert(PathBuf::from(path), content.to_string());
74        m
75    }
76
77    #[test]
78    fn new_file_is_created() {
79        let dir = TempDir::new().unwrap();
80        let files = single_file("src/foo.yml", "hello");
81        let report = write_tree(dir.path(), &files, false).unwrap();
82        assert_eq!(report.created, 1);
83        assert_eq!(report.updated, 0);
84        assert_eq!(report.unchanged, 0);
85        assert_eq!(
86            fs::read_to_string(dir.path().join("src/foo.yml")).unwrap(),
87            "hello"
88        );
89    }
90
91    #[test]
92    fn identical_file_is_skipped() {
93        let dir = TempDir::new().unwrap();
94        fs::create_dir_all(dir.path().join("src")).unwrap();
95        fs::write(dir.path().join("src/foo.yml"), "hello").unwrap();
96
97        let files = single_file("src/foo.yml", "hello");
98        let report = write_tree(dir.path(), &files, false).unwrap();
99        assert_eq!(report.unchanged, 1);
100        assert_eq!(report.created, 0);
101        assert_eq!(report.updated, 0);
102    }
103
104    #[test]
105    fn changed_file_is_updated() {
106        let dir = TempDir::new().unwrap();
107        fs::create_dir_all(dir.path().join("src")).unwrap();
108        fs::write(dir.path().join("src/foo.yml"), "old content").unwrap();
109
110        let files = single_file("src/foo.yml", "new content");
111        let report = write_tree(dir.path(), &files, false).unwrap();
112        assert_eq!(report.updated, 1);
113        assert_eq!(
114            fs::read_to_string(dir.path().join("src/foo.yml")).unwrap(),
115            "new content"
116        );
117    }
118
119    #[test]
120    fn dry_run_writes_nothing() {
121        let dir = TempDir::new().unwrap();
122        let files = single_file("src/foo.yml", "hello");
123        let report = write_tree(dir.path(), &files, true).unwrap();
124        assert_eq!(report.created, 1, "dry_run should still count created");
125        assert!(
126            !dir.path().join("src/foo.yml").exists(),
127            "dry_run must not write files"
128        );
129    }
130}