greentic_component/scaffold/
write.rs

1#![cfg(feature = "cli")]
2
3use std::fs;
4use std::io::{self, Write};
5use std::path::{Path, PathBuf};
6
7use tempfile::NamedTempFile;
8use thiserror::Error;
9
10#[derive(Debug, Default, Clone)]
11pub struct Writer;
12
13impl Writer {
14    pub fn new() -> Self {
15        Self
16    }
17
18    pub fn write_all(
19        &self,
20        root: &Path,
21        files: &[GeneratedFile],
22    ) -> Result<Vec<String>, WriteError> {
23        if !root.exists() {
24            fs::create_dir_all(root)
25                .map_err(|err| WriteError::CreateDir(root.to_path_buf(), err))?;
26        }
27
28        let mut created = Vec::with_capacity(files.len());
29        for file in files {
30            let target = root.join(&file.relative_path);
31            if let Some(parent) = target.parent() {
32                fs::create_dir_all(parent)
33                    .map_err(|err| WriteError::CreateDir(parent.to_path_buf(), err))?;
34            }
35
36            let mut tmp = NamedTempFile::new_in(target.parent().unwrap_or(root))
37                .map_err(|err| WriteError::WriteFile(file.relative_path.to_path_buf(), err))?;
38            tmp.write_all(&file.contents)
39                .map_err(|err| WriteError::WriteFile(file.relative_path.to_path_buf(), err))?;
40            tmp.flush()
41                .map_err(|err| WriteError::WriteFile(file.relative_path.to_path_buf(), err))?;
42            tmp.as_file()
43                .sync_all()
44                .map_err(|err| WriteError::WriteFile(file.relative_path.to_path_buf(), err))?;
45
46            tmp.persist(&target)
47                .map_err(|err| WriteError::Persist(file.relative_path.to_path_buf(), err.error))?;
48
49            if file.executable {
50                set_executable(&target).map_err(|err| {
51                    WriteError::Permissions(file.relative_path.to_path_buf(), err)
52                })?;
53            }
54
55            created.push(file.relative_path.display().to_string());
56        }
57
58        created.sort();
59        Ok(created)
60    }
61}
62
63fn set_executable(path: &Path) -> io::Result<()> {
64    #[cfg(unix)]
65    {
66        use std::os::unix::fs::PermissionsExt;
67        let metadata = fs::metadata(path)?;
68        let mut permissions = metadata.permissions();
69        let current = permissions.mode();
70        permissions.set_mode(current | 0o755);
71        fs::set_permissions(path, permissions)
72    }
73    #[cfg(not(unix))]
74    {
75        let _ = path;
76        Ok(())
77    }
78}
79
80#[derive(Debug, Clone)]
81pub struct GeneratedFile {
82    pub relative_path: PathBuf,
83    pub contents: Vec<u8>,
84    pub executable: bool,
85}
86
87#[derive(Debug, Error)]
88pub enum WriteError {
89    #[error("failed to create directory {0}: {1}")]
90    CreateDir(PathBuf, #[source] io::Error),
91    #[error("failed to write {0}: {1}")]
92    WriteFile(PathBuf, #[source] io::Error),
93    #[error("failed to persist {0}: {1}")]
94    Persist(PathBuf, #[source] io::Error),
95    #[error("failed to update permissions for {0}: {1}")]
96    Permissions(PathBuf, #[source] io::Error),
97}