Skip to main content

git_conform/core/
api.rs

1//! Public interface of the core module
2
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::missing_panics_doc)]
5
6use crate::core::backend::{
7    search_for_repos,
8    exec_async_check
9};
10use crate::utils::{
11    APP_NAME,
12    TrackingFile,
13    repo_is_tracked,
14    repos_valid
15};
16
17use std::fs::{self, OpenOptions};
18use std::io::Write as _;
19use std::path::Path;
20
21use colored::Colorize;
22
23/// Scans only specified directories
24pub fn scan_dirs(mut dirs: Vec<String>, tracking_file: &TrackingFile, scan_hidden: bool) -> Result<String, String> {
25    // Remove duplicates
26    dirs.sort_unstable();
27    dirs.dedup();
28
29    // Directories validation
30
31    let mut dirs_ok = true;
32
33    for dir in &mut dirs {
34        let path = Path::new(&dir);
35
36        // Check if the path exists
37        if let Ok(p) = path.try_exists() {
38            if !p {
39                eprintln!("{APP_NAME}: Directory '{dir}' does not exist");
40                dirs_ok = false;
41                continue;
42            }
43        }
44        else {
45            eprintln!("{APP_NAME}: Cannot check the existance of directory '{dir}'");
46            dirs_ok = false;
47            continue;
48        }
49
50        // Check if the path leads to a file
51        if path.is_file() {
52            eprintln!("{APP_NAME}: '{dir}' is not a directory");
53            dirs_ok = false;
54        }
55
56        // Check if the path contains valid UTF-8 characters
57        // and make it absolute, if it does
58        if let Some(s) = fs::canonicalize(&dir)
59            .map_err(|e| format!("{dir}: {e}"))?
60            .to_str() {
61            *dir = s.to_string();
62        }
63        else {
64            eprintln!("{APP_NAME}: {dir}: The path contains invalid UTF-8 characters");
65            dirs_ok = false;
66        }
67    }
68
69    if !dirs_ok {
70        return Err(String::from("Directories validation failed"));
71    }
72
73    Ok(search_for_repos(dirs.as_slice(), tracking_file, scan_hidden)?)
74}
75
76/// Scans all directories in user's /home
77pub fn scan_all(home_dir: String, tracking_file: &TrackingFile, scan_hidden: bool) -> Result<String, String> {
78    Ok(search_for_repos(&[home_dir], tracking_file, scan_hidden)?)
79}
80
81/// Prints the paths of all tracked git repositories to the standard output
82pub fn list(track_file_contents: &str) -> Result<(), String> {
83    if track_file_contents.is_empty() {
84        return Err(String::from("No repository is being tracked"));
85    }
86
87    print!("{}", track_file_contents.bold());
88
89    Ok(())
90}
91
92/// Writes the paths of the specified repos to the tracking file
93pub fn add(mut repos: Vec<String>, tracking_file: &TrackingFile) -> Result<(), String> {
94    // Remove duplicates
95    repos.sort_unstable();
96    repos.dedup();
97
98    repos = repos_valid(repos.as_slice())?;
99
100    // Open/create the tracking file for writing
101    let mut track_file = OpenOptions::new()
102        .create(true)
103        .append(true)
104        .open(tracking_file.path.clone())
105        .map_err(|e| format!("{}: {e}", tracking_file.path))?;
106
107    for repo in repos {
108        // Check if the tracking file already
109        // contains the git repository path
110        if repo_is_tracked(repo.as_str(), tracking_file.contents.as_str()) {
111            println!("{APP_NAME}: '{repo}' is already being tracked");
112            continue;
113        }
114
115        // Add the path of the git repository to the tracking file
116        track_file.write_all(
117            format!("{repo}\n").as_bytes())
118            .map_err(|e| format!("{}: {e}", tracking_file.path))?;
119    }
120
121    Ok(())
122}
123
124/// Removes only specified repositories from the tracking file
125pub fn remove_repos(mut repos: Vec<String>, tracking_file: &TrackingFile) -> Result<(), String> {
126    if tracking_file.contents.is_empty() {
127        return Err(String::from("No repository is being tracked"));
128    }
129
130    // Remove duplicates
131    repos.sort_unstable();
132    repos.dedup();
133
134    let mut repos_ok = true;
135
136    // Repositories validation
137    for repo in &repos {
138        // Check if the tracking file contains the git repository
139        if !repo_is_tracked(repo.as_str(), tracking_file.contents.as_str()) {
140            eprintln!("{APP_NAME}: '{repo}' is not being tracked");
141            repos_ok = false;
142        }
143    }
144
145    if !repos_ok {
146        return Err(String::from("Repositories validation failed"));
147    }
148
149    let mut track_file_lines: Vec<&str> = tracking_file.contents.lines().collect();
150
151    // Open/create the tracking file for writing
152    let mut track_file = OpenOptions::new()
153        .write(true)
154        .truncate(true)
155        .open(tracking_file.path.clone())
156        .map_err(|e| format!("{}: {e}", tracking_file.path))?;
157
158    for repo in repos {
159        // Remove specified repositories from the vector
160        if let Some(last) = track_file_lines.last() {
161            if repo.trim() == last.trim() {
162                track_file_lines.pop();
163            }
164            else {
165                track_file_lines.retain(|&x| x.trim() != repo.trim());
166            }
167        }
168    }
169
170    // Write the final changes to the tracking file
171    track_file.write_all(track_file_lines.join("\n").as_bytes())
172        .map_err(|e| format!("{}: {e}", tracking_file.path))?;
173
174    Ok(())
175}
176
177/// Removes the tracking file
178pub fn remove_all(tracking_file: &TrackingFile) -> Result<(), String> {
179    if tracking_file.contents.is_empty() {
180        return Err(String::from("No repository is being tracked"));
181    }
182
183    fs::remove_file(tracking_file.path.clone()).map_err(|e| format!("{}: {e}", tracking_file.path))?;
184
185    Ok(())
186}
187
188/// Asynchronously retrieves important details about each repo
189/// in the repos Vec and prints them to the standard output
190pub async fn check_repos(mut repos: Vec<String>, flags: &[bool]) -> Result<(), String> {
191    // Remove duplicates
192    repos.sort_unstable();
193    repos.dedup();
194
195    repos = repos_valid(repos.as_slice())?;
196
197    exec_async_check(repos, flags.to_vec()).await?;
198
199    Ok(())
200}
201
202/// Asynchronously retrieves important details about each repo
203/// in the tracking file and prints them to the standard output
204pub async fn check_all(tracking_file: &TrackingFile, flags: &[bool]) -> Result<(), String> {
205    if tracking_file.contents.is_empty() {
206        return Err(String::from("No repository is being tracked"));
207    }
208
209    // Put all the tracking file entries in a Vec to
210    // avoid lifetime constraints on async tasks
211    let track_file_lines: Vec<String> = tracking_file.contents
212        .lines()
213        .map(String::from)
214        .collect();
215
216    exec_async_check(track_file_lines, flags.to_vec()).await?;
217
218    Ok(())
219}