Skip to main content

tycode_core/file/
manager.rs

1use crate::file::access::FileAccessManager;
2use crate::tools::r#trait::FileModification;
3use anyhow::{Context, Result};
4
5/// Statistics returned by file modification operations
6#[derive(Debug, Clone)]
7pub struct FileModificationStats {
8    pub lines_added: u32,
9    pub lines_removed: u32,
10}
11
12/// Manages file modifications with security enforcement and future review capabilities
13pub struct FileModificationManager {
14    file_access: FileAccessManager,
15}
16
17impl FileModificationManager {
18    pub fn new(file_access: FileAccessManager) -> Self {
19        Self { file_access }
20    }
21
22    pub async fn apply_modification(
23        &self,
24        modification: FileModification,
25    ) -> Result<FileModificationStats> {
26        let stats = match modification.operation {
27            crate::tools::r#trait::FileOperation::Create => {
28                let content = modification
29                    .new_content
30                    .ok_or_else(|| anyhow::anyhow!("Create operation requires new_content"))?;
31
32                self.file_access
33                    .write_file(
34                        modification
35                            .path
36                            .to_str()
37                            .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?,
38                        &content,
39                    )
40                    .await
41                    .with_context(|| {
42                        format!("Failed to create file: {}", modification.path.display())
43                    })?;
44
45                let lines_added = content.lines().count() as u32;
46                let lines_removed = 0;
47
48                tracing::info!(
49                    "Created file: {} ({} lines)",
50                    modification.path.display(),
51                    lines_added
52                );
53
54                FileModificationStats {
55                    lines_added,
56                    lines_removed,
57                }
58            }
59            crate::tools::r#trait::FileOperation::Update => {
60                let content = modification
61                    .new_content
62                    .ok_or_else(|| anyhow::anyhow!("Update operation requires new_content"))?;
63
64                let original_content = modification.original_content.as_deref().unwrap_or("");
65                let lines_added = count_lines_added(original_content, &content);
66                let lines_removed = count_lines_removed(original_content, &content);
67
68                self.file_access
69                    .write_file(
70                        modification
71                            .path
72                            .to_str()
73                            .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?,
74                        &content,
75                    )
76                    .await
77                    .with_context(|| {
78                        format!("Failed to update file: {}", modification.path.display())
79                    })?;
80
81                tracing::info!(
82                    "Updated file: {} (+{} lines, -{} lines)",
83                    modification.path.display(),
84                    lines_added,
85                    lines_removed
86                );
87
88                FileModificationStats {
89                    lines_added,
90                    lines_removed,
91                }
92            }
93            crate::tools::r#trait::FileOperation::Delete => {
94                // Read the original content before deleting to count lines
95                let original_content = self
96                    .file_access
97                    .read_file(
98                        modification
99                            .path
100                            .to_str()
101                            .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?,
102                    )
103                    .await
104                    .with_context(|| {
105                        format!(
106                            "Failed to read file before deletion: {}",
107                            modification.path.display()
108                        )
109                    })?;
110
111                let lines_removed = original_content.lines().count() as u32;
112
113                self.file_access
114                    .delete_file(
115                        modification
116                            .path
117                            .to_str()
118                            .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?,
119                    )
120                    .await
121                    .with_context(|| {
122                        format!("Failed to delete file: {}", modification.path.display())
123                    })?;
124
125                tracing::info!(
126                    "Deleted file: {} ({} lines)",
127                    modification.path.display(),
128                    lines_removed
129                );
130
131                FileModificationStats {
132                    lines_added: 0,
133                    lines_removed,
134                }
135            }
136        };
137
138        Ok(stats)
139    }
140}
141
142/// Counts the number of lines added when comparing original to new content
143fn count_lines_added(original: &str, new: &str) -> u32 {
144    let original_lines: std::collections::HashSet<&str> = original.lines().collect();
145    let new_lines: std::collections::HashSet<&str> = new.lines().collect();
146
147    // Lines that are in new but not in original are added
148    new_lines.difference(&original_lines).count() as u32
149}
150
151/// Counts the number of lines removed when comparing original to new content
152fn count_lines_removed(original: &str, new: &str) -> u32 {
153    let original_lines: std::collections::HashSet<&str> = original.lines().collect();
154    let new_lines: std::collections::HashSet<&str> = new.lines().collect();
155
156    // Lines that are in original but not in new are removed
157    original_lines.difference(&new_lines).count() as u32
158}