Feat/use installed config #13

Merged
perl_d merged 2 commits from feat/use_installed_config into main 2026-07-30 12:19:46 +02:00
4 changed files with 129 additions and 8 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"deployments_base": "/sls/mx/applications/aaredaq",
"deployments_base": "/sls/mx/applications/AareDAQ",
"default_venv_name": ".venv",
"default_gui_file": "src/aare/gui/gui.py",
"default_server_file": "src/aare/daq/server.py",
+49
View File
@@ -192,4 +192,53 @@ mod tests {
"expected field path in error, got: {message}"
);
}
#[test]
fn join_relative_keeps_relative_paths() {
assert_eq!(
join_relative(Path::new("/base"), Path::new("sub/x.py")),
PathBuf::from("/base/sub/x.py")
);
}
#[test]
fn join_relative_strips_leading_slash() {
assert_eq!(
join_relative(Path::new("/base"), Path::new("/sub/x.py")),
PathBuf::from("/base/sub/x.py")
);
}
#[test]
fn absolute_entry_points_resolve_relative_to_dir() {
// A leading slash on an entry point is treated as relative to `dir`,
// not as an absolute path.
let json = r#"{
"deployments_base": "/apps",
"default_venv_name": ".venv",
"default_gui_file": "src/gui.py",
"default_server_file": "src/server.py",
"map": { "x10sa": { "dir": "d", "gui_file": "/abs/gui.py" } }
}"#;
let config = load_config(json).unwrap();
assert_eq!(
config.entries["x10sa"].gui_file,
PathBuf::from("/apps/d/abs/gui.py")
);
}
#[test]
fn custom_venv_overrides_default() {
let config = load_config(SAMPLE).unwrap();
// x10sa sets "venv": "pxii"; x06da falls back to ".venv".
assert_eq!(
config.entries["x10sa"].python,
PathBuf::from("/apps/aaredaq-pxii/pxii/bin/python")
);
assert_eq!(
config.entries["x06da"].python,
PathBuf::from("/apps/aaredaq-pxiii/.venv/bin/python")
);
}
}
+28 -7
View File
@@ -10,17 +10,37 @@ use clap::{Parser, Subcommand};
use config::{Config, LauncherEntry, load_config};
use detect::detect_beamline;
/// Config file installed by the RPM, preferred when it exists.
const INSTALLED_CONFIG: &str = "/etc/aarel/defaults.json";
/// Config file shipped in the source tree, used as a fallback.
const LOCAL_CONFIG: &str = "data/defaults.json";
#[derive(Parser)]
#[command(name = "aarel")]
struct Cli {
/// Path to the launcher config file.
#[arg(long, default_value = "data/defaults.json")]
config: PathBuf,
/// Path to the launcher config file. Defaults to /etc/aarel/defaults.json,
/// falling back to ./data/defaults.json.
#[arg(long)]
config: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
/// Resolve the config path: the explicit `--config`, else the installed config
/// if present, else the local one.
fn resolve_config_path(config: Option<PathBuf>) -> PathBuf {
if let Some(path) = config {
return path;
}
let installed = PathBuf::from(INSTALLED_CONFIG);
if installed.is_file() {
installed
} else {
PathBuf::from(LOCAL_CONFIG)
}
}
#[derive(Subcommand)]
enum Command {
/// Enter the TUI to choose what to launch
@@ -45,12 +65,13 @@ enum Command {
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_path = resolve_config_path(cli.config);
let json = std::fs::read_to_string(&config_path)
.with_context(|| format!("failed to read config file {}", config_path.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)?;
check_base_exists("deployments_base", &config.deployments_base, &config_path)?;
check_base_exists("venvs_base", &config.deployments_base, &config_path)?;
match cli.command {
Command::Pick => {
+51
View File
@@ -155,3 +155,54 @@ fn dir_list<'a>(title: &'a str, items: &'a [String], focused: bool) -> List<'a>
.highlight_style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
.highlight_symbol("> ")
}
#[cfg(test)]
mod tests {
use super::*;
fn items(n: usize) -> Vec<String> {
(0..n).map(|i| i.to_string()).collect()
}
#[test]
fn initial_state_selects_first_when_nonempty() {
assert_eq!(initial_state(&items(3)).selected(), Some(0));
}
#[test]
fn initial_state_selects_nothing_when_empty() {
assert_eq!(initial_state(&items(0)).selected(), None);
}
#[test]
fn select_relative_moves_within_bounds() {
let items = items(3);
let mut state = initial_state(&items);
select_relative(&items, &mut state, 1);
assert_eq!(state.selected(), Some(1));
}
#[test]
fn select_relative_wraps_forward_past_end() {
let items = items(3);
let mut state = ListState::default();
state.select(Some(2));
select_relative(&items, &mut state, 1);
assert_eq!(state.selected(), Some(0));
}
#[test]
fn select_relative_wraps_backward_past_start() {
let items = items(3);
let mut state = initial_state(&items);
select_relative(&items, &mut state, -1);
assert_eq!(state.selected(), Some(2));
}
#[test]
fn select_relative_on_empty_is_noop() {
let mut state = ListState::default();
select_relative(&items(0), &mut state, 1);
assert_eq!(state.selected(), None);
}
}