use anyhow::Result;
use dialoguer::{FuzzySelect, theme::ColorfulTheme};
use sefy_core::ItemSummary;
use std::io::IsTerminal;
pub fn available() -> bool {
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
}
pub fn choose(items: &[ItemSummary], prompt: &str) -> Result<Option<ItemSummary>> {
if items.is_empty() {
return Ok(None);
}
if let [only] = items {
return Ok(Some(only.clone()));
}
let labels: Vec<String> = items.iter().map(label).collect();
let picked = FuzzySelect::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
.items(&labels)
.default(0)
.max_length(15)
.interact_opt()?;
Ok(picked.map(|index| items[index].clone()))
}
fn label(item: &ItemSummary) -> String {
let mut line = item.title.clone();
line.push_str(" ");
line.push_str(item.kind.as_str());
if !item.tags.is_empty() {
line.push_str(" [");
line.push_str(&item.tags.join(", "));
line.push(']');
}
line
}
#[cfg(test)]
mod tests {
use super::*;
use sefy_core::ItemKind;
fn summary(title: &str, kind: ItemKind, tags: &[&str]) -> ItemSummary {
ItemSummary {
id: 1,
uuid: String::new(),
title: title.to_owned(),
kind,
tags: tags.iter().map(|tag| (*tag).to_owned()).collect(),
created_at: 0,
updated_at: 0,
}
}
#[test]
fn a_label_carries_what_tells_two_items_apart() {
assert_eq!(
label(&summary("mail", ItemKind::Login, &["work"])),
"mail login [work]"
);
assert_eq!(
label(&summary("mail", ItemKind::Login, &["home"])),
"mail login [home]"
);
assert_eq!(label(&summary("bank", ItemKind::Note, &[])), "bank note");
}
#[test]
fn an_empty_list_is_no_choice_rather_than_a_prompt() {
assert!(choose(&[], "pick").unwrap().is_none());
}
#[test]
fn a_single_item_is_taken_without_asking() {
let only = summary("mail", ItemKind::Login, &[]);
let picked = choose(std::slice::from_ref(&only), "pick").unwrap();
assert_eq!(picked.map(|item| item.title), Some("mail".to_owned()));
}
}