mod config; mod detect; mod launcher; mod tui; use std::path::{Path, PathBuf}; use anyhow::{Context, Error, Result, bail}; use clap::{Parser, Subcommand}; use config::{Config, LauncherEntry, load_config}; use detect::detect_beamline; #[derive(Parser)] #[command(name = "aarel")] struct Cli { /// Path to the launcher config file. #[arg(long, default_value = "data/defaults.json")] config: PathBuf, #[command(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { /// Enter the TUI to choose what to launch #[command(alias = "p")] Pick, /// Launch the AareGUI with default settings #[command(alias = "g")] Gui { /// Config entry (beamline) to launch. Defaults to the detected beamline. #[arg(long)] entry: Option, }, /// Launch the AareDAQ server with default settings #[command(alias = "s")] Server { /// Config entry (beamline) to launch. Defaults to the detected beamline. #[arg(long)] entry: Option, }, } fn main() -> Result<()> { let cli = Cli::parse(); let json = std::fs::read_to_string(&cli.config) .with_context(|| format!("failed to read config file {}", cli.config.display()))?; let config = load_config(&json)?; check_base_exists("deployments_base", &config.deployments_base, &cli.config)?; check_base_exists("venvs_base", &config.deployments_base, &cli.config)?; match cli.command { Command::Pick => { if let Some((python, entrypoint)) = tui::run(&config)? { launcher::launch(&python, &entrypoint)?; } } Command::Gui { entry } => launcher::launch_gui(lookup_entry(&config, entry)?)?, Command::Server { entry } => launcher::launch_server(lookup_entry(&config, entry)?)?, } Ok(()) } /// Fail with a helpful message if a configured base directory is missing. fn check_base_exists(field: &str, base: &Path, config_file: &Path) -> Result<()> { if !base.is_dir() { bail!( "{field} '{}' does not exist — edit '{field}' in {} to point at a valid directory", base.display(), config_file.display(), ); } Ok(()) } /// Look up the launcher entry to use: the explicitly requested `entry`, or the /// detected beamline when none was given. fn lookup_entry(config: &Config, entry: Option) -> Result<&LauncherEntry> { let beamline = match entry { Some(entry) => entry, None => detect_beamline().map_err(Error::msg)?, }; config .entries .get(&beamline) .with_context(|| format!("no config entry for beamline '{beamline}'")) }