Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 70 additions & 6 deletions src/cli/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anyhow::{Context, Result};
use colored::Colorize;
use console::Term;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
use std::path::PathBuf;
use std::path::{Path, PathBuf};

/// Interactive session state
pub struct InteractiveSession {
Expand Down Expand Up @@ -101,21 +101,55 @@ impl InteractiveSession {
/// Prompt for playbook selection
pub fn select_playbook(&self, playbooks: &[PathBuf]) -> Result<Option<PathBuf>> {
if playbooks.is_empty() {
println!("{}", "No playbooks found in current directory.".yellow());
return Ok(None);
println!(
"{}",
"No playbooks found. Please enter path manually.".yellow()
);
let custom: String = Input::with_theme(&self.theme)
.with_prompt("✏️ Enter playbook path")
.validate_with(|input: &String| -> Result<(), &str> {
let expanded = shellexpand::tilde(input);
let path = Path::new(expanded.as_ref());
if path.exists() {
Ok(())
Comment on lines +113 to +114

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject directories in custom playbook validation

The new custom playbook validator accepts any existing path via Path::exists(), which also returns true for directories; this lets users submit a directory from interactive mode and only fail later when playbook loading/parsing runs, so the prompt no longer provides the immediate validation it intends to provide. This is reproducible whenever the custom path points to an existing directory (for example . or playbooks/).

Useful? React with 👍 / 👎.

} else {
Err("Path does not exist")
}
})
.interact_on(&self.term)?;
let expanded = shellexpand::tilde(&custom);
return Ok(Some(PathBuf::from(expanded.as_ref())));
}

let items: Vec<String> = playbooks
let mut items: Vec<String> = playbooks
.iter()
.map(|p| format!("📖 {}", p.display()))
.collect();
items.push("✏️ Enter custom path...".to_string());

let selection = Select::with_theme(&self.theme)
.with_prompt("📖 Select a playbook")
.items(&items)
.default(0)
.interact_on(&self.term)?;

if selection == items.len() - 1 {
let custom: String = Input::with_theme(&self.theme)
.with_prompt("✏️ Enter playbook path")
.validate_with(|input: &String| -> Result<(), &str> {
let expanded = shellexpand::tilde(input);
let path = Path::new(expanded.as_ref());
if path.exists() {
Ok(())
} else {
Err("Path does not exist")
}
})
.interact_on(&self.term)?;
let expanded = shellexpand::tilde(&custom);
return Ok(Some(PathBuf::from(expanded.as_ref())));
}

Ok(Some(playbooks[selection].clone()))
}

Expand All @@ -125,12 +159,25 @@ impl InteractiveSession {
let custom: String = Input::with_theme(&self.theme)
.with_prompt("✏️ Enter inventory path (or 'localhost' for local)")
.default("localhost".to_string())
.validate_with(|input: &String| -> Result<(), &str> {
if input == "localhost" {
return Ok(());
}
let expanded = shellexpand::tilde(input);
let path = Path::new(expanded.as_ref());
if path.exists() {
Ok(())
} else {
Err("Path does not exist")
}
})
.interact_on(&self.term)?;

if custom == "localhost" {
return Ok(None);
}
return Ok(Some(PathBuf::from(custom)));
let expanded = shellexpand::tilde(&custom);
return Ok(Some(PathBuf::from(expanded.as_ref())));
}

let mut items: Vec<String> = inventories
Expand All @@ -153,8 +200,25 @@ impl InteractiveSession {
if selection == items.len() - 2 {
let custom: String = Input::with_theme(&self.theme)
.with_prompt("✏️ Enter inventory path")
.validate_with(|input: &String| -> Result<(), &str> {
if input == "localhost" {
return Ok(());
}
let expanded = shellexpand::tilde(input);
let path = Path::new(expanded.as_ref());
if path.exists() {
Ok(())
} else {
Err("Path does not exist")
}
})
.interact_on(&self.term)?;
return Ok(Some(PathBuf::from(custom)));

if custom == "localhost" {
return Ok(None);
}
let expanded = shellexpand::tilde(&custom);
return Ok(Some(PathBuf::from(expanded.as_ref())));
}

Ok(Some(inventories[selection].clone()))
Expand Down
Loading