add:list functionality

This commit is contained in:
kokopi-dev
2026-04-04 23:04:46 +09:00
parent 99a80f2f58
commit 36d2227a86
2 changed files with 120 additions and 4 deletions

View File

@@ -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
}

View File

@@ -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(&registry)
}
/// 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 = &registry.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(&registry)?;
}
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()?;