rif/
rif.rs

1pub mod rel;
2pub mod config;
3pub mod history;
4pub mod hook;
5pub mod meta;
6
7use crate::checker::Checker;
8use crate::models::{LoopBranch, ListType};
9use crate::utils;
10use std::collections::HashSet;
11use config::Config;
12use rel::Relations;
13use history::History;
14use meta::Meta;
15use crate::RifError;
16use std::path::{Path, PathBuf};
17use crate::consts::*;
18
19/// Rif struct stores all iformation necessary for rif operations
20pub struct Rif {
21    config: Config,
22    history: History,
23    relation: Relations,
24    meta: Meta,
25    black_list: HashSet<PathBuf>,
26    root_path: Option<PathBuf>,
27}
28
29impl Rif {
30    /// Create new rif struct
31    pub fn new(path: Option<impl AsRef<Path>>) -> Result<Self, RifError> {
32        let config = Config::read_from_file(path.as_ref())?;
33        let black_list = utils::get_black_list(config.git_ignore)?;
34        Ok(Self {
35            config,
36            history: History::read_from_file(path.as_ref())?,
37            relation: Relations::read_from_file(path.as_ref())?,
38            meta: Meta::read_from_file(path.as_ref())?,
39            black_list,
40            root_path: path.map(|p| p.as_ref().clone().to_owned()),
41        })
42    }
43
44    // ==========
45    // External methods start
46
47    /// Initiate given directory
48    ///
49    /// If directory is not supplied, initiates current working directory
50    pub fn init(path: Option<impl AsRef<Path>>,create_rif_ignore: bool) -> Result<(), RifError> {
51        let path = if let Some(path) = path {
52            path.as_ref().to_owned()
53        } else { std::env::current_dir()? };
54
55        // Already initiated
56        if path.join(RIF_DIECTORY).exists() {
57            return Err(RifError::RifIoError(String::from("Directory is already initiated")));
58        }
59
60        // Crate root directory
61        std::fs::create_dir(path.join(RIF_DIECTORY))?;
62
63        // Rif relation
64        let new_relations = Relations::new();
65        new_relations.save_to_file(Some(&path))?;
66        // Rif history
67        let new_rif_history = History::new();
68        new_rif_history.save_to_file(Some(&path))?;
69        // Rif Config
70        let new_config = Config::new();
71        new_config.save_to_file(Some(&path))?;
72        // Rif meta
73        let new_meta = Meta::new();
74        new_meta.save_to_file(Some(&path))?;
75        println!("Initiated a rif directory \"{}\"", std::env::current_dir()?.display());
76
77        // Also create rifignore file
78        if create_rif_ignore {
79            std::fs::write(".rifignore",".git")?;
80        }
81        Ok(())
82    }
83
84    /// Add new file 
85    ///
86    /// Files that modified, newly created, deleted files can be added but non modiifed files can
87    /// alos be added with force option
88    pub fn add(&mut self, files: &Vec<impl AsRef<Path>>, force: bool) -> Result<(), RifError> {
89        for file in files {
90            let mut path = file.as_ref().to_owned();
91
92            // If file doesn't exist, simply ignore
93            if !path.exists() {
94                continue;
95            }
96
97            // If given value is "." which means that input value has not been expanded.
98            // substitute with current working directory
99            if path.to_str().unwrap() == "." {
100                path = std::env::current_dir()?;
101                self.add_directory(&path)?;
102                continue;
103            } else if path.is_dir() {
104                self.add_directory(&path)?;
105                continue;
106            }
107
108            // Don't do anything if file is in blacklist
109            if self.is_in_black_list(&path) {
110                continue;
111            }
112
113            // First, file is already inside
114            // Second, file is new
115            if self.relation.files.contains_key(&path) {
116                self.add_old_file(&path, force)?;
117            } else {
118                self.add_new_file(&path)?;
119            }
120        } // for loop end
121
122        // Update relation file
123        self.relation.save_to_file(self.root_path.as_ref())?;
124        self.meta.save_to_file(self.root_path.as_ref())?;
125        Ok(())
126    }
127
128    /// Revert added files
129    ///
130    /// No files arugment revert all added files
131    pub fn revert(&mut self, files: Option<&Vec<impl AsRef<Path>>>) -> Result<(), RifError> {
132        if let Some(files) = files {
133            for file in files {
134                let path = file.as_ref();
135                // Removes single item
136                self.meta.remove_add_queue(&path);
137            } // for loop end
138        } else {
139            // No argument, revert everything
140            self.meta.clear();
141        }
142
143        self.meta.save_to_file(self.root_path.as_ref())?;
144        Ok(())
145    }
146
147    /// Commit addition to rif struct and check impact
148    ///
149    /// Message is saved inside history file
150    pub fn commit(&mut self, message: Option<&str>) -> Result<(), RifError> {
151
152        // Literaly, commit needs to resolve all deleted files
153        if self.relation.get_deleted_files().len() != self.meta.to_be_deleted.len() {
154            return Err(RifError::CommitFail("Commit without deleted files are illegal. Rejected".to_owned()))
155        }
156
157        // delete
158        for file in self.meta.to_be_deleted.clone().iter() {
159            self.remove_file(file)?;
160        }
161
162        // Register new files
163        for file in self.meta.to_be_registerd.clone().into_iter() {
164            self.register_new_file(&file, message)?;
165        }
166
167        // force updates
168        for file in self.meta.to_be_forced.iter() {
169            self.relation.update_filestamp_force(&file)?;
170        }
171
172        // updates
173        for file in self.meta.to_be_added.iter() {
174            self.relation.update_filestamp(&file)?;
175
176            // Add message to history
177            if let Some(msg) = message {
178                self.history.add_history(&file, msg)?;
179                self.history.save_to_file(self.root_path.as_ref())?;
180            }
181        }
182
183        // Check if added files are not empty
184        if self.meta.to_be_added_later().count() != 0 {
185            self.check_exec()?;
186        }
187
188        // Clear meta
189        self.meta.clear();
190
191        // Save files
192        self.meta.save_to_file(self.root_path.as_ref())?;
193        self.relation.save_to_file(self.root_path.as_ref())?;
194        self.history.save_to_file(self.root_path.as_ref())?;
195
196        Ok(())
197    }
198    
199    /// Discard file change and updated filestamp
200    ///
201    /// This cannot be reverted so multiple files are not supported
202    pub fn discard(&mut self, file: impl AsRef<Path>) -> Result<(), RifError> {
203        self.relation.discard_change(file.as_ref())?;
204        self.relation.save_to_file(self.root_path.as_ref())?;
205        Ok(())
206    }
207
208    // TODO
209    // Needs serious consideration
210    // Whether new_name exists in real directory
211    // Whether sany check is activated so that insane rename should be not executed
212    /// Rename rif file 
213    ///
214    /// If file exist, change the file name in filesystem
215    pub fn rename(&mut self, source_name: &str, new_name: &str) -> Result<(), RifError> {
216        let source_name = Path::new(source_name);
217        let new_name = Path::new(new_name);
218        if let Some(_) = self.relation.files.get(new_name) {
219            return Err(RifError::RenameFail(format!("Rename target: \"{}\" already exists", new_name.display())));
220        }
221
222        // Rename file if it exsits and inside relation files
223        if source_name.exists() && self.relation.files.contains_key(source_name) {
224            if !new_name.exists() {
225                std::fs::rename(source_name, new_name)?;
226            } else {
227                return Err(RifError::RenameFail("New name already exists".to_owned()));
228            }
229        }
230
231        self.relation.rename_file(source_name, new_name)?;
232        self.relation.save_to_file(self.root_path.as_ref())?;
233        Ok(())
234    }
235
236    /// Remove file from rif
237    pub fn remove(&mut self, files: &Vec<impl AsRef<Path>>) -> Result<(), RifError> {
238        for file in files {
239            self.remove_file(file.as_ref())?;
240        }
241        self.relation.save_to_file(self.root_path.as_ref())?;
242        self.history.save_to_file(self.root_path.as_ref())?;
243        Ok(())
244    }
245
246    /// Set reference of file
247    pub fn set(&mut self, file: &Path, refs : &Vec<impl AsRef<Path>>) -> Result<(), RifError> {
248        let refs: HashSet<PathBuf> = refs.iter().map(|a| a.as_ref().to_owned()).collect();
249
250        self.relation.add_reference(file, &refs)?;
251        self.relation.save_to_file(self.root_path.as_ref())?;
252        Ok(())
253    }
254
255    /// Unset reference of file
256    pub fn unset(&mut self, file: &Path, refs : &Vec<impl AsRef<Path>>) -> Result<(), RifError> {
257        let refs: HashSet<PathBuf> = refs.iter().map(|a| a.as_ref().to_owned()).collect();
258
259        self.relation.remove_reference(file, &refs)?;
260        self.relation.save_to_file(self.root_path.as_ref())?;
261        Ok(())
262    }
263
264    /// Show current status of rif project
265    pub fn status(&mut self, ignore: bool, verbose: bool) -> Result<(), RifError> {
266        // Remove deleted files from to be added.
267        self.meta.remove_non_exsitent();
268
269        let mut to_be_added_later = self.meta.to_be_added_later().peekable();
270
271        if let Some(_) = to_be_added_later.peek() {
272            println!("# Changes to be commited :");
273            for item in &self.meta.to_be_registerd {
274                let format = format!("    new file : {}", &item.to_str().unwrap());
275                println!("{}", utils::green(&format));
276            }
277            for item in &self.meta.to_be_added {
278                let format = format!("    modified : {}", &item.to_str().unwrap());
279                println!("{}", utils::green(&format));
280            }
281            for item in &self.meta.to_be_forced {
282                let format = format!("    forced   : {}", &item.to_str().unwrap());
283                println!("{}", utils::green(&format));
284            }
285            for item in &self.meta.to_be_deleted {
286                let format = format!("    deleted  : {}", &item.to_str().unwrap());
287                println!("{}", utils::green(&format));
288            }
289            println!("");
290        }
291    
292        println!("# Changed files :");
293        self.relation.track_modified_files(self.meta.to_be_added_later())?;
294
295        // Ignore untracked files
296        if !ignore {
297            // Default black list only includes .rif file for now
298            // Currently only check relative paths,or say, stripped path
299            println!("\n# Untracked files :");
300            self.relation.track_unregistered_files(&self.black_list, &self.meta.to_be_registerd)?;
301        }
302
303        if verbose {
304            println!("\n# Current rif status:\n---");
305            print!("{}", self.relation);
306        }
307
308        // Save meta file
309        self.meta.save_to_file(self.root_path.as_ref())?;
310
311        Ok(())
312    }
313
314    /// Show file informations of rif project
315    pub fn list(&self, file : Option<impl AsRef<Path>>, list_type: ListType, depth: Option<usize>) -> Result<(), RifError> {
316        if let Some(file) = file {
317            // Print relation tree
318            self.relation.display_file_depth(file.as_ref(), 0)?;
319
320            // Also print update 
321            println!("\n# History : ");
322            self.history.print_history(file.as_ref())?;
323        }  else { // No file was given
324            match list_type {
325                ListType::All => {
326                    self.relation.display_depth(depth.unwrap_or(0))?;
327                }
328                ListType::Stale => {
329                    self.relation.display_stale_files(depth.unwrap_or(0))?;
330                }
331                // Updated is not yet added
332                _ => (),
333            }
334        }
335        Ok(())
336    }
337
338    /// Show data of rif project
339    pub fn data(&self, data_type: Option<&str>, compact: bool) -> Result<(), RifError> {
340        if let Some(data_type) = data_type {
341            match data_type {
342                "meta"  => {
343                    println!("{:#?}", self.meta);
344                }
345                "history" => {
346                    println!("{:#?}", self.history);
347                }
348                _ => () // This doesn't happen
349            }
350        } else {
351            if compact {
352                println!("{:?}", self.relation);
353            } else {
354                println!("{:#?}", self.relation);
355            }
356        }
357
358        Ok(())
359    }
360
361    /// Show files that depend on given file
362    pub fn depend(&self, file: &Path)  -> Result<(), RifError> {
363        let dependes = self.relation.find_depends(file)?;
364        println!("Files that depends on \"{}\"", file.display());
365        println!("=====");
366        for item in dependes {
367            println!("{}", utils::green(&item.display().to_string()));
368        }
369        Ok(())
370    }
371
372    /// Check file references
373    pub fn check(&mut self) -> Result<(), RifError> {
374        if self.relation.get_deleted_files().len() != 0 {
375            return Err(RifError::CheckerError("Check with deleted files are illegal. Rejected".to_owned()));
376        }
377
378        self.check_exec()?;
379        Ok(())
380    }
381
382    /// Check sanity of rif proeject
383    pub fn sanity(&mut self, fix: bool) -> Result<(), RifError> {
384        // NOTE ::: You don't have to manually call sanity check
385        // Because read operation always check file sanity after reading a file
386        // and return erros if sanity was not assured.
387        if fix {
388            self.relation.sanity_fix()?;
389            self.relation.save_to_file(self.root_path.as_ref())?;
390            println!("Sucessfully fixed the rif file");
391        } else {
392            self.relation.sanity_check()?;
393            println!("Sucessfully checked the rif file");
394        }
395        Ok(())
396    }
397
398    // External methods end
399
400    // MISC methods start
401    //
402    
403    /// Check file relations(impact of changes)
404    fn check_exec(&mut self) -> Result<(), RifError> {
405        // Check relations(impact)
406        let mut checker = Checker::with_relations(&self.relation)?;
407        let changed_files = checker.check(&mut self.relation)?;
408
409        if changed_files.len() != 0 && self.config.hook.trigger {
410            println!("\nHook Output");
411            self.config.hook.execute(changed_files)?;
412        }
413
414        Ok(())
415    }
416    
417    /// Check if given path is inside black_list
418    fn is_in_black_list(&self, path: &Path) -> bool {
419        // File is in rif ignore
420        if let Some(_) = self.black_list.get(path) {
421            // If File is not configurable
422            // It's not allowed by the program
423            // else it's allowd by the program 
424            if BLACK_LIST.to_vec().contains(&path.to_str().unwrap()) {
425                eprintln!("File : \"{}\" is not allowed", path.display());
426            } else {
427                println!("\"{}\" is in rifignore file, which is ignored.", path.display());
428            }
429            return true;
430        } 
431
432        false
433    }
434
435    /// Add new file to rif 
436    fn add_new_file(&mut self, file: &Path) -> Result<(), RifError> {
437        self.meta.to_be_registerd.insert(file.to_owned());
438        Ok(())
439    }
440
441    /// Remove new file to rif 
442    fn remove_file(&mut self, file: &Path) -> Result<(), RifError> {
443        self.relation.remove_file(file)?;
444        self.history.remove_file(file)?;
445        Ok(())
446    }
447
448    /// Register a new file
449    fn register_new_file(&mut self, file: &Path, message: Option<&str>) -> Result<(), RifError> {
450        // Closure to recursively get inside directory and add files
451        let mut closure = |entry_path : PathBuf| -> Result<LoopBranch, RifError> {
452            let striped_path = utils::relativize_path(&entry_path)?;
453
454            // Early return if file or directory is in black_list
455            // Need to check the black_list once more because closure checks nested
456            // directory that is not checked in outer for loop
457            if let Some(_) = self.black_list.get(&striped_path) {
458                if striped_path.is_dir() {
459                    return Ok(LoopBranch::Exit);
460                } 
461                else {
462                    return Ok(LoopBranch::Continue);
463                }
464            }
465
466            if !self.relation.add_file(&striped_path)? { return Ok(LoopBranch::Continue); }
467            Ok(LoopBranch::Continue)
468        }; // Closure end here 
469
470        // if path is a directory then recusively get into it
471        // if path is a file then simply add a file
472        if file.is_dir() {
473            utils::walk_directory_recursive(file, &mut closure)?;
474        } else { 
475            let file = utils::relativize_path(file)?;
476
477            // TODO 
478            // THis returns bools, is it not needed?
479            // File was not added e.g. file already exists
480            self.relation.add_file(&file)?;
481            self.history.add_history(&file, message.unwrap_or(""))?;
482        }
483        Ok(())
484    }
485    
486    /// Add directory
487    fn add_directory(&mut self, dir: &Path) -> Result<(), RifError> {
488        let tracked = self.relation.files.keys().cloned().collect::<Vec<PathBuf>>();
489        let modified = self.relation.get_modified_files()?.clone();
490        let mut deleted = self.relation.get_deleted_files().clone();
491        let mut to_be_deleted = HashSet::new();
492        let mut to_be_added = HashSet::new();
493        let mut to_be_registerd = HashSet::new();
494        // Closure to recursively get inside directory and add files
495        let mut closure = |entry_path : PathBuf| -> Result<LoopBranch, RifError> {
496            let striped_path = utils::relativize_path(&entry_path)?;
497            // Early return if file or directory is in black_list
498            // Need to check the black_list once more because closure checks nested
499            // directory that is not checked in outer for loop
500            if let Some(_) = self.black_list.get(&striped_path) {
501                if striped_path.is_dir() {
502                    return Ok(LoopBranch::Exit);
503                } 
504                else {
505                    return Ok(LoopBranch::Continue);
506                }
507            }
508
509            // If directory go inside and don't add the directory
510            if striped_path.is_dir() {
511                // Only retain deleted files that are not specified in directory
512                // or say, paths that don't start with entry path
513                deleted.retain(|path| {
514                    let is_inside = path.starts_with(&entry_path);
515                    // Add to to_be_deleted
516                    if is_inside {
517                        to_be_deleted.insert(path.to_owned());
518                    }
519                    // Only retain files that is not inside the directory
520                    !is_inside
521                });
522                return Ok(LoopBranch::Continue);
523            } 
524
525            if modified.contains(&striped_path) {
526                // Is modified file
527                to_be_added.insert(striped_path);
528            } else if !tracked.contains(&striped_path) {
529                // NOt in a tracking file
530                to_be_registerd.insert(striped_path);
531            }
532            Ok(LoopBranch::Continue)
533        }; // Closure end here 
534
535        utils::walk_directory_recursive(dir, &mut closure)?;
536
537        self.meta.to_be_registerd.extend(to_be_registerd);
538        self.meta.to_be_added.extend(to_be_added);
539        self.meta.to_be_deleted.extend(to_be_deleted);
540
541        Ok(())
542    }
543
544    /// Add old file
545    fn add_old_file(&mut self, file: &Path, force: bool) -> Result<(), RifError> {
546        if file.exists() {
547            self.meta.queue_added(file, force);
548        } else {
549            self.meta.queue_deleted(file);
550        }
551        Ok(())
552    }
553
554    // MISC methods end
555}