/ src / main.rs
main.rs
  1  #![feature(let_chains)]
  2  
  3  mod add;
  4  mod config;
  5  mod debug;
  6  mod import;
  7  mod list;
  8  mod remove;
  9  mod util;
 10  
 11  use anyhow::Result;
 12  use clap::{Parser, Subcommand};
 13  use std::{
 14      io::{self, ErrorKind},
 15      path::PathBuf,
 16      sync::OnceLock,
 17  };
 18  use util::rerun_with_root;
 19  
 20  #[derive(Parser, Debug)]
 21  #[command(name = "dots")]
 22  struct Cli {
 23      #[arg(short, long)]
 24      /// Only output the found items
 25      silent: bool,
 26      #[command(subcommand)]
 27      command: Commands,
 28  }
 29  
 30  #[derive(Subcommand, Debug)]
 31  enum Commands {
 32      /// Add the given path to the system
 33      #[command(arg_required_else_help = true)]
 34      Add {
 35          /// Format: (sub-dir of ~/.config/rebos/files)/(path to symlink).
 36          /// If the path is absolute, it is automatically prepended with <`DEFAULT_SUBDIR`>.
 37          /// "{hostname}" can be used as a placeholder for the actual hostname of the system.
 38          /// "{home}" can be used as a placeholder for the home dir of the user.
 39          path: PathBuf,
 40  
 41          #[arg(long)]
 42          /// Copy instead of symlink the path
 43          copy: bool,
 44  
 45          #[arg(short, long)]
 46          /// Overwrite the destination without asking
 47          force: bool,
 48      },
 49      /// Remove the given path from the system (does not remove the files the path points to, only the symlink)
 50      #[command(arg_required_else_help = true)]
 51      Remove {
 52          /// Format: (sub-dir of ~/.config/rebos/files}/{path to symlink)
 53          /// If the path is absolute, it is assumed to already be the path to remove.
 54          /// "{hostname}" can be used as a placeholder for the actual hostname of the system.
 55          /// "{home}" can be used as a placeholder for the home dir of the user.
 56          path: PathBuf,
 57      },
 58      /// Import the given path from the system
 59      #[command(arg_required_else_help = true)]
 60      Import {
 61          /// Format: (sub-dir of ~/.config/rebos/files)/(path to symlink).
 62          /// If the path is absolute, it is automatically prepended with <`DEFAULT_SUBDIR`>.
 63          /// "{hostname}" can be used as a placeholder for the actual hostname of the system.
 64          /// "{home}" can be used as a placeholder for the home dir of the user.
 65          path: PathBuf,
 66  
 67          #[arg(long)]
 68          /// Copy instead of symlink the path
 69          copy: bool,
 70      },
 71      /// Outputs a list of all symlinks on the system that are probably made by dots
 72      List {
 73          #[arg(short, long)]
 74          /// Assume that the current user is root
 75          rooted: bool,
 76  
 77          #[arg(long, trailing_var_arg = true, num_args(1..))]
 78          copy: Option<Vec<String>>,
 79      },
 80      /// Debugging commands
 81      #[command(subcommand)]
 82      Debug(DebugCommands),
 83      /// Interactively creates the config file
 84      Config,
 85  }
 86  
 87  #[derive(Subcommand, Debug)]
 88  enum DebugCommands {
 89      /// Print the config path of the given path
 90      ConfigPath {
 91          /// Format: (sub-dir of ~/.config/rebos/files)/(path to symlink).
 92          /// If the path is absolute, it is automatically prepended with <`DEFAULT_SUBDIR`>.
 93          /// "{hostname}" can be used as a placeholder for the actual hostname of the system.
 94          /// "{home}" can be used as a placeholder for the home dir of the user.
 95          path: PathBuf,
 96      },
 97      /// Print the system path of the given path
 98      SystemPath {
 99          /// Format: (sub-dir of ~/.config/rebos/files)/(path to symlink).
100          /// If the path is absolute, it is automatically prepended with <`DEFAULT_SUBDIR`>.
101          /// "{hostname}" can be used as a placeholder for the actual hostname of the system.
102          /// "{home}" can be used as a placeholder for the home dir of the user.
103          path: PathBuf,
104      },
105  }
106  
107  static SILENT: OnceLock<bool> = OnceLock::new();
108  
109  #[expect(clippy::expect_used)]
110  fn main() -> Result<()> {
111      let args = Cli::parse();
112  
113      SILENT
114          .set(args.silent)
115          .expect("SILENT shouldnt be already initialized");
116  
117      match args.command {
118          Commands::Add { path, force, copy } => add::add(&path, force, copy),
119          Commands::Remove { path } => remove::remove(&path),
120          Commands::Import { path, copy } => import::import(&path, copy),
121          Commands::List { rooted, copy } => list::list(rooted, copy),
122          Commands::Debug(debug_command) => debug::debug(debug_command),
123          Commands::Config => config::Config::setup(),
124      }
125      .inspect_err(|error| {
126          if let Some(io_error) = error.root_cause().downcast_ref::<io::Error>() {
127              if io_error.kind() == ErrorKind::PermissionDenied {
128                  rerun_with_root(&format!("{error}")); // just formatting the error should be fine for handling context
129              }
130          }
131      })
132  }