Skip to main content

st/file_history/
operations.rs

1//! File operation codes and definitions
2//!
3//! 🎸 The Cheet says: "Every operation has its own rhythm!"
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::hash::Hash;
8
9/// File operation types with codes
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum FileOperation {
12    /// Append content to end of file (least intrusive)
13    Append,
14    /// Prepend content to beginning of file
15    Prepend,
16    /// Insert content at specific position
17    Insert,
18    /// Delete content from file
19    Delete,
20    /// Replace content in file
21    Replace,
22    /// Create new file
23    Create,
24    /// Delete entire file
25    Remove,
26    /// Relocate/move file
27    Relocate,
28    /// Rename file
29    Rename,
30    /// Change permissions
31    Chmod,
32    /// Read file (for tracking access patterns)
33    Read,
34}
35
36impl FileOperation {
37    /// Get operation code (single character for compact logging)
38    pub fn code(&self) -> char {
39        match self {
40            FileOperation::Append => 'A',
41            FileOperation::Prepend => 'P',
42            FileOperation::Insert => 'I',
43            FileOperation::Delete => 'D',
44            FileOperation::Replace => 'R',
45            FileOperation::Create => 'C',
46            FileOperation::Remove => 'X',
47            FileOperation::Relocate => 'M',
48            FileOperation::Rename => 'N',
49            FileOperation::Chmod => 'H',
50            FileOperation::Read => 'r',
51        }
52    }
53
54    /// Parse operation from code
55    pub fn from_code(code: char) -> Option<Self> {
56        match code {
57            'A' => Some(FileOperation::Append),
58            'P' => Some(FileOperation::Prepend),
59            'I' => Some(FileOperation::Insert),
60            'D' => Some(FileOperation::Delete),
61            'R' => Some(FileOperation::Replace),
62            'C' => Some(FileOperation::Create),
63            'X' => Some(FileOperation::Remove),
64            'M' => Some(FileOperation::Relocate),
65            'N' => Some(FileOperation::Rename),
66            'H' => Some(FileOperation::Chmod),
67            'r' => Some(FileOperation::Read),
68            _ => None,
69        }
70    }
71
72    /// Check if operation is non-destructive
73    pub fn is_safe(&self) -> bool {
74        matches!(self, FileOperation::Append | FileOperation::Read)
75    }
76
77    /// Get operation description
78    pub fn description(&self) -> &'static str {
79        match self {
80            FileOperation::Append => "Appended content to file",
81            FileOperation::Prepend => "Prepended content to file",
82            FileOperation::Insert => "Inserted content into file",
83            FileOperation::Delete => "Deleted content from file",
84            FileOperation::Replace => "Replaced content in file",
85            FileOperation::Create => "Created new file",
86            FileOperation::Remove => "Removed file",
87            FileOperation::Relocate => "Relocated file",
88            FileOperation::Rename => "Renamed file",
89            FileOperation::Chmod => "Changed file permissions",
90            FileOperation::Read => "Read file",
91        }
92    }
93}
94
95impl fmt::Display for FileOperation {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}", self.description())
98    }
99}
100
101/// Operation context with metadata
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct OperationContext {
104    /// Type of operation
105    pub operation: FileOperation,
106    /// Position in file (for insert operations)
107    pub position: Option<usize>,
108    /// Number of bytes affected
109    pub bytes_affected: usize,
110    /// Old content hash (before operation)
111    pub old_hash: Option<String>,
112    /// New content hash (after operation)
113    pub new_hash: Option<String>,
114    /// Additional metadata
115    pub metadata: Option<serde_json::Value>,
116}
117
118impl OperationContext {
119    /// Create new operation context
120    pub fn new(operation: FileOperation) -> Self {
121        Self {
122            operation,
123            position: None,
124            bytes_affected: 0,
125            old_hash: None,
126            new_hash: None,
127            metadata: None,
128        }
129    }
130
131    /// Set position for insert operations
132    pub fn with_position(mut self, pos: usize) -> Self {
133        self.position = Some(pos);
134        self
135    }
136
137    /// Set bytes affected
138    pub fn with_bytes(mut self, bytes: usize) -> Self {
139        self.bytes_affected = bytes;
140        self
141    }
142
143    /// Set hashes
144    pub fn with_hashes(mut self, old: Option<String>, new: Option<String>) -> Self {
145        self.old_hash = old;
146        self.new_hash = new;
147        self
148    }
149}
150
151/// Suggest best operation for a given change
152pub fn suggest_operation(
153    original: Option<&str>,
154    modified: &str,
155    prefer_append: bool,
156) -> FileOperation {
157    match original {
158        None => FileOperation::Create,
159        Some("") => FileOperation::Append,
160        Some(orig) => {
161            // Check if it's a simple append
162            if prefer_append && modified.starts_with(orig) {
163                FileOperation::Append
164            }
165            // Check if it's a prepend
166            else if modified.ends_with(orig) {
167                FileOperation::Prepend
168            }
169            // Otherwise it's a more complex change
170            else {
171                FileOperation::Replace
172            }
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_operation_codes() {
183        assert_eq!(FileOperation::Append.code(), 'A');
184        assert_eq!(FileOperation::from_code('A'), Some(FileOperation::Append));
185    }
186
187    #[test]
188    fn test_suggest_operation() {
189        // Test append preference
190        let op = suggest_operation(Some("hello"), "hello world", true);
191        assert_eq!(op, FileOperation::Append);
192
193        // Test prepend detection
194        let op = suggest_operation(Some("world"), "hello world", false);
195        assert_eq!(op, FileOperation::Prepend);
196
197        // Test create for new file
198        let op = suggest_operation(None, "new content", true);
199        assert_eq!(op, FileOperation::Create);
200    }
201}