rif/
utils.rs

1use crate::models::LoopBranch;
2use std::io::{BufRead, BufReader};
3use std::fs::File;
4use std::fs::metadata;
5use std::collections::HashSet;
6use std::path::{PathBuf, Path};
7use std::process::Command;
8
9#[cfg(feature = "color")]
10use colored::*;
11use chrono::NaiveDateTime;
12use filetime::FileTime;
13use crate::consts::*;
14use crate::RifError;
15
16/// Get file's system timestamp in unix time
17///
18/// # Args
19///
20/// * `path` - File path to get system timestamp
21pub fn get_file_unix_time(path: &Path) -> Result<NaiveDateTime, RifError> {
22    let metadata = std::fs::metadata(path)?;
23    // File
24    let mtime = FileTime::from_last_modification_time(&metadata);
25    // Convert to unix_time
26    let unix_time = chrono::NaiveDateTime::from_timestamp(mtime.unix_seconds(), 0);
27    Ok(unix_time)
28}
29
30/// Get current time in unix time
31pub fn get_current_unix_time() -> NaiveDateTime {
32    let now = chrono::Utc::now().timestamp();
33    let unix_time = chrono::NaiveDateTime::from_timestamp(now, 0);
34    unix_time
35}
36
37/// Recursively walk directories and call a given function
38///
39/// Function is called on all paths including files and directories
40/// but given path directory.
41///
42/// # Args
43///
44/// * `path` - File path to start directory walking
45/// * `f` - Function refernce to be triggered on every path entry
46pub fn walk_directory_recursive(path: &Path, f: &mut dyn FnMut(PathBuf) -> Result<LoopBranch, RifError>) -> Result<(), RifError> {
47    for entry in std::fs::read_dir(path)? {
48        let entry_path: PathBuf = strip_path(&entry?.path(), None)?;
49        let md = metadata(entry_path.clone()).unwrap();
50
51        // TODO Remove this check becuase std walk_dir doesn't include self path
52        if entry_path != path { // prevent infinite loop
53            // if not a directory, or is a file
54            // else, is a directory, recursive call a function
55            if !md.is_dir() {
56                if let LoopBranch::Exit = f(entry_path)? {
57                    return Ok(());
58                }
59            } else {
60                if let LoopBranch::Continue = f(entry_path.clone())? {
61                    walk_directory_recursive(&entry_path, f)?;
62                }
63            }
64        }  
65    }
66
67    Ok(())
68} // function end
69
70/// Walk directories and call a given function
71///
72/// Function is called on all paths including files and directories
73/// but given path directory.
74///
75/// # Args
76///
77/// * `path` - File path to start directory walking
78/// * `f` - Function refernce to be triggered on every path entry
79pub fn walk_directory(path: &Path, f: &mut dyn FnMut(PathBuf) -> Result<(), RifError>) -> Result<(), RifError> {
80    for entry in std::fs::read_dir(path)? {
81        f(entry?.path())?;
82    }
83    Ok(())
84}
85
86/// Strip a target path with a given base path
87///
88/// If no strip path is given, then strip a current working directory from a given path.
89///
90/// # Args
91///
92/// * `path` - Target path to strip
93/// * `base_path` - Path to strip from target, default is current working directory
94///
95/// # Example
96///
97/// ```
98/// // Current working directory is /home/user/test
99/// let target_path = PathBuf::from("/home/user/test/target");
100/// let stripped = strip_path(&target_path, None);
101/// assert_eq!(stripped, PathBuf::from("target"));
102/// 
103/// let stripped2 = strip_path(&target_path, Some(PathBuf::from("/home/user")));
104/// assert_eq!(stripped, PathBuf::from("test/target"));
105/// ```
106pub fn strip_path(path: &Path, base_path: Option<PathBuf>) -> Result<PathBuf, RifError> {
107    if let Some(base_path) = base_path {
108        if let Ok( striped_path ) =  path.strip_prefix(base_path) {
109            Ok(striped_path.to_owned())
110        } else {
111            Err(RifError::Ext(String::from("Failed to get stripped path")))
112        }
113    } else {
114        if let Ok( striped_path ) =  path.strip_prefix(std::env::current_dir()?) {
115            Ok(striped_path.to_owned())
116        } else {
117            Ok(path.to_path_buf())
118        }
119    }
120}
121
122/// Convert a path into a relative path
123///
124/// This function yields error when absolute path doesn't start with current working directory.
125/// # Args
126///
127/// * `path` - File path to make as relative path
128///
129/// # Example
130/// ```
131/// // Current working directory is /home/user/test/example
132/// let absolute = relativize_path(PathBuf::from("/home/user/test/example"));
133/// assert_eq!(absolute, PathBuf::from("example"));
134///
135/// let dotslash = relativize_path(PathBuf::from("./test/example"));
136/// assert_eq!(absolute, PathBuf::from("example"));
137/// ```
138pub fn relativize_path(path: &Path) -> Result<PathBuf, RifError> {
139    let path_buf: PathBuf;
140
141    if path.starts_with("./") {
142        path_buf = strip_path(path, Some(PathBuf::from("./")))?;
143    } else if path.starts_with(&std::env::current_dir()?){
144        path_buf = strip_path(path, Some(std::env::current_dir()?))?;
145    } else if !std::env::current_dir()?.join(path).exists() {
146        return Err(RifError::RifIoError( format!("Only files inside of rif directory can be added\nFile \"{}\" is not.", path.display())));
147    } else {
148        return Ok(path.to_path_buf());
149    }
150
151    Ok(path_buf)
152}
153
154/// Get rif file
155///
156/// Return error if rif file is not in current or ancestors' directory
157/// This returns root directory that contains .rif directory, not .rif directory itself
158pub fn get_rif_directory() -> Result<PathBuf, RifError> {
159    for path in std::env::current_dir()?.ancestors() {
160        if path.join(RIF_DIECTORY).is_dir() {
161            return Ok(path.to_owned());
162        }
163    }
164    Err(RifError::ConfigError("Not a rif directory".to_owned()))
165}
166
167/// Get black list
168///
169/// This read rig ignore file contents and merge with const black list contents
170pub fn get_black_list(use_gitignore: bool) -> Result<HashSet<PathBuf>, RifError> {
171    let mut black_list: HashSet<PathBuf> = HashSet::new();
172    let rif_ignore = read_rif_ignore()?;
173
174    // Const files that should be ignored
175    black_list.extend(BLACK_LIST.to_vec().iter().map(|a| PathBuf::from(*a)).collect::<HashSet<PathBuf>>());
176    // Rif ignore files
177    black_list.extend(rif_ignore);
178
179    // Include git ignore files
180    if use_gitignore{
181        black_list.extend(read_git_ignore()?);
182        black_list.insert(PathBuf::from(".gitignore"));
183    } 
184
185    Ok(black_list)
186}
187
188/// Read rif ignore file
189///
190/// Do io operation and converts into hashset
191fn read_rif_ignore() -> Result<HashSet<PathBuf>, RifError> {
192    if let Ok(file) = File::open(RIF_IGNORE_FILE) {
193        let buffer = BufReader::new(file);
194        let ignore: Result<HashSet<PathBuf>, RifError> = buffer
195            .lines()
196            .map(|op| -> Result<PathBuf, RifError> {Ok(PathBuf::from(op?))})
197            .collect();
198
199        Ok(ignore?)
200    } else {
201        // It is perfectly normal that rifignore file doesn't exist
202        Ok(HashSet::new())
203    }
204}
205
206/// Read git ignore file
207fn read_git_ignore() -> Result<HashSet<PathBuf>, RifError> {
208    if let Ok(file) = File::open(".gitignore") {
209        let buffer = BufReader::new(file);
210        let ignore: Result<HashSet<PathBuf>, RifError> = buffer
211            .lines()
212            .map(|op| -> Result<PathBuf, RifError> {Ok(PathBuf::from(op?))})
213            .collect();
214
215        Ok(ignore?)
216    } else {
217        // It is perfectly normal that gitignore file doesn't exist
218        Ok(HashSet::new())
219    }
220}
221
222// Path Getters
223
224pub fn get_rel_path(path : Option<impl AsRef<Path>>) -> Result<PathBuf, RifError> {
225    let path = if let Some(path) = path {
226        path.as_ref().to_owned()
227    } else {  
228        std::env::current_dir()?
229    };
230    Ok(path.join(RIF_DIECTORY).join(RIF_REL_FILE))
231}
232
233pub fn get_config_path(path : Option<impl AsRef<Path>>) -> Result<PathBuf, RifError> {
234    let path = if let Some(path) = path {
235        path.as_ref().to_owned()
236    } else {  
237        std::env::current_dir()?
238    };
239    Ok(path.join(RIF_DIECTORY).join(RIF_CONFIG))
240}
241
242pub fn get_history_path(path : Option<impl AsRef<Path>>) -> Result<PathBuf, RifError> {
243    let path = if let Some(path) = path {
244        path.as_ref().to_owned()
245    } else {  
246        std::env::current_dir()?
247    };
248    Ok(path.join(RIF_DIECTORY).join(RIF_HIST_FILE))
249}
250
251pub fn get_meta_path(path : Option<impl AsRef<Path>>) -> Result<PathBuf, RifError> {
252    let path = if let Some(path) = path {
253        path.as_ref().to_owned()
254    } else {  
255        std::env::current_dir()?
256    };
257    Ok(path.join(RIF_DIECTORY).join(RIF_META))
258}
259
260pub fn green(string : &str) -> Box<dyn std::fmt::Display> {
261    if cfg!(feature = "color") {
262        #[cfg(feature = "color")]
263        return Box::new(string.green().to_owned());
264    }
265    Box::new(string.to_owned())
266}
267
268pub fn blue(string : &str) -> Box<dyn std::fmt::Display> {
269    if cfg!(feature = "color") {
270        #[cfg(feature = "color")]
271        return Box::new(string.blue().to_owned());
272    }
273    Box::new(string.to_owned())
274}
275
276pub fn red(string : &str) -> Box<dyn std::fmt::Display> {
277    if cfg!(feature = "color") {
278        #[cfg(feature = "color")]
279        return Box::new(string.red().to_owned());
280    }
281    Box::new(string.to_owned())
282}
283
284pub fn yellow(string : &str) -> Box<dyn std::fmt::Display> {
285    if cfg!(feature = "color") {
286        #[cfg(feature = "color")]
287        return Box::new(string.yellow().to_owned());
288    }
289    Box::new(string.to_owned())
290}
291
292pub fn cmd(cmd: &str, args: Vec<String>) -> Result<(), RifError> {
293    let output = if cfg!(target_os = "windows") {
294        Command::new("cmd")
295            .arg("/C")
296            .args(args)
297            .output()
298            .expect("failed to execute process")
299    } else {
300        Command::new(cmd)
301            .args(args)
302            .output()
303            .expect("failed to execute process")
304    };
305
306    // This can be not valid, since it is lossy, but hardly 
307    let stdout = String::from_utf8_lossy(&output.stdout);
308    let stderr = String::from_utf8_lossy(&output.stderr);
309
310    if stdout.len() != 0 { println!("{}",stdout); }
311    if stderr.len() != 0 { eprintln!("{}",stderr); }
312
313    Ok(())
314}