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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
config::{defaults::registry_file, types::{Registry, ScriptEntry}},
|
||||
config::{
|
||||
defaults::registry_file,
|
||||
types::{Registry, ScriptEntry},
|
||||
},
|
||||
error::AppError,
|
||||
};
|
||||
|
||||
@@ -45,6 +48,26 @@ impl RegistryService {
|
||||
self.save(®istry)
|
||||
}
|
||||
|
||||
/// Removes the entry with the given name and persists the registry.
|
||||
/// Also removes the shim and symlink files from disk if they exist.
|
||||
/// Does nothing if the name is not found.
|
||||
pub fn remove(&self, name: &str) -> Result<(), AppError> {
|
||||
let mut registry = self.load()?;
|
||||
|
||||
if let Some(pos) = registry.scripts.iter().position(|s| s.name == name) {
|
||||
let entry = ®istry.scripts[pos];
|
||||
|
||||
// Best-effort removal of generated files — don't fail if already gone
|
||||
let _ = fs::remove_file(&entry.shim_path);
|
||||
let _ = fs::remove_file(&entry.symlink_path);
|
||||
|
||||
registry.scripts.remove(pos);
|
||||
self.save(®istry)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if a script with the given name is already registered.
|
||||
pub fn name_exists(&self, name: &str) -> Result<bool, AppError> {
|
||||
let registry = self.load()?;
|
||||
|
||||
Reference in New Issue
Block a user