aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs265
1 files changed, 82 insertions, 183 deletions
diff --git a/src/main.rs b/src/main.rs
index a56238e..80a03b9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,226 +1,125 @@
1#![feature(try_blocks)] 1#![feature(drain_filter)]
2 2
3mod depot; 3//pub mod config;
4mod dotup; 4pub mod dotup;
5mod utils;
6 5
7use std::path::PathBuf; 6use std::path::PathBuf;
8 7
9use clap::Parser; 8use anyhow::Context;
10use flexi_logger::Logger; 9use clap::{Parser, Subcommand};
11use utils::DEFAULT_DEPOT_FILE_NAME;
12 10
13#[derive(Parser, Debug)] 11#[derive(Parser, Debug)]
14pub struct Flags { 12struct GlobalFlags {
15 /// Path to the depot file, default to `.depot`.
16 #[clap(long)] 13 #[clap(long)]
17 depot: Option<PathBuf>, 14 base: Option<PathBuf>,
18 15
19 /// Path to the install base, defaults to the home directory. 16 #[clap(long, default_value = "./dotup")]
20 #[clap(long)] 17 config: PathBuf,
21 install_base: Option<PathBuf>,
22}
23
24#[derive(Parser, Debug)]
25#[clap(author, version, about, long_about = None)]
26struct Args {
27 /// A level of verbosity, and can be used multiple times
28 ///
29 /// Level 1 - Info
30 ///
31 /// Level 2 - Debug
32 ///
33 /// Level 3 - Trace
34 #[clap(short, long, parse(from_occurrences))]
35 verbose: i32,
36
37 #[clap(flatten)]
38 flags: Flags,
39
40 #[clap(subcommand)]
41 command: SubCommand,
42} 18}
43 19
44#[derive(Parser, Debug)] 20#[derive(Subcommand, Debug)]
45enum SubCommand { 21enum SubCommand {
46 Init(InitArgs),
47 Link(LinkArgs),
48 Unlink(UnlinkArgs),
49 Install(InstallArgs), 22 Install(InstallArgs),
50 Uninstall(UninstallArgs), 23 Uninstall(UninstallArgs),
51 Mv(MvArgs),
52 Status(StatusArgs), 24 Status(StatusArgs),
25 Format(FormatArgs),
53} 26}
54 27
55fn main() -> anyhow::Result<()> {
56 let args = Args::parse();
57
58 let log_level = match args.verbose {
59 0 => "warn",
60 1 => "info",
61 2 => "debug",
62 _ => "trace",
63 };
64
65 Logger::try_with_env_or_str(log_level)?
66 .format(flexi_logger::colored_default_format)
67 .set_palette("196;208;32;198;15".to_string())
68 .start()?;
69
70 match args.command {
71 SubCommand::Init(cmd_args) => command_init(args.flags, cmd_args),
72 SubCommand::Link(cmd_args) => command_link(args.flags, cmd_args),
73 SubCommand::Unlink(cmd_args) => command_unlink(args.flags, cmd_args),
74 SubCommand::Install(cmd_args) => command_install(args.flags, cmd_args),
75 SubCommand::Uninstall(cmd_args) => command_uninstall(args.flags, cmd_args),
76 SubCommand::Mv(cmd_args) => command_mv(args.flags, cmd_args),
77 SubCommand::Status(cmd_args) => command_status(args.flags, cmd_args),
78 }
79}
80
81/// Creates an empty depot file if one doesnt already exist.
82///
83/// By default this will create the file in the current directory
84/// but the `path` option can be used to change this path.
85#[derive(Parser, Debug)] 28#[derive(Parser, Debug)]
86struct InitArgs { 29struct InstallArgs {
87 path: Option<PathBuf>, 30 groups: Vec<String>,
88}
89
90fn command_init(_global_flags: Flags, args: InitArgs) -> anyhow::Result<()> {
91 let depot_path = {
92 let mut path = args.path.unwrap_or_else(utils::default_depot_path);
93 if path.is_dir() {
94 path = path.join(DEFAULT_DEPOT_FILE_NAME);
95 }
96 path
97 };
98
99 if depot_path.exists() {
100 println!("Depot at {} already exists", depot_path.display());
101 } else {
102 depot::write(&depot_path, &Default::default())?;
103 println!("Depot initialized at {}", depot_path.display());
104 }
105
106 Ok(())
107} 31}
108 32
109/// Creates links
110///
111/// If a link is created for a file that already had a link then the old link will be overwritten.
112/// By default creating a link to a directory will recursively link all files under that
113/// directory, to actually link a directory use the --directory flag.
114#[derive(Parser, Debug)] 33#[derive(Parser, Debug)]
115struct LinkArgs { 34struct UninstallArgs {
116 #[clap(long)] 35 groups: Vec<String>,
117 directory: bool,
118
119 #[clap(min_values = 1)]
120 origins: Vec<PathBuf>,
121
122 destination: PathBuf,
123}
124
125fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> {
126 let mut dotup = utils::read_dotup(&global_flags)?;
127 for origin in args.origins {
128 if !args.directory && origin.is_dir() {
129 let directory = origin;
130 let origins = utils::collect_files_in_dir_recursive(&directory)?;
131 for origin in origins {
132 // unwrap: origin is under directory so stripping should not fail
133 let path_extra = origin.strip_prefix(&directory).unwrap();
134 let destination = args.destination.join(path_extra);
135 dotup.link(&origin, &destination);
136 }
137 } else {
138 dotup.link(&origin, &args.destination);
139 };
140 }
141 utils::write_dotup(&dotup)?;
142 Ok(())
143} 36}
144 37
145/// Unlinks files/directories.
146///
147/// This will recursively remove links. If a path is a directory then it will remove all links
148/// recursively.
149/// The links are not uninstall by default, see the --uninstall parameter.
150#[derive(Parser, Debug)] 38#[derive(Parser, Debug)]
151struct UnlinkArgs { 39struct StatusArgs {
152 #[clap(long)] 40 groups: Vec<String>,
153 uninstall: bool,
154
155 paths: Vec<PathBuf>,
156}
157
158fn command_unlink(global_flags: Flags, args: UnlinkArgs) -> anyhow::Result<()> {
159 let mut dotup = utils::read_dotup(&global_flags)?;
160 dotup.unlink(args.paths.into_iter(), args.uninstall);
161 utils::write_dotup(&dotup)?;
162 Ok(())
163} 41}
164 42
165/// Install links. (Creates symlinks).
166///
167/// Installing a link will create the necessary directories.
168/// If a file or directory already exists at the location a link would be installed this command will fail.
169#[derive(Parser, Debug)] 43#[derive(Parser, Debug)]
170struct InstallArgs { 44struct FormatArgs {}
171 #[clap(long)]
172 directory: bool,
173 45
174 paths: Vec<PathBuf>, 46#[derive(Parser, Debug)]
47struct Args {
48 #[clap(flatten)]
49 globals: GlobalFlags,
50 #[clap(subcommand)]
51 command: SubCommand,
175} 52}
176 53
177fn command_install(global_flags: Flags, args: InstallArgs) -> anyhow::Result<()> { 54fn main() -> anyhow::Result<()> {
178 let dotup = utils::read_dotup(&global_flags)?; 55 env_logger::init();
179 dotup.install(args.paths.into_iter());
180 Ok(())
181}
182 56
183/// Uninstalls links. (Removes symlinks). 57 let args = Args::parse();
184/// 58 match args.command {
185/// Uninstalling a link for a file that didn't have a link will do nothing. 59 SubCommand::Install(install) => command_install(args.globals, install),
186/// Uninstalling a directory will recursively uninstall all files under it. 60 SubCommand::Uninstall(uninstall) => command_uninstall(args.globals, uninstall),
187/// Symlinks are only deleted if they were pointing to the correct file. 61 SubCommand::Status(status) => command_status(args.globals, status),
188#[derive(Parser, Debug)] 62 SubCommand::Format(format) => command_format(args.globals, format),
189struct UninstallArgs { 63 }
190 paths: Vec<PathBuf>,
191} 64}
192 65
193fn command_uninstall(global_flags: Flags, args: UninstallArgs) -> anyhow::Result<()> { 66impl GlobalFlags {
194 let dotup = utils::read_dotup(&global_flags)?; 67 fn base_path_or_default(&self) -> PathBuf {
195 dotup.uninstall(args.paths.into_iter()); 68 self.base.clone().unwrap_or_else(|| {
196 Ok(()) 69 PathBuf::from(std::env::var("HOME").expect("failed to get HOME directory"))
70 })
71 }
197} 72}
198 73
199/// Moves files/directories and updates links. 74fn command_install(globals: GlobalFlags, args: InstallArgs) -> anyhow::Result<()> {
200#[derive(Parser, Debug)] 75 let dotup = dotup::load_file(&globals.config).context("failed to parse config")?;
201struct MvArgs { 76 let cwd = std::env::current_dir().context("failed to get current directory")?;
202 #[clap(min_values = 1)] 77 let install_params = dotup::InstallParams {
203 origins: Vec<PathBuf>, 78 cwd: &cwd,
204 79 home: &globals.base_path_or_default(),
205 destination: PathBuf, 80 };
81 for group in args.groups {
82 match dotup.find_group_by_name(&group) {
83 Some(group_id) => dotup.install(install_params, group_id)?,
84 None => log::error!("group not found: {}", group),
85 };
86 }
87 Ok(())
206} 88}
207 89
208fn command_mv(global_flags: Flags, args: MvArgs) -> anyhow::Result<()> { 90fn command_uninstall(globals: GlobalFlags, args: UninstallArgs) -> anyhow::Result<()> {
209 let mut dotup = utils::read_dotup(&global_flags)?; 91 let dotup = dotup::load_file(&globals.config).context("failed to parse config")?;
210 dotup.mv(args.origins.into_iter(), args.destination); 92 let cwd = std::env::current_dir().context("failed to get current directory")?;
211 utils::write_dotup(&dotup)?; 93 let uninstall_params = dotup::UninstallParams {
94 cwd: &cwd,
95 home: &globals.base_path_or_default(),
96 };
97 for group in args.groups {
98 match dotup.find_group_by_name(&group) {
99 Some(group_id) => dotup.uninstall(uninstall_params, group_id)?,
100 None => log::error!("group not found: {}", group),
101 };
102 }
212 Ok(()) 103 Ok(())
213} 104}
214 105
215/// Shows information about links 106fn command_status(globals: GlobalFlags, args: StatusArgs) -> anyhow::Result<()> {
216#[derive(Parser, Debug)] 107 let dotup = dotup::load_file(&globals.config).context("failed to parse config")?;
217struct StatusArgs { 108 let cwd = std::env::current_dir().context("failed to get current directory")?;
218 #[clap(default_value = ".")] 109 let install_params = dotup::InstallParams {
219 paths: Vec<PathBuf>, 110 cwd: &cwd,
111 home: &globals.base_path_or_default(),
112 };
113 for group in args.groups {
114 match dotup.find_group_by_name(&group) {
115 Some(group_id) => dotup.status(install_params, group_id)?,
116 None => log::error!("group not found: {}", group),
117 };
118 }
119 Ok(())
220} 120}
221 121
222fn command_status(global_flags: Flags, args: StatusArgs) -> anyhow::Result<()> { 122fn command_format(globals: GlobalFlags, _args: FormatArgs) -> anyhow::Result<()> {
223 let dotup = utils::read_dotup(&global_flags)?; 123 dotup::format_file_inplace(&globals.config).context("failed to format config")?;
224 dotup.status(args.paths.into_iter());
225 Ok(()) 124 Ok(())
226} 125}