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}