1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
//! The Backup module handles creating backups using the methods //! specified in the GNU coreutils manual. //! //! ## About //! The GNU coreutils [backup options](https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html#Backup-options) //! support 4 backup modes: //! - None //! - Numbered //! - Existing //! - Simple //! //! **NOTE:** This module does not figure out default values based on environment //! variables as defined in the GNU backup options manual page. Whether to adhere to the //! GNU standard or not is up to the user of this module. //! //! ### None //! Can be specified by either supplying the strings `none` or `off`. //! Fairly self-explanatory: Never make backups. //! //! ### Numbered //! Can be specified by either supplying the strings `numbered` or `t`. //! This mode always makes numbered backups. This means that a backup of a file `a.txt` //! will be backed up to `a.txt~X~` where `X` is the next number backup. //! //! For example, if we create a file named `main.rs` and then back it up in this mode, we //! will get `main.rs~1~`. If we back the file up a second time, we will get `main.rs~2~`. //! //! ### Simple //! Can be specified by either supplying the strings `simple` or `never` (not to be //! confused with `none`) This mode simple appends a static suffix to the backup (This is //! how Emacs makes backup files by default). //! //! For exmaple, if we create a file named `main.rs` and then back it up in this mode, we //! will get `main.rs~`. If we back the file up a second time, we will overwrite the old //! backup and the new backup will be called `main.rs~`. //! //! ### Existing //! Can be specified by either supplying the strings `existing` or `nil`. //! This mode checks for the existance of previous backups in any mode. If it finds //! numbered backups, it will continue to make numbered backups. If it finds simple //! backups, it will continue to make simple backups. use regex::Regex; use std::{ fs, io::{Error, ErrorKind}, path::{Path, PathBuf}, }; /// Convenience Enum to represent the different backup modes. See module documentation for /// an in-depth overview of what each backup mode means/does. #[derive(Debug, Clone, PartialEq, Eq)] pub enum BackupMode { /// No backups will be made. None, /// Backups will be made of the form `<filename>~<X>~` where `X` is the next backup /// number. Numbered, /// The backup method will be consistant with what already exists for the current /// file. Existing, /// Backups will be made of the form `<filename><suffix>` where `suffix` is any /// suffix. Simple, } impl BackupMode { /// Creates an instance of [`BackupMode`] from a string slice. Any invalid input will /// result in [`BackupMode::Existing`] to be returned. pub fn from_string(string: impl AsRef<str>) -> Self { Self::from(string.as_ref()) } } impl From<&str> for BackupMode { /// Creates an instance of [`BackupMode`] from a string slice. Any invalid input will /// result in [`BackupMode::Existing`] to be returned. fn from(string: &str) -> Self { match string { "none" | "off" => BackupMode::None, "numbered" | "t" => BackupMode::Numbered, "existing" | "nil" => BackupMode::Existing, "simple" | "never" => BackupMode::Simple, _ => BackupMode::Existing, } } } /// Creates a numbered backup. Does so by taking the input `file` and poking the parent /// directory to find a file of the form `<file>~<X>~` where `X` is a number. If none can /// be found, a backup file is created where `X` is `1`. Else, it creates a backup file /// where `X` is `X + 1`. /// /// # Errors /// If this function encounters any kind of I/O error, an error variant will be returned. // # Arguments // * `file` - the file to be backed up // # Remarks // Returns a `Result` of either a `PathBuf` to the newly created backup file or an // `io::Error` pub fn create_numbered_backup(file: &Path) -> Result<PathBuf, Error> { let mut index = 1_u64; loop { if index == std::u64::MAX { return Err(Error::new( ErrorKind::AlreadyExists, "Cannot create backup: too many backup files", )); } let new = file.with_extension(format!("~{}~", index)); if !new.exists() { match fs::rename(file, &new) { Ok(()) => return Ok(new), Err(err) => return Err(err), }; } index += 1; } } /// Creates a backup in-keeping with previous backups. Pokes the directory to see whether /// there are any numbered or simple backups of the input `file`. If numbered backups are /// found, a numbered backup will be created. Else, a simple backup is created using the /// input `suffix` /// /// # Errors /// If this function encounters any kind of I/O error, an error variant will be returned. // # Arguments // * `file` - the file to be backed up // * `suffix` - the suffix of the backup file // # Remarks // Returns a `Result` of either a `PathBuf` to the newly created backup file or an // `io::Error` pub fn create_existing_backup(file: &Path, suffix: &str) -> Result<PathBuf, Error> { let mut has_numbered_backup = false; let regex = Regex::new(r"~\d+~").unwrap(); let parent = file.parent().unwrap(); for entry in parent.read_dir().unwrap() { if let Ok(entry) = entry { if let Some(ext) = entry.path().extension() { if regex.is_match(ext.to_str().unwrap()) { has_numbered_backup = true; break; } } } } if has_numbered_backup { create_numbered_backup(file) } else { create_simple_backup(file, suffix) } } /// Creates a simple backup. Creates a backup of the form `<file><suffix>`. Overwrites any /// previous backup files with that same suffix. /// /// # Errors /// If this function encounters any kind of I/O error, an error variant will be returned. // # Arguments // * `file` - the file to be backed up // * `suffix` - the suffix of the backup file // # Remarks // Returns a `Result` of either a `PathBuf` to the newly created backup file or an // `io::Error` pub fn create_simple_backup(file: &Path, suffix: &str) -> Result<PathBuf, Error> { let new = PathBuf::from(format!("{}{}", file.display(), suffix)); match fs::rename(file, &new) { Ok(()) => Ok(new), Err(error) => Err(error), } }