1pub mod cli;
2pub mod path;
3pub mod readme_template;
4
5use git2::build::RepoBuilder;
6use git2::{Cred, FetchOptions, RemoteCallbacks, Repository};
7use log::info;
8use path::{file_to_path, home_path};
9use rayon::prelude::*;
10use readme_template::write_template_readme;
11use serde::{Deserialize, Serialize};
12use std::fs::{copy, create_dir_all, File};
13use std::path::{Path, PathBuf};
14use std::string::String;
15
16#[derive(Debug, PartialEq, Serialize, Deserialize)]
18struct DotFiles {
19 dotfiles: Vec<String>,
21}
22
23fn copy_file(dest_file: String, orig_file: String, dry_run: bool) -> Result<u8, String> {
33 let source_file_pathbuf: PathBuf = file_to_path(&orig_file, true)?;
35 let dest_file_pathbuf: PathBuf = file_to_path(&dest_file, false)?;
36 let prefix: &Option<&Path> = &dest_file_pathbuf.parent();
38 if let Some(prefix_path) = prefix {
39 create_dir_all(prefix_path).map_err(|e| e.to_string())?
40 }
41
42 let mut label: &str = "Dry run";
44 if !dry_run {
45 copy(source_file_pathbuf, dest_file_pathbuf).map_err(|e| e.to_string())?;
46 label = "Shell";
47 }
48 info!("[{}] Copied {} to {}", label, orig_file, dest_file,);
49 Ok(0)
50}
51
52pub fn save(
61 dotfile_list: Vec<String>,
62 destination_dir: String,
63 dry_run: bool,
64) -> Result<Vec<u8>, String> {
65 let home_dir: String = home_path()?;
66 if !dry_run {
69 write_template_readme(format!("{}/README.md", &destination_dir))?;
70 }
71
72 dotfile_list
74 .into_par_iter()
75 .map(|dotfile| {
76 let orig_file: String = format!("{}/{}", home_dir, dotfile);
77 let dest_file: String = format!("{}/{}", destination_dir, dotfile);
78 copy_file(dest_file, orig_file, dry_run)
79 })
80 .collect()
81}
82
83pub fn apply(
90 dotfile_list: Vec<String>,
91 dotfiles_dir: String,
92 dry_run: bool,
93) -> Result<Vec<u8>, String> {
94 info!("Applying dotfiles from: {}", dotfiles_dir);
95 let home_dir: String = home_path()?;
96 dotfile_list
99 .into_par_iter()
100 .map(|dotfile| {
101 let orig_file: String = format!("{}/{}", dotfiles_dir, dotfile);
102 let dest_file: String = format!("{}/{}", home_dir, dotfile);
103 copy_file(dest_file, orig_file, dry_run)
104 })
105 .collect()
106}
107
108pub fn install(
116 dotfile_list: Vec<String>,
117 github_url: String,
118 ssh_key_file: String,
119 dry_run: bool,
120) -> Result<Vec<u8>, String> {
121 let home_dir: String = home_path()?;
122
123 let git_dotfiles_dir: String = format!("{}/dotfiles", &home_dir);
125 let git_dotfiles_path: &Path = Path::new(&git_dotfiles_dir);
126
127 let _repo = match git_dotfiles_path.exists() {
129 true => Repository::open(git_dotfiles_path)
130 .map_err(|_| format!("Folder not exists: {}", git_dotfiles_dir)),
131 _ => {
132 let repo: Result<Repository, String> =
133 git_clone(github_url, &git_dotfiles_dir, ssh_key_file);
134 info!("Clone complete");
135 repo
136 }
137 }?;
138 apply(dotfile_list, git_dotfiles_dir, dry_run)
140}
141
142fn git_clone(
149 github_url: String,
150 git_dotfiles_dir: &String,
151 ssh_private_key_fn: String,
152) -> Result<Repository, String> {
153 match github_url.starts_with("git@github.com") {
154 true => {
155 info!("Cloning {} into {}", github_url, git_dotfiles_dir);
156 let git_dotfiles_path: &Path = Path::new(&git_dotfiles_dir);
157 let ssh_pub_key_fn: String = format!("{}.pub", &ssh_private_key_fn);
159 let ssh_pub_key_file_path: PathBuf = file_to_path(&ssh_pub_key_fn, true)?;
160 let ssh_private_key_file_path: PathBuf = file_to_path(&ssh_private_key_fn, true)?;
161
162 let mut builder: RepoBuilder = RepoBuilder::new();
164 let mut callbacks: RemoteCallbacks = RemoteCallbacks::new();
165 let mut fetch_options: FetchOptions = FetchOptions::new();
166
167 callbacks.credentials(|_, _, _| {
168 let credentials: Cred = Cred::ssh_key(
169 "git",
170 Some(&ssh_pub_key_file_path),
171 &ssh_private_key_file_path,
172 None,
173 ).expect("Credential problem");
174 Ok(credentials)
175 });
176
177 fetch_options.remote_callbacks(callbacks);
178
179 builder.fetch_options(fetch_options);
180
181 builder
182 .clone(&github_url, git_dotfiles_path)
183 .map_err(|e| e.to_string())
184 },
185 _ => Err(String::from("We only support ssh-key cloning, which the github url should start with git@github.com prefix"))
186 }
187}
188
189pub fn read_yaml(yaml_fn: &str) -> Result<Vec<String>, String> {
206 let f: File = File::open(yaml_fn).map_err(|e| e.to_string())?;
207 let data: DotFiles = serde_yaml::from_reader(f).map_err(|e| e.to_string())?;
208 Ok(data.dotfiles)
209}