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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
#![feature(try_blocks)]
mod depot;
mod dotup;
mod utils;
use std::path::PathBuf;
use clap::Parser;
use flexi_logger::Logger;
use utils::DEFAULT_DEPOT_FILE_NAME;
#[derive(Parser, Debug)]
pub struct Flags {
/// Path to the depot file, default to `.depot`.
#[clap(long)]
depot: Option<PathBuf>,
/// Path to the install base, defaults to the home directory.
#[clap(long)]
install_base: Option<PathBuf>,
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// A level of verbosity, and can be used multiple times
///
/// Level 1 - Info
///
/// Level 2 - Debug
///
/// Level 3 - Trace
#[clap(short, long, parse(from_occurrences))]
verbose: i32,
#[clap(flatten)]
flags: Flags,
#[clap(subcommand)]
command: SubCommand,
}
#[derive(Parser, Debug)]
enum SubCommand {
Init(InitArgs),
Link(LinkArgs),
Unlink(UnlinkArgs),
Install(InstallArgs),
Uninstall(UninstallArgs),
Mv(MvArgs),
Status(StatusArgs),
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let log_level = match args.verbose {
0 => "warn",
1 => "info",
2 => "debug",
_ => "trace",
};
Logger::try_with_env_or_str(log_level)?
.format(flexi_logger::colored_default_format)
.set_palette("196;208;32;198;15".to_string())
.start()?;
match args.command {
SubCommand::Init(cmd_args) => command_init(args.flags, cmd_args),
SubCommand::Link(cmd_args) => command_link(args.flags, cmd_args),
SubCommand::Unlink(cmd_args) => command_unlink(args.flags, cmd_args),
SubCommand::Install(cmd_args) => command_install(args.flags, cmd_args),
SubCommand::Uninstall(cmd_args) => command_uninstall(args.flags, cmd_args),
SubCommand::Mv(cmd_args) => command_mv(args.flags, cmd_args),
SubCommand::Status(cmd_args) => command_status(args.flags, cmd_args),
}
}
/// Creates an empty depot file if one doesnt already exist.
///
/// By default this will create the file in the current directory
/// but the `path` option can be used to change this path.
#[derive(Parser, Debug)]
struct InitArgs {
path: Option<PathBuf>,
}
fn command_init(_global_flags: Flags, args: InitArgs) -> anyhow::Result<()> {
let depot_path = {
let mut path = args.path.unwrap_or_else(utils::default_depot_path);
if path.is_dir() {
path = path.join(DEFAULT_DEPOT_FILE_NAME);
}
path
};
if depot_path.exists() {
println!("Depot at {} already exists", depot_path.display());
} else {
depot::write(&depot_path, &Default::default())?;
println!("Depot initialized at {}", depot_path.display());
}
Ok(())
}
/// Creates links
///
/// If a link is created for a file that already had a link then the old link will be overwritten.
/// By default creating a link to a directory will recursively link all files under that
/// directory, to actually link a directory use the --directory flag.
#[derive(Parser, Debug)]
struct LinkArgs {
#[clap(long)]
directory: bool,
origins: Vec<PathBuf>,
destination: PathBuf,
}
fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> {
let mut dotup = utils::read_dotup(&global_flags)?;
for origin in args.origins {
if !args.directory && origin.is_dir() {
let directory = origin;
let origins = utils::collect_files_in_dir_recursive(&directory)?;
for origin in origins {
// unwrap: origin is under directory so stripping should not fail
let path_extra = origin.strip_prefix(&directory).unwrap();
let destination = args.destination.join(path_extra);
dotup.link(&origin, &destination);
}
} else {
dotup.link(&origin, &args.destination);
};
}
utils::write_dotup(&dotup)?;
Ok(())
}
/// Unlinks files/directories.
///
/// This will recursively remove links. If a path is a directory then it will remove all links
/// recursively.
/// The links are not uninstall by default, see the --uninstall parameter.
#[derive(Parser, Debug)]
struct UnlinkArgs {
#[clap(long)]
uninstall: bool,
paths: Vec<PathBuf>,
}
fn command_unlink(global_flags: Flags, args: UnlinkArgs) -> anyhow::Result<()> {
let mut dotup = utils::read_dotup(&global_flags)?;
dotup.unlink(args.paths.into_iter(), args.uninstall);
utils::write_dotup(&dotup)?;
Ok(())
}
/// Install links. (Creates symlinks).
///
/// Installing a link will create the necessary directories.
/// If a file or directory already exists at the location a link would be installed this command will fail.
#[derive(Parser, Debug)]
struct InstallArgs {
#[clap(long)]
directory: bool,
paths: Vec<PathBuf>,
}
fn command_install(global_flags: Flags, args: InstallArgs) -> anyhow::Result<()> {
let dotup = utils::read_dotup(&global_flags)?;
dotup.install(args.paths.into_iter());
Ok(())
}
/// Uninstalls links. (Removes symlinks).
///
/// Uninstalling a link for a file that didn't have a link will do nothing.
/// Uninstalling a directory will recursively uninstall all files under it.
/// Symlinks are only deleted if they were pointing to the correct file.
#[derive(Parser, Debug)]
struct UninstallArgs {
paths: Vec<PathBuf>,
}
fn command_uninstall(global_flags: Flags, args: UninstallArgs) -> anyhow::Result<()> {
let dotup = utils::read_dotup(&global_flags)?;
dotup.uninstall(args.paths.into_iter());
Ok(())
}
/// Moves files/directories and updates links.
#[derive(Parser, Debug)]
struct MvArgs {
paths: Vec<PathBuf>,
}
fn command_mv(global_flags: Flags, args: MvArgs) -> anyhow::Result<()> {
let mut dotup = utils::read_dotup(&global_flags)?;
let mut paths = args.paths;
if paths.len() < 2 {
return Err(anyhow::anyhow!("mv requires atleast 2 arguments"));
}
let to = paths.pop().unwrap();
let from = paths;
dotup.mv(from.iter(), &to);
utils::write_dotup(&dotup)?;
Ok(())
}
/// Shows information about links
#[derive(Parser, Debug)]
struct StatusArgs {
#[clap(default_value = ".")]
paths: Vec<PathBuf>,
}
fn command_status(global_flags: Flags, args: StatusArgs) -> anyhow::Result<()> {
let dotup = utils::read_dotup(&global_flags)?;
dotup.status(args.paths.into_iter());
Ok(())
}
|