version_manager/
files.rs

1use crate::{VersionError, VersionResult};
2use regex::Regex;
3use semver::Version;
4use serde::{Deserialize, Serialize};
5use std::{
6    collections::BTreeMap,
7    env,
8    fs::{File, OpenOptions, remove_file, rename},
9    io::{BufRead, BufReader, BufWriter, Lines, Read, Write},
10    path::PathBuf,
11};
12
13#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, PartialOrd)]
14pub struct VersionFile {
15    pub version: Version,
16    pub files: Vec<TrackedFiles>,
17    pub package: BTreeMap<String, Package>,
18}
19
20#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, PartialOrd)]
21pub struct Package {
22    pub version: Version,
23    pub files: Vec<TrackedFiles>,
24}
25
26impl Default for VersionFile {
27    fn default() -> Self {
28        VersionFile {
29            version: Version::new(0, 1, 0),
30            files: vec![],
31            package: BTreeMap::new(),
32        }
33    }
34}
35
36impl Default for Package {
37    fn default() -> Self {
38        Package {
39            version: Version::new(0, 1, 0),
40            files: vec![],
41        }
42    }
43}
44
45pub trait ModifyTrackedFiles {
46    fn sync_files(&self) -> VersionResult<()> {
47        self.update_tracked_files()
48    }
49    fn update_tracked_files(&self) -> VersionResult<()>;
50    fn add_tracked_file(&mut self, file: TrackedFiles) -> VersionResult<()>;
51    fn remove_tracked_file(&mut self, file: PathBuf) -> VersionResult<()>;
52    fn update_file(&self, file: PathBuf) -> VersionResult<()>;
53    fn list_tracked_files(&self) -> VersionResult<Vec<TrackedFiles>>;
54}
55
56impl ModifyTrackedFiles for VersionFile {
57    fn update_tracked_files(&self) -> VersionResult<()> {
58        for file in self.files.iter() {
59            file.update(self.version.to_string())?;
60        }
61        Ok(())
62    }
63
64    fn add_tracked_file(&mut self, file: TrackedFiles) -> VersionResult<()> {
65        self.files.push(file);
66        Ok(())
67    }
68
69    fn remove_tracked_file(&mut self, file: PathBuf) -> VersionResult<()> {
70        self.files.retain(|f| f != file);
71        Ok(())
72    }
73
74    fn update_file(&self, file: PathBuf) -> VersionResult<()> {
75        for f in self.files.iter() {
76            if f == file {
77                f.update(self.version.to_string())?;
78            }
79        }
80        Ok(())
81    }
82
83    fn list_tracked_files(&self) -> VersionResult<Vec<TrackedFiles>> {
84        return Ok(self.files.clone());
85    }
86}
87
88impl ModifyTrackedFiles for Package {
89    fn update_tracked_files(&self) -> VersionResult<()> {
90        for file in self.files.iter() {
91            file.update(self.version.to_string())?;
92        }
93        Ok(())
94    }
95
96    fn add_tracked_file(&mut self, file: TrackedFiles) -> VersionResult<()> {
97        self.files.push(file);
98        Ok(())
99    }
100
101    fn remove_tracked_file(&mut self, file: PathBuf) -> VersionResult<()> {
102        self.files.retain(|f| f != file);
103        Ok(())
104    }
105
106    fn update_file(&self, file: PathBuf) -> VersionResult<()> {
107        for f in self.files.iter() {
108            if f == file {
109                f.update(self.version.to_string())?;
110            }
111        }
112        Ok(())
113    }
114
115    fn list_tracked_files(&self) -> VersionResult<Vec<TrackedFiles>> {
116        return Ok(self.files.clone());
117    }
118}
119
120impl VersionFile {
121    pub fn get_package(&self, name: &str) -> VersionResult<&Package> {
122        if let Some(ref pkg) = self.package.get(name) {
123            return Ok(pkg);
124        }
125        Err(VersionError::InvalidCommand)
126    }
127
128    pub fn get_package_mut(&mut self, name: &str) -> VersionResult<&mut Package> {
129        if let Some(pkg) = self.package.get_mut(name) {
130            return Ok(pkg);
131        }
132        Err(VersionError::InvalidCommand)
133    }
134
135    pub fn load(version_file: PathBuf) -> VersionResult<Self> {
136        let ver: Self = match File::open(version_file.clone()) {
137            Ok(mut file) => {
138                let mut contents = String::new();
139                match file.read_to_string(&mut contents) {
140                    Ok(_) => {}
141                    Err(e) => {
142                        return Err(VersionError::IoError(e));
143                    }
144                }
145                match toml::from_str(&contents) {
146                    Ok(ver) => ver,
147                    Err(e) => return Err(VersionError::TomlDeError(e)),
148                }
149            }
150            Err(_) => match File::create(version_file) {
151                Ok(_) => Self::default(),
152                Err(e) => return Err(VersionError::IoError(e)),
153            },
154        };
155
156        Ok(ver)
157    }
158
159    pub fn save(&mut self, version_file: PathBuf) -> VersionResult<()> {
160        self.save_version(version_file)?;
161        self.save_files()?;
162        Ok(())
163    }
164
165    fn save_version(&self, version_file: PathBuf) -> VersionResult<()> {
166        let version = match toml::to_string_pretty(&self) {
167            Ok(v) => v,
168            Err(e) => {
169                return Err(VersionError::TomlSerError(e));
170            }
171        };
172        let mut file = match File::options()
173            .write(true)
174            .truncate(true)
175            .open(version_file)
176        {
177            Ok(file) => file,
178            Err(e) => {
179                return Err(VersionError::IoError(e));
180            }
181        };
182        match file.write_all(version.as_bytes()) {
183            Ok(_) => match file.flush() {
184                Ok(_) => Ok(()),
185                Err(e) => Err(VersionError::IoError(e)),
186            },
187            Err(e) => Err(VersionError::IoError(e)),
188        }
189    }
190
191    fn save_files(&self) -> VersionResult<()> {
192        self.sync_files()?;
193        for (_, pkg) in self.package.iter() {
194            pkg.sync_files()?;
195        }
196        Ok(())
197    }
198}
199
200#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, PartialOrd)]
201pub struct TrackedFiles {
202    pub file: String,
203    pub expr: String,
204}
205
206impl TrackedFiles {
207    pub fn new(file: String, expr: String) -> Self {
208        TrackedFiles { file, expr }
209    }
210
211    pub fn new_from_path(file: PathBuf, expr: String) -> Self {
212        TrackedFiles {
213            file: file.to_string_lossy().to_string(),
214            expr,
215        }
216    }
217
218    pub fn new_from_re(file: String, expr: Regex) -> Self {
219        TrackedFiles {
220            file,
221            expr: expr.as_str().to_string(),
222        }
223    }
224
225    pub fn new_from_path_and_regex(file: PathBuf, expr: Regex) -> Self {
226        TrackedFiles {
227            file: file.to_string_lossy().to_string(),
228            expr: expr.as_str().to_string(),
229        }
230    }
231
232    pub fn open(&self) -> VersionResult<File> {
233        let cd = match env::current_dir() {
234            Ok(cd) => cd,
235            Err(e) => return Err(VersionError::IoError(e)),
236        };
237        let path = cd.join(&self.file);
238        match OpenOptions::new().read(true).open(&path) {
239            Ok(file) => Ok(file),
240            Err(e) => Err(VersionError::IoError(e)),
241        }
242    }
243
244    pub fn open_tmp(&self) -> VersionResult<File> {
245        let cd = match env::current_dir() {
246            Ok(cd) => cd,
247            Err(e) => return Err(VersionError::IoError(e)),
248        };
249        let mut path = cd.join(&self.file);
250        path = path.with_extension("tmp");
251        match OpenOptions::new().create(true).write(true).open(&path) {
252            Ok(file) => Ok(file),
253            Err(e) => Err(VersionError::IoError(e)),
254        }
255    }
256
257    pub fn close(&self) -> VersionResult<()> {
258        let cd = match env::current_dir() {
259            Ok(cd) => cd,
260            Err(e) => return Err(VersionError::IoError(e)),
261        };
262        let old_path = cd.join(&self.file);
263        let new_path = old_path.with_extension("tmp");
264        match remove_file(&old_path) {
265            Ok(_) => (),
266            Err(e) => return Err(VersionError::IoError(e)),
267        };
268        match rename(new_path, old_path) {
269            Ok(_) => Ok(()),
270            Err(e) => Err(VersionError::IoError(e)),
271        }
272    }
273
274    pub fn read_lines(&self) -> VersionResult<Lines<BufReader<File>>> {
275        let file = self.open()?;
276        let reader = BufReader::new(file);
277        Ok(reader.lines())
278    }
279
280    pub fn writer(&self) -> VersionResult<BufWriter<File>> {
281        let file = self.open_tmp()?;
282        Ok(BufWriter::new(file))
283    }
284
285    pub fn update(&self, version: String) -> VersionResult<()> {
286        let mut writer = self.writer()?;
287        let regex = match Regex::new(&self.expr) {
288            Ok(re) => re,
289            Err(e) => return Err(VersionError::RegexError(e)),
290        };
291        let mut updated = false;
292        for line in match self.read_lines() {
293            Ok(lines) => lines,
294            Err(e) => return Err(e),
295        } {
296            let line = match line {
297                Ok(line) => format!("{}\n", line),
298                Err(e) => return Err(VersionError::IoError(e)),
299            };
300            if !updated {
301                if let Some(matches) = regex.captures(&line) {
302                    let new = line.replace(&matches[1], version.as_str());
303                    match writer.write(new.as_bytes()) {
304                        Ok(_) => (),
305                        Err(e) => return Err(VersionError::IoError(e)),
306                    };
307                    updated = true;
308                } else {
309                    match writer.write(line.as_bytes()) {
310                        Ok(_) => (),
311                        Err(e) => return Err(VersionError::IoError(e)),
312                    };
313                }
314            } else {
315                match writer.write(line.as_bytes()) {
316                    Ok(_) => (),
317                    Err(e) => return Err(VersionError::IoError(e)),
318                };
319            }
320        }
321        match writer.flush() {
322            Ok(_) => (),
323            Err(e) => return Err(VersionError::IoError(e)),
324        };
325        drop(writer);
326        self.close()
327    }
328}
329
330impl PartialEq<String> for TrackedFiles {
331    fn eq(&self, other: &String) -> bool {
332        self.file == *other
333    }
334}
335
336impl PartialEq<PathBuf> for TrackedFiles {
337    fn eq(&self, other: &PathBuf) -> bool {
338        let path = PathBuf::from(&self.file);
339        path == *other
340    }
341}
342
343impl PartialEq<String> for &TrackedFiles {
344    fn eq(&self, other: &String) -> bool {
345        self.file == *other
346    }
347}
348
349impl PartialEq<PathBuf> for &TrackedFiles {
350    fn eq(&self, other: &PathBuf) -> bool {
351        let path = PathBuf::from(&self.file);
352        path == *other
353    }
354}
355
356impl PartialEq<String> for &mut TrackedFiles {
357    fn eq(&self, other: &String) -> bool {
358        self.file == *other
359    }
360}
361
362impl PartialEq<PathBuf> for &mut TrackedFiles {
363    fn eq(&self, other: &PathBuf) -> bool {
364        let path = PathBuf::from(&self.file);
365        path == *other
366    }
367}