add:list functionality
This commit is contained in:
@@ -1,6 +1,99 @@
|
||||
use std::{os::unix::process::CommandExt, process::Command};
|
||||
|
||||
use cliclack::{confirm, log, note, outro, select};
|
||||
|
||||
use crate::{error::AppError, services::ServiceStore};
|
||||
|
||||
pub fn run(_store: &ServiceStore) -> Result<(), AppError> {
|
||||
// TODO: load registry, display all scripts in a cliclack table/select
|
||||
Ok(())
|
||||
/// Health of a registered script's files on disk.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
enum Health {
|
||||
Ok,
|
||||
Broken,
|
||||
}
|
||||
|
||||
pub fn run(store: &ServiceStore) -> Result<(), AppError> {
|
||||
let registry = store.registry.load()?;
|
||||
|
||||
if registry.scripts.is_empty() {
|
||||
note(
|
||||
"No scripts registered",
|
||||
"Use 'Add' to register your first script.",
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Annotate each entry with its health before building the select items
|
||||
let annotated: Vec<(&crate::config::types::ScriptEntry, Health)> = registry
|
||||
.scripts
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let health = if is_healthy(entry) {
|
||||
Health::Ok
|
||||
} else {
|
||||
Health::Broken
|
||||
};
|
||||
(entry, health)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// ── Script selection ───────────────────────────────────────────────────────
|
||||
// Use the script name as the select value — simple, Clone+Eq for free.
|
||||
let selected_name: String = {
|
||||
let mut prompt = select("Which script?");
|
||||
for (entry, health) in &annotated {
|
||||
let label = match health {
|
||||
Health::Ok => entry.name.clone(),
|
||||
Health::Broken => format!("⚠ {}", entry.name),
|
||||
};
|
||||
prompt = prompt.item(entry.name.clone(), label, &entry.description);
|
||||
}
|
||||
prompt.interact()?
|
||||
};
|
||||
|
||||
// Look up the full entry and its health by name
|
||||
let (entry, health) = annotated
|
||||
.into_iter()
|
||||
.find(|(e, _)| e.name == selected_name)
|
||||
.expect("selected name must exist in annotated list");
|
||||
|
||||
// ── Broken entry handling ──────────────────────────────────────────────────
|
||||
if health == Health::Broken {
|
||||
log::warning(format!(
|
||||
"'{}' is broken — shim or symlink is missing.\n Source: {}",
|
||||
entry.name,
|
||||
entry.source_path.display(),
|
||||
))?;
|
||||
|
||||
let remove = confirm("Remove this broken entry from the registry?").interact()?;
|
||||
if remove {
|
||||
store.registry.remove(&entry.name)?;
|
||||
log::success(format!("'{}' removed from registry.", entry.name))?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// ── Healthy entry: exec shim --help, replacing this process ───────────────
|
||||
outro(format!("Running {} --help...", entry.name))?;
|
||||
|
||||
let err = Command::new(&entry.shim_path).arg("--help").exec();
|
||||
|
||||
// exec() only returns on failure
|
||||
Err(AppError::Io(err))
|
||||
}
|
||||
|
||||
/// Returns true if both the shim and the symlink target are present on disk.
|
||||
fn is_healthy(entry: &crate::config::types::ScriptEntry) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let shim_ok = entry
|
||||
.shim_path
|
||||
.metadata()
|
||||
.map(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
|
||||
.unwrap_or(false);
|
||||
|
||||
// Follow the symlink — if the target is gone, symlink_metadata succeeds but
|
||||
// metadata() (which follows links) will fail
|
||||
let symlink_ok = entry.symlink_path.metadata().map(|m| m.is_file()).unwrap_or(false);
|
||||
|
||||
shim_ok && symlink_ok
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user