diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 265 |
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 | ||
| 3 | mod depot; | 3 | //pub mod config; |
| 4 | mod dotup; | 4 | pub mod dotup; |
| 5 | mod utils; | ||
| 6 | 5 | ||
| 7 | use std::path::PathBuf; | 6 | use std::path::PathBuf; |
| 8 | 7 | ||
| 9 | use clap::Parser; | 8 | use anyhow::Context; |
| 10 | use flexi_logger::Logger; | 9 | use clap::{Parser, Subcommand}; |
| 11 | use utils::DEFAULT_DEPOT_FILE_NAME; | ||
| 12 | 10 | ||
| 13 | #[derive(Parser, Debug)] | 11 | #[derive(Parser, Debug)] |
| 14 | pub struct Flags { | 12 | struct 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)] | ||
| 26 | struct 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)] |
| 45 | enum SubCommand { | 21 | enum 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 | ||
| 55 | fn 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)] |
| 86 | struct InitArgs { | 29 | struct InstallArgs { |
| 87 | path: Option<PathBuf>, | 30 | groups: Vec<String>, |
| 88 | } | ||
| 89 | |||
| 90 | fn 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)] |
| 115 | struct LinkArgs { | 34 | struct 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 | |||
| 125 | fn 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)] |
| 151 | struct UnlinkArgs { | 39 | struct StatusArgs { |
| 152 | #[clap(long)] | 40 | groups: Vec<String>, |
| 153 | uninstall: bool, | ||
| 154 | |||
| 155 | paths: Vec<PathBuf>, | ||
| 156 | } | ||
| 157 | |||
| 158 | fn 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)] |
| 170 | struct InstallArgs { | 44 | struct FormatArgs {} |
| 171 | #[clap(long)] | ||
| 172 | directory: bool, | ||
| 173 | 45 | ||
| 174 | paths: Vec<PathBuf>, | 46 | #[derive(Parser, Debug)] |
| 47 | struct Args { | ||
| 48 | #[clap(flatten)] | ||
| 49 | globals: GlobalFlags, | ||
| 50 | #[clap(subcommand)] | ||
| 51 | command: SubCommand, | ||
| 175 | } | 52 | } |
| 176 | 53 | ||
| 177 | fn command_install(global_flags: Flags, args: InstallArgs) -> anyhow::Result<()> { | 54 | fn 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), |
| 189 | struct UninstallArgs { | 63 | } |
| 190 | paths: Vec<PathBuf>, | ||
| 191 | } | 64 | } |
| 192 | 65 | ||
| 193 | fn command_uninstall(global_flags: Flags, args: UninstallArgs) -> anyhow::Result<()> { | 66 | impl 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. | 74 | fn 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")?; |
| 201 | struct 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 | ||
| 208 | fn command_mv(global_flags: Flags, args: MvArgs) -> anyhow::Result<()> { | 90 | fn 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 | 106 | fn 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")?; |
| 217 | struct 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 | ||
| 222 | fn command_status(global_flags: Flags, args: StatusArgs) -> anyhow::Result<()> { | 122 | fn 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 | } |
