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
use crate::{dotbak::Dotbak, errors::Result};
use clap::Parser;
use indicatif::HumanDuration;
use std::path::PathBuf;
use std::time::Instant;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// The action to perform
#[clap(subcommand)]
pub action: Action,
/// Whether to be verbose with logging or not.
/// Ex: printing the output of git commands.
#[clap(short, long)]
pub verbose: bool,
}
impl Cli {
/// Gets the action that's currently being performed, as a human-readable string.
pub fn action(&self) -> String {
match &self.action {
Action::Init { repo_url } => format!(
"Initializing{}...",
if repo_url.is_some() {
format!(" with url '{}'", repo_url.as_ref().unwrap())
} else {
String::new()
}
),
Action::Clone { repo_url } => format!("Cloning with url {}", repo_url).to_string(),
Action::Add { paths } => format!("Adding {} file(s)", paths.len()),
Action::Sync => "Synchronizing".to_string(),
Action::Remove { paths } => format!("Removing {} file(s)", paths.len()),
Action::Push => "Pushing".to_string(),
Action::Pull => "Pulling".to_string(),
Action::Git { args } => format!("Running 'git {}'", args.join(" ")),
Action::Deinit => "Deinitializing".to_string(),
}
}
/// Runs the command-line interface for `dotbak` based on the user's input.
pub fn run(&self) -> Result<()> {
// Get the dotbak instance.
let mut dotbak = self.get_dotbak()?;
let started = Instant::now();
println!("⏳ {}...", self.action());
// Run the action.
match &self.action {
// Do nothing if we've already initialized.
Action::Init { .. } | Action::Clone { .. } => (),
// Add the files.
Action::Add { paths } => {
dotbak.add(paths)?;
}
// Synchonize the files.
Action::Sync => {
dotbak.sync()?;
}
// Remove the files.
Action::Remove { paths } => {
dotbak.remove(paths)?;
}
// Push changes to remote.
Action::Push => {
dotbak.push()?;
}
// Pull changes from remote.
Action::Pull => {
dotbak.pull()?;
}
// Run an arbitrary git command.
Action::Git { args } => {
dotbak
.arbitrary_git_command(&args.iter().map(|s| s.as_str()).collect::<Vec<_>>())?;
}
// Deinitialize `dotbak`.
Action::Deinit => {
dotbak.deinit()?;
}
}
println!(
"✨ Done! {}",
console::style(format!("[{}]", HumanDuration(started.elapsed())))
.bold()
.dim(),
);
Ok(())
}
}
impl Cli {
/// Get the dotbak structure depending on the action.
fn get_dotbak(&self) -> Result<Dotbak> {
// Initialize the `Dotbak` instance depending on what the user wants.
match &self.action {
// If we are initializing, then just initialize.
Action::Init { repo_url: None } => Dotbak::init(self.verbose),
// If we're provided a repository URL, then clone it.
Action::Clone { repo_url }
| Action::Init {
repo_url: Some(repo_url),
} => Dotbak::clone(repo_url, self.verbose),
// Otherwise, we just load the instance.
_ => Dotbak::load(self.verbose),
}
}
}
#[derive(Parser)]
pub enum Action {
/// Initializes a new instance of `dotbak` in your home directory (at `~/.dotbak`).
Init {
/// The URL of the repository to clone. This is essentially the same as 'dotbak clone <REPO_URL>'.
#[arg(short, long)]
repo_url: Option<String>,
},
/// Clones an instance of `dotbak` from the given URL to your home directory (at `~/.dotbak`).
Clone {
/// The URL of the repository to clone.
repo_url: String,
},
/// Adds files to the repository.
Add {
/// The paths to the files to add.
paths: Vec<PathBuf>,
},
/// Synchonizes the home directory with the repository.
Sync,
/// Removes files from the repository.
Remove {
/// The paths to the files to remove.
paths: Vec<PathBuf>,
},
/// Pushes the repository to the remote.
Push,
/// Pulls the repository from the remote.
Pull,
/// Runs an arbitrary git command on the repository, as if you were in the repository directory.
/// TODO: this does not work with flags passed to git.
Git {
/// The arguments to pass to git.
args: Vec<String>,
},
/// Deinitializes an instance of `dotbak` in your home directory.
Deinit,
}