use clap::{Parser, Subcommand}; use std::path::PathBuf; use std::process::Command; mod platform; mod config; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Register the deep link to execute this app. Register, /// Unregister the deep link. Unregister, /// Open a file explorer to the given path. Open { path: String }, /// Set the file explorer and base path. /// The explorer must be an executable in your PATH. Setting { #[arg(short, long)] explorer: Option, #[arg(short, long)] base_path: Option, }, } fn main() { let cli = Cli::parse(); let result = match &cli.command { Commands::Register => platform::handle_register(), Commands::Unregister => platform::handle_unregister(), Commands::Open { path } => handle_open(path), Commands::Setting { explorer, base_path } => handle_setting(explorer, base_path), }; if let Err(e) = result { eprintln!("Error: {}", e); } } fn handle_open(path: &str) -> Result<(), String> { let config = config::load_config().unwrap_or_else(|_| config::Config { explorer: None, base_path: None, }); let path_without_scheme = path.strip_prefix("ionian-find:").unwrap_or(path); let relative_path = path_without_scheme.trim_start_matches('/'); let mut full_path = PathBuf::new(); if let Some(base_path) = &config.base_path { full_path.push(shellexpand::tilde(base_path).as_ref()); } full_path.push(relative_path); let result = full_path.canonicalize() .expect("Failed to canonicalize path") .to_str() .ok_or("Failed to convert path to string")? .to_string(); println!("Opening the file explorer to: {:?}", result); let explorer = config.explorer.unwrap_or_else(|| "xdg-open".to_string()); Command::new(explorer) .arg(result) .output() .map_err(|e| format!("Failed to execute file explorer: {}", e))?; Ok(()) } fn handle_setting(explorer: &Option, base_path: &Option) -> Result<(), String> { let mut config = config::load_config().unwrap_or_else(|_| config::Config { explorer: None, base_path: None, }); // config should be saved only if at least one setting is provided // If no settings are provided, print the current configuration // and do not save the config. // TODO: config guard like ```rust // defer { // config::save_config(&config); // }``` // This is possible with the `defer` crate, but not in the standard library. // https://crates.io/crates/defer let mut is_set = false; if let Some(explorer) = explorer { println!("Setting the file explorer to: {}", explorer); config.explorer = Some(explorer.clone()); is_set = true; } if let Some(base_path) = base_path { println!("Setting the base path to: {}", base_path); config.base_path = Some(base_path.clone()); is_set = true; } if !is_set { // If no settings were provided, print the current configuration println!("Current configuration:"); println!("Explorer: {:?}", config.explorer.unwrap_or("Not set".into())); println!("Base Path: {:?}", config.base_path.unwrap_or("Not set".into())); return Ok(()); } else { config::save_config(&config) } }