update:ui qol
This commit is contained in:
@@ -9,8 +9,8 @@ use crate::{error::AppError, services::ServiceStore};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
enum MenuAction {
|
||||
Add,
|
||||
List,
|
||||
Add,
|
||||
Edit,
|
||||
Remove,
|
||||
}
|
||||
@@ -19,15 +19,15 @@ pub fn show_main_menu(store: &ServiceStore) -> Result<(), AppError> {
|
||||
intro("scripts-organizer")?;
|
||||
|
||||
let action = select("What would you like to do?")
|
||||
.item(MenuAction::Add, "Add", "register a new script")
|
||||
.item(MenuAction::List, "List", "view and run registered scripts")
|
||||
.item(MenuAction::Add, "Add", "register a new script")
|
||||
.item(MenuAction::Edit, "Edit", "update a registered script's settings")
|
||||
.item(MenuAction::Remove, "Remove", "unregister a script")
|
||||
.interact()?;
|
||||
|
||||
match action {
|
||||
MenuAction::Add => add::run(store)?,
|
||||
MenuAction::List => list::run(store)?,
|
||||
MenuAction::Add => add::run(store)?,
|
||||
MenuAction::Edit => edit::run(store)?,
|
||||
MenuAction::Remove => remove::run(store)?,
|
||||
}
|
||||
|
||||
@@ -3,17 +3,25 @@ use std::{os::unix::process::CommandExt, process::Command};
|
||||
use cliclack::{confirm, log, note, outro, select};
|
||||
|
||||
use crate::{
|
||||
config::types::ScriptEntry,
|
||||
error::AppError,
|
||||
services::{registry_service::is_healthy, ServiceStore},
|
||||
};
|
||||
|
||||
/// Health of a registered script's files on disk.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
enum Health {
|
||||
Ok,
|
||||
Broken,
|
||||
}
|
||||
|
||||
/// Select value — either a real script name or a group header sentinel.
|
||||
/// Headers are re-prompted if accidentally selected.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
enum Item {
|
||||
Script(String),
|
||||
Header(String),
|
||||
}
|
||||
|
||||
pub fn run(store: &ServiceStore) -> Result<(), AppError> {
|
||||
let registry = store.registry.load()?;
|
||||
|
||||
@@ -25,33 +33,74 @@ pub fn run(store: &ServiceStore) -> Result<(), AppError> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Annotate each entry with its health before building the select items
|
||||
let annotated: Vec<(&crate::config::types::ScriptEntry, Health)> = registry
|
||||
// ── Group by primary tag, sort within each group by name ──────────────────
|
||||
let mut scripts_with_health: Vec<(&ScriptEntry, Health)> = registry
|
||||
.scripts
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let health = if is_healthy(entry) { Health::Ok } else { Health::Broken };
|
||||
(entry, health)
|
||||
.map(|e| {
|
||||
let health = if is_healthy(e) { Health::Ok } else { Health::Broken };
|
||||
(e, health)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// ── Script selection ───────────────────────────────────────────────────────
|
||||
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);
|
||||
scripts_with_health.sort_by(|(a, _), (b, _)| {
|
||||
let tag_a = a.tags.first().map(String::as_str).unwrap_or("untagged");
|
||||
let tag_b = b.tags.first().map(String::as_str).unwrap_or("untagged");
|
||||
tag_a.cmp(tag_b).then(a.name.cmp(&b.name))
|
||||
});
|
||||
|
||||
// Collect into (group_label, entries) pairs
|
||||
let mut groups: Vec<(String, Vec<(&ScriptEntry, Health)>)> = Vec::new();
|
||||
for (entry, health) in scripts_with_health {
|
||||
let group_key = if entry.tags.is_empty() {
|
||||
"untagged".to_string()
|
||||
} else {
|
||||
// Header shows all tags: [git, config]
|
||||
format!("[{}]", entry.tags.join(", "))
|
||||
};
|
||||
|
||||
match groups.last_mut() {
|
||||
Some((key, entries)) if key == &group_key => entries.push((entry, health)),
|
||||
_ => groups.push((group_key, vec![(entry, health)])),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Build select — headers + indented script names ─────────────────────────
|
||||
let selected_name = loop {
|
||||
let mut prompt = select("Which script?");
|
||||
|
||||
for (group_label, entries) in &groups {
|
||||
prompt = prompt.item(Item::Header(group_label.clone()), group_label, "");
|
||||
|
||||
for (entry, health) in entries {
|
||||
let name = match health {
|
||||
Health::Ok => format!(" * {}", entry.name),
|
||||
Health::Broken => format!(" * ⚠ {}", entry.name),
|
||||
};
|
||||
prompt = prompt.item(
|
||||
Item::Script(entry.name.clone()),
|
||||
name,
|
||||
&entry.description,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match prompt.interact()? {
|
||||
Item::Script(name) => break name,
|
||||
Item::Header(_) => continue,
|
||||
}
|
||||
prompt.interact()?
|
||||
};
|
||||
|
||||
let (entry, health) = annotated
|
||||
.into_iter()
|
||||
// ── Look up the selected entry ─────────────────────────────────────────────
|
||||
let (entry, health) = registry
|
||||
.scripts
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let health = if is_healthy(e) { Health::Ok } else { Health::Broken };
|
||||
(e, health)
|
||||
})
|
||||
.find(|(e, _)| e.name == selected_name)
|
||||
.expect("selected name must exist in annotated list");
|
||||
.expect("selected name must exist in registry");
|
||||
|
||||
// ── Broken entry handling ──────────────────────────────────────────────────
|
||||
if health == Health::Broken {
|
||||
@@ -69,11 +118,10 @@ pub fn run(store: &ServiceStore) -> Result<(), AppError> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// ── Healthy entry: exec shim --help, replacing this process ───────────────
|
||||
// ── Healthy: 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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user