fix: allow more than one venv per deployment

This commit is contained in:
2026-08-20 09:33:43 +02:00
committed by perl_d
parent 7d14746d0d
commit 50569b1d37
3 changed files with 37 additions and 26 deletions
+19 -9
View File
@@ -56,11 +56,13 @@ pub struct Config {
}
impl Config {
/// Discover launchable deployments on disk under `deployments_base`, one
/// entry per (deployment, virtualenv) pair, labelled `"<deployment>/<venv>"`.
/// Discover launchable deployments on disk under `deployments_base`.
///
/// These are the "custom" deployments that are present on the machine but
/// not named in the config. Entry points use the configured defaults.
/// not named in the config; entry points use the configured defaults. Each
/// is labelled by its deployment name, with the virtualenv kept inside the
/// entry — only when a deployment has more than one virtualenv is the venv
/// name appended (`"<deployment>/<venv>"`) to keep the labels unique.
pub fn discovered_entries(&self) -> Vec<(String, LauncherEntry)> {
let mut found = Vec::new();
let Ok(dirs) = fs::read_dir(&self.deployments_base) else {
@@ -74,13 +76,21 @@ impl Config {
let dir = deployment.path();
let name = deployment.file_name().to_string_lossy().into_owned();
for venv in detect_venvs(&dir).unwrap_or_default() {
let venv_name = venv
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
let venvs = detect_venvs(&dir).unwrap_or_default();
let disambiguate = venvs.len() > 1;
for venv in venvs {
let label = if disambiguate {
let venv_name = venv
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
format!("{name}/{venv_name}")
} else {
name.clone()
};
found.push((
format!("{name}/{venv_name}"),
label,
LauncherEntry {
python: venv.join("bin/python"),
gui_file: join_relative(&dir, &self.default_gui_file),
-1
View File
@@ -83,7 +83,6 @@ fn main() -> Result<()> {
let mut config = load_config(&json)?;
check_base_exists("deployments_base", &config.deployments_base, &config_path)?;
check_base_exists("venvs_base", &config.deployments_base, &config_path)?;
// Populate the config with deployments found on disk so they are available
// to both `lookup_entry` and the TUI. Configured entries take precedence.
+18 -16
View File
@@ -27,7 +27,10 @@ pub struct EntriesTab {
}
impl EntriesTab {
pub fn new(entries: Vec<(String, LauncherEntry)>) -> Self {
pub fn new(mut entries: Vec<(String, LauncherEntry)>) -> Self {
// Production deployments first, then the rest, alphabetical within each.
entries.sort_by(|a, b| sort_key(&a.0).cmp(&sort_key(&b.0)));
let mut list_state = ListState::default();
if !entries.is_empty() {
list_state.select(Some(0));
@@ -117,7 +120,7 @@ impl EntriesTab {
let items: Vec<ListItem> = self
.entries
.iter()
.map(|(label, _)| ListItem::new(display_label(label)))
.map(|(label, _)| ListItem::new(label.as_str()))
.collect();
let list = List::new(items)
.block(Block::default().borders(Borders::ALL).title("Entries"))
@@ -168,10 +171,12 @@ impl EntriesTab {
}
}
/// The label shown in the list: the default `/.venv` virtualenv suffix (used by
/// discovered deployments) is dropped for readability.
fn display_label(label: &str) -> &str {
label.strip_suffix("/.venv").unwrap_or(label)
/// Production deployment labels, pinned to the top of the list.
const PRODUCTION: [&str; 3] = ["x06da", "x06sa", "x10sa"];
/// Sort key placing production deployments first, then alphabetical by label.
fn sort_key(label: &str) -> (bool, &str) {
(!PRODUCTION.contains(&label), label)
}
/// A rectangle centered within `area`, sized as a percentage of it.
@@ -187,20 +192,17 @@ fn centered_rect(width_pct: u16, height_pct: u16, area: Rect) -> Rect {
#[cfg(test)]
mod tests {
use super::display_label;
use super::sort_key;
#[test]
fn strips_default_venv_suffix() {
assert_eq!(display_label("mydeploy/.venv"), "mydeploy");
fn production_sorts_before_others() {
// Production wins even though its label sorts later alphabetically.
assert!(sort_key("x10sa") < sort_key("aaa"));
}
#[test]
fn keeps_named_venv() {
assert_eq!(display_label("mydeploy/pxii"), "mydeploy/pxii");
}
#[test]
fn leaves_plain_labels_untouched() {
assert_eq!(display_label("x10sa"), "x10sa");
fn production_ordered_alphabetically() {
assert!(sort_key("x06da") < sort_key("x06sa"));
assert!(sort_key("x06sa") < sort_key("x10sa"));
}
}