Share tree output with search for more intuitive UI
Diff
onep-backend-op/src/lib.rs | 23 +++++++++++++++++------
onep-cli/src/main.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
2 files changed, 130 insertions(+), 92 deletions(-)
@@ -1,9 +1,8 @@
#![deny(clippy::pedantic)]
@@ -251,16 +250,26 @@
async fn search(&self, terms: Option<&str>) -> Result<Vec<api::ItemMetadata>, Self::Error> {
let ret: Vec<ListItem> = serde_json::from_slice(&exec(&["list", "items"]).await?)?;
let terms = terms.map(str::to_lowercase);
Ok(ret
.into_iter()
.filter(|v| {
if let Some(terms) = terms {
if let Some(terms) = &terms {
let terms = terms.as_ref();
v.uuid == terms
|| v.vault_uuid == terms
|| v.overview.urls.iter().any(|v| v.url.contains(terms))
|| v.overview.title.contains(terms)
|| v.overview.account_info.contains(terms)
|| v.overview.tags.iter().any(|v| v.contains(terms))
|| v.overview
.urls
.iter()
.any(|v| v.url.to_lowercase().contains(terms))
|| v.overview.title.to_lowercase().contains(terms)
|| v.overview.account_info.to_lowercase().contains(terms)
|| v.overview
.tags
.iter()
.any(|v| v.to_lowercase().contains(terms))
} else {
true
}
@@ -5,6 +5,7 @@
use itertools::Itertools;
use onep_backend_api as api;
use onep_backend_op as backend;
use std::collections::BTreeMap;
use term_table::{
row::Row,
table_cell::{Alignment, TableCell},
@@ -29,10 +30,16 @@
#[clap(long, short = 'n')]
show_account_names: bool,
},
Search {
#[clap(long, short = 'i')]
show_uuids: bool,
#[clap(long, short = 'n')]
show_account_names: bool,
terms: String,
},
Totp { uuid: String },
Search { terms: String },
#[clap(alias = "get")]
Show { uuid: String },
@@ -61,8 +68,7 @@
}
}
#[allow(clippy::non_ascii_literal)]
async fn run<T: api::Backend>(imp: &T) -> anyhow::Result<()>
async fn run<T: api::Backend>(backend: &T) -> anyhow::Result<()>
where
T::Error: 'static + std::error::Error + Send + Sync,
{
@@ -70,87 +76,15 @@
Opt::List {
show_uuids,
show_account_names,
} => {
let (account, vaults, results) =
tokio::try_join!(imp.account(), imp.vaults(), imp.search(None),)?;
let mut results_grouped: Vec<(_, Vec<_>)> = Vec::new();
for (key, group) in &results.into_iter().group_by(|v| v.vault_uuid.clone()) {
results_grouped.push((key, group.collect()));
}
println!("{} ({})", account.name, account.domain);
let vault_count = results_grouped.len() - 1;
for (current_vault_index, (vault, group)) in results_grouped.into_iter().enumerate() {
let vault = vaults
.iter()
.find(|v| v.uuid == vault)
.map_or_else(|| format!("Unknown Vault ({})", vault), |v| v.name.clone());
println!(
"{} {}",
if current_vault_index < vault_count {
"├──"
} else {
"└──"
},
vault.blue()
);
let line_start = if current_vault_index < vault_count {
"│"
} else {
" "
};
let item_count = group.len() - 1;
for (current_item_index, result) in group.into_iter().enumerate() {
println!(
"{} {} {}",
line_start,
if current_item_index < item_count {
"├──"
} else {
"└──"
},
result.title.trim()
);
let prefix = if current_item_index < item_count {
"│ "
} else {
" "
};
if show_account_names && !result.account_info.trim().is_empty() {
println!(
"{} {} {}",
line_start,
prefix,
result.account_info.trim().green()
);
}
if show_uuids {
println!("{} {} {}", line_start, prefix, result.uuid.yellow());
}
}
}
}
Opt::Totp { uuid } => println!("{}", imp.totp(&uuid).await?.trim()),
Opt::Search { terms } => {
for result in imp.search(Some(&terms)).await? {
println!("[{}]", result.title.green());
println!("{}", result.account_info);
println!("{}", result.uuid);
println!();
}
}
} => search(backend, None, show_uuids, show_account_names).await?,
Opt::Search {
terms,
show_uuids,
show_account_names,
} => search(backend, Some(terms), show_uuids, show_account_names).await?,
Opt::Totp { uuid } => println!("{}", backend.totp(&uuid).await?.trim()),
Opt::Show { uuid } => {
let result = imp.get(&uuid).await?.ok_or(Error::NotFound)?;
let result = backend.get(&uuid).await?.ok_or(Error::NotFound)?;
show(result);
}
Opt::Generate {
@@ -159,10 +93,105 @@
url,
tags,
} => {
let result = imp
let result = backend
.generate(&name, username.as_deref(), url.as_deref(), tags.as_deref())
.await?;
show(result);
}
}
Ok(())
}
#[allow(clippy::non_ascii_literal)]
async fn search<T: api::Backend>(
backend: &T,
terms: Option<String>,
show_uuids: bool,
show_account_names: bool,
) -> anyhow::Result<()>
where
T::Error: 'static + std::error::Error + Send + Sync,
{
let (account, vaults, results) = tokio::try_join!(
backend.account(),
backend.vaults(),
backend.search(terms.as_deref())
)?;
let mut results_grouped: BTreeMap<_, Vec<_>> = BTreeMap::new();
for (key, group) in &results.into_iter().group_by(|v| v.vault_uuid.clone()) {
results_grouped.insert(key, group.collect());
}
if let Some(terms) = terms {
if let Some(vault) = vaults
.iter()
.find(|v| v.name.to_lowercase() == terms.to_lowercase())
{
results_grouped.insert(vault.uuid.clone(), backend.search(Some(&vault.uuid)).await?);
}
}
println!("{} ({})", account.name, account.domain);
let vault_count = results_grouped.len() - 1;
for (current_vault_index, (vault, group)) in results_grouped.into_iter().enumerate() {
let vault = vaults
.iter()
.find(|v| v.uuid == vault)
.map_or_else(|| format!("Unknown Vault ({})", vault), |v| v.name.clone());
println!(
"{} {}",
if current_vault_index < vault_count {
"├──"
} else {
"└──"
},
vault.blue()
);
let line_start = if current_vault_index < vault_count {
"│"
} else {
" "
};
let item_count = group.len() - 1;
for (current_item_index, result) in group.into_iter().enumerate() {
println!(
"{} {} {}",
line_start,
if current_item_index < item_count {
"├──"
} else {
"└──"
},
result.title.trim()
);
let prefix = if current_item_index < item_count {
"│ "
} else {
" "
};
if show_account_names && !result.account_info.trim().is_empty() {
println!(
"{} {} {}",
line_start,
prefix,
result.account_info.trim().green()
);
}
if show_uuids {
println!("{} {} {}", line_start, prefix, result.uuid.yellow());
}
}
}