1use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::hash::Hash;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum FileOperation {
12 Append,
14 Prepend,
16 Insert,
18 Delete,
20 Replace,
22 Create,
24 Remove,
26 Relocate,
28 Rename,
30 Chmod,
32 Read,
34}
35
36impl FileOperation {
37 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 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 pub fn is_safe(&self) -> bool {
74 matches!(self, FileOperation::Append | FileOperation::Read)
75 }
76
77 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#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct OperationContext {
104 pub operation: FileOperation,
106 pub position: Option<usize>,
108 pub bytes_affected: usize,
110 pub old_hash: Option<String>,
112 pub new_hash: Option<String>,
114 pub metadata: Option<serde_json::Value>,
116}
117
118impl OperationContext {
119 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 pub fn with_position(mut self, pos: usize) -> Self {
133 self.position = Some(pos);
134 self
135 }
136
137 pub fn with_bytes(mut self, bytes: usize) -> Self {
139 self.bytes_affected = bytes;
140 self
141 }
142
143 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
151pub 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 if prefer_append && modified.starts_with(orig) {
163 FileOperation::Append
164 }
165 else if modified.ends_with(orig) {
167 FileOperation::Prepend
168 }
169 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 let op = suggest_operation(Some("hello"), "hello world", true);
191 assert_eq!(op, FileOperation::Append);
192
193 let op = suggest_operation(Some("world"), "hello world", false);
195 assert_eq!(op, FileOperation::Prepend);
196
197 let op = suggest_operation(None, "new content", true);
199 assert_eq!(op, FileOperation::Create);
200 }
201}