use dialoguer::{Input, Select};
use crate::error::{Result, ScoopError};
use crate::output::Output;
pub const MAX_RENAME_ATTEMPTS: usize = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictResolution {
Overwrite,
Rename,
Skip,
}
pub fn prompt_conflict_resolution(
output: &Output,
name: &str,
existing: &std::path::Path,
) -> Result<ConflictResolution> {
output.warn(&format!(
"Name conflict: '{}' already exists at {}",
name,
existing.display()
));
let options = &[
"Overwrite - Delete existing and migrate fresh",
"Rename - Migrate with a different name",
"Skip - Don't migrate this environment",
];
let selection = Select::new()
.with_prompt("How would you like to resolve this conflict?")
.items(options)
.default(2) .interact()
.map_err(|e| ScoopError::Io(std::io::Error::other(format!("Dialog error: {}", e))))?;
Ok(match selection {
0 => ConflictResolution::Overwrite,
1 => ConflictResolution::Rename,
_ => ConflictResolution::Skip,
})
}
pub fn prompt_rename(name: &str) -> Result<String> {
let suggested = format!("{}-pyenv", name);
let new_name: String = Input::new()
.with_prompt("Enter new name for the environment")
.default(suggested)
.validate_with(|input: &String| {
crate::validate::validate_env_name(input)
.map(|_| ())
.map_err(|e| e.to_string())
})
.interact_text()
.map_err(|e| ScoopError::Io(std::io::Error::other(format!("Dialog error: {}", e))))?;
Ok(new_name)
}
pub fn generate_unique_name(base_name: &str) -> Result<String> {
let first_try = format!("{}-pyenv", base_name);
if !crate::paths::virtualenv_path(&first_try)?.exists() {
return Ok(first_try);
}
for i in 1..MAX_RENAME_ATTEMPTS {
let candidate = format!("{}-{}", base_name, i);
if !crate::paths::virtualenv_path(&candidate)?.exists() {
return Ok(candidate);
}
}
Err(ScoopError::MigrationFailed {
reason: format!("Could not find unique name for '{}'", base_name),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::with_temp_scoop_home;
use serial_test::serial;
use std::fs;
#[test]
#[serial]
fn test_generate_unique_name_no_conflict() {
with_temp_scoop_home(|_temp| {
let result = generate_unique_name("myenv");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "myenv-pyenv");
});
}
#[test]
#[serial]
fn test_generate_unique_name_first_collision() {
with_temp_scoop_home(|temp| {
let existing = temp.path().join("virtualenvs").join("myenv-pyenv");
fs::create_dir_all(&existing).expect("Failed to create existing dir");
let result = generate_unique_name("myenv");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "myenv-1");
});
}
#[test]
#[serial]
fn test_generate_unique_name_multiple_collisions() {
with_temp_scoop_home(|temp| {
let venvs_dir = temp.path().join("virtualenvs");
fs::create_dir_all(&venvs_dir).unwrap();
fs::create_dir_all(venvs_dir.join("myenv-pyenv")).unwrap();
fs::create_dir_all(venvs_dir.join("myenv-1")).unwrap();
fs::create_dir_all(venvs_dir.join("myenv-2")).unwrap();
let result = generate_unique_name("myenv");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "myenv-3");
});
}
#[test]
#[serial]
fn test_generate_unique_name_max_attempts_exceeded() {
with_temp_scoop_home(|temp| {
let venvs_dir = temp.path().join("virtualenvs");
fs::create_dir_all(&venvs_dir).unwrap();
fs::create_dir_all(venvs_dir.join("myenv-pyenv")).unwrap();
for i in 1..MAX_RENAME_ATTEMPTS {
fs::create_dir_all(venvs_dir.join(format!("myenv-{}", i))).unwrap();
}
let result = generate_unique_name("myenv");
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("myenv") || err_msg.contains("unique name"),
"Error should mention the base name or unique name: {}",
err_msg
);
});
}
#[test]
#[serial]
fn test_generate_unique_name_empty_base() {
with_temp_scoop_home(|_temp| {
let result = generate_unique_name("");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "-pyenv");
});
}
#[test]
#[serial]
fn test_generate_unique_name_with_special_chars() {
with_temp_scoop_home(|_temp| {
let result = generate_unique_name("my-env_test");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "my-env_test-pyenv");
});
}
#[test]
fn test_conflict_resolution_enum_variants() {
let overwrite = ConflictResolution::Overwrite;
let rename = ConflictResolution::Rename;
let skip = ConflictResolution::Skip;
let cloned = overwrite;
assert_eq!(cloned, overwrite);
assert_ne!(overwrite, rename);
assert_ne!(rename, skip);
assert_ne!(overwrite, skip);
let debug_str = format!("{:?}", overwrite);
assert!(debug_str.contains("Overwrite"));
}
#[test]
fn test_max_rename_attempts_constant() {
assert_eq!(MAX_RENAME_ATTEMPTS, 100);
}
}