use std::io;
use anyhow::Result;
use clap::ArgMatches;
use prs_lib::{store::SecretIterConfig, Secret, Store};
use text_trees::{FormatCharacters, StringTreeNode, TreeFormatting};
use thiserror::Error;
use crate::cmd::matcher::{list::ListMatcher, MainMatcher, Matcher};
#[cfg(all(feature = "tomb", target_os = "linux"))]
use crate::util::tomb;
pub struct List<'a> {
cmd_matches: &'a ArgMatches,
}
impl<'a> List<'a> {
pub fn new(cmd_matches: &'a ArgMatches) -> Self {
Self { cmd_matches }
}
pub fn invoke(&self) -> Result<()> {
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_list = ListMatcher::with(self.cmd_matches).unwrap();
let store = Store::open(matcher_main.store()).map_err(Err::Store)?;
#[cfg(all(feature = "tomb", target_os = "linux"))]
let mut tomb = store.tomb(
!matcher_main.verbose(),
matcher_main.verbose(),
matcher_main.force(),
);
#[cfg(all(feature = "tomb", target_os = "linux"))]
tomb::prepare_tomb(&mut tomb, &matcher_main).map_err(Err::Tomb)?;
let config = SecretIterConfig {
find_files: !matcher_list.only_aliases(),
find_symlink_files: !matcher_list.only_non_aliases(),
};
let mut secrets: Vec<Secret> = store
.secret_iter_config(config)
.filter_name(matcher_list.query())
.collect();
secrets.sort_unstable_by(|a, b| a.name.cmp(&b.name));
if secrets.is_empty() {
return Ok(());
}
if matcher_list.list() {
secrets.iter().for_each(|s| println!("{}", s.name));
} else {
display_tree(&secrets);
}
#[cfg(all(feature = "tomb", target_os = "linux"))]
tomb::finalize_tomb(&mut tomb, &matcher_main, false).map_err(Err::Tomb)?;
Ok(())
}
}
fn display_tree(secrets: &[Secret]) {
let names: Vec<_> = secrets.iter().map(|s| s.name.as_str()).collect();
let nodes = tree_nodes("", &names);
StringTreeNode::with_child_nodes(".".into(), nodes.into_iter())
.write_with_format(
&mut io::stdout(),
&TreeFormatting::dir_tree(FormatCharacters::box_chars()),
)
.expect("failed to print tree list");
}
fn tree_nodes(prefix: &str, mut secrets: &[&str]) -> Vec<StringTreeNode> {
let mut nodes = vec![];
while !secrets.is_empty() {
let child_name = secrets[0]
.trim_start_matches(prefix)
.trim_start_matches('/')
.split('/')
.next()
.unwrap();
if child_name.trim().is_empty() {
return vec![];
}
let child_prefix = if prefix.is_empty() {
child_name.to_string()
} else {
format!("{}/{}", prefix, child_name)
};
let next_child_name = secrets[1..]
.iter()
.position(|s| {
s.trim_start_matches(prefix)
.trim_start_matches('/')
.split('/')
.next()
.unwrap()
!= child_name
})
.unwrap_or(secrets.len() - 1)
+ 1;
let (children, todo) = secrets.split_at(next_child_name);
secrets = todo;
nodes.push(StringTreeNode::with_child_nodes(
child_name.into(),
tree_nodes(&child_prefix, children).into_iter(),
));
}
nodes
}
#[derive(Debug, Error)]
pub enum Err {
#[error("failed to access password store")]
Store(#[source] anyhow::Error),
#[cfg(all(feature = "tomb", target_os = "linux"))]
#[error("failed to prepare password store tomb for usage")]
Tomb(#[source] anyhow::Error),
}