refactor(cli): move cmd_get/list/history/status/sync into commands/
This commit is contained in:
103
crates/relicario-cli/src/commands/list.rs
Normal file
103
crates/relicario-cli/src/commands/list.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
//! `relicario list` and `relicario history` — both read-only browse paths.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn cmd_list(
|
||||
type_filter: Option<String>,
|
||||
group_filter: Option<String>,
|
||||
tag_filter: Option<String>,
|
||||
trashed: bool,
|
||||
) -> Result<()> {
|
||||
use relicario_core::ItemType;
|
||||
|
||||
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||
let manifest = vault.load_manifest()?;
|
||||
crate::refresh_groups_cache(vault.root(), &manifest);
|
||||
|
||||
let parsed_type: Option<ItemType> = match type_filter.as_deref() {
|
||||
None => None,
|
||||
Some("login") => Some(ItemType::Login),
|
||||
Some("secure_note") | Some("note") => Some(ItemType::SecureNote),
|
||||
Some("identity") => Some(ItemType::Identity),
|
||||
Some("card") => Some(ItemType::Card),
|
||||
Some("key") => Some(ItemType::Key),
|
||||
Some("document") => Some(ItemType::Document),
|
||||
Some("totp") => Some(ItemType::Totp),
|
||||
Some(other) => anyhow::bail!("unknown type filter: {other}"),
|
||||
};
|
||||
|
||||
let mut entries: Vec<_> = manifest.items.values()
|
||||
.filter(|e| {
|
||||
if trashed { e.trashed_at.is_some() } else { e.trashed_at.is_none() }
|
||||
})
|
||||
.filter(|e| match parsed_type {
|
||||
Some(t) => e.r#type == t,
|
||||
None => true,
|
||||
})
|
||||
.filter(|e| group_filter.as_ref().is_none_or(|g| e.group.as_deref() == Some(g.as_str())))
|
||||
.filter(|e| tag_filter.as_ref().is_none_or(|t| e.tags.iter().any(|x| x == t)))
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase()));
|
||||
|
||||
if entries.is_empty() {
|
||||
eprintln!("(no items match)");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("{:<16} {:<14} {:<6} TITLE", "ID", "TYPE", "FAV");
|
||||
for e in entries {
|
||||
let fav = if e.favorite { " *" } else { "" };
|
||||
println!("{:<16} {:<14} {:<6} {}", e.id.as_str(), format!("{:?}", e.r#type), fav, e.title);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cmd_history(query: String, show: bool, field: Option<String>) -> Result<()> {
|
||||
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
||||
let manifest = vault.load_manifest()?;
|
||||
let entry = super::resolve_query(&manifest, &query)?;
|
||||
let item = vault.load_item(&entry.id)?;
|
||||
|
||||
println!("History for {} ({})", item.title, item.id.as_str());
|
||||
println!();
|
||||
|
||||
// Filter and sort the field-id keys so output is deterministic.
|
||||
let mut keys: Vec<&relicario_core::FieldId> = item.field_history.keys().collect();
|
||||
keys.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let mut printed_any = false;
|
||||
for fid in keys {
|
||||
let display_name = fid.0.strip_prefix("core:").unwrap_or(&fid.0);
|
||||
if let Some(filter) = &field {
|
||||
if display_name != filter && fid.0 != *filter { continue; }
|
||||
}
|
||||
let entries = &item.field_history[fid];
|
||||
if entries.is_empty() { continue; }
|
||||
printed_any = true;
|
||||
|
||||
println!("{display_name} ({} {})",
|
||||
entries.len(),
|
||||
if entries.len() == 1 { "entry" } else { "entries" });
|
||||
for (i, e) in entries.iter().enumerate() {
|
||||
let ts = crate::helpers::iso8601(e.replaced_at);
|
||||
if show {
|
||||
println!(" [{i}] {ts} {}", e.value.as_str());
|
||||
} else {
|
||||
println!(" [{i}] {ts} ********");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if !printed_any {
|
||||
if field.is_some() {
|
||||
println!("no history for the requested field");
|
||||
} else {
|
||||
println!("no history captured for this item");
|
||||
}
|
||||
} else if !show {
|
||||
println!("(use --show to reveal values)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user