use anyhow::{Context, Result, bail};
use colored::{Color, Colorize};
use smol::{Unblock, fs::*, io::AsyncWriteExt};
use std::{
env::home_dir,
io::{Error, ErrorKind, prelude::*, stderr, stdin, stdout},
path::PathBuf,
};
use willow25::prelude::*;
pub const SNEAKERWEB_NAMESPACE_ID_BYTES: [u8; NAMESPACE_ID_WIDTH] = [
159, 196, 204, 134, 202, 217, 77, 17, 2, 90, 252, 247, 94, 13, 171, 36, 188, 60, 108, 145, 240,
205, 146, 251, 224, 202, 87, 77, 70, 156, 104, 30,
];
pub const _SNEAKERWEB_SERVICE_NAME: &str = "org.worm-blossom.sneakerweb";
pub fn user_input<F>(message: &str, prompt_style: Option<F>) -> Result<Option<String>, Error>
where
F: Fn(&str) -> String,
{
println!("{}", message);
match prompt_style {
Some(style) => print!("{} ", style(">")),
None => print!("{} ", ">".bold()),
}
stdout().flush()?;
stdin().lines().next().transpose()
}
pub async fn sneakerweb_dir() -> Result<PathBuf, Error> {
let mut store_path = home_dir().ok_or(Error::new(
ErrorKind::NotFound,
"could not determine user home directory",
))?;
store_path.push(".sneakerweb");
create_dir_all(&store_path).await?;
Ok(store_path)
}
pub fn domain_style(message: &str) -> String {
format!("{}", message.bold().color(Color::Green))
}
pub fn secret_style(message: &str) -> String {
format!("{}", message.bold().color(Color::Magenta))
}
pub fn yay_style(message: &str) -> String {
format!("{}", message.bold().color(Color::Green))
}
pub fn oh_no_style(message: &str) -> String {
format!("{}", message.bold().color(Color::Magenta))
}
pub fn hmm_style(message: &str) -> String {
format!("{}", message.bold().color(Color::Yellow))
}
pub fn emph_style(message: &str) -> String {
format!("{}", message.bold())
}
pub async fn yay(message: &str) {
let mut stdout = Unblock::new(stdout());
stdout
.write_all(format!("{} {}\n", yay_style("Yay:"), message).as_bytes())
.await
.unwrap();
stdout.flush().await.unwrap();
}
pub async fn oh_no(message: &str) {
let mut stderr = Unblock::new(stderr());
stderr
.write_all(format!("{} {}\n", oh_no_style("Oh No:"), message).as_bytes())
.await
.unwrap();
stderr.flush().await.unwrap();
}
pub async fn hmm(message: &str) {
let mut stdout = Unblock::new(stdout());
stdout
.write_all(format!("{} {}\n", hmm_style("Hmm:"), message).as_bytes())
.await
.unwrap();
stdout.flush().await.unwrap();
}
pub fn get_domain(encoded: Option<&str>, action: &str) -> Result<(SubspaceId, String)> {
let domain = match encoded {
Some(domain) => domain.trim().to_owned(),
None => user_input(
&format!(
"Which {} do you want to {}?",
domain_style("domain"),
action
),
Some(domain_style),
)
.context("failed to retrieve user input")?
.unwrap_or_default(),
};
match base16::decode(&domain) {
Ok(decoded) if decoded.len() == 32 => Ok((
SubspaceId::from_bytes(decoded.as_array().expect("this is a vec of 32 bytes")),
domain,
)),
_ => bail!("invalid domain"),
}
}
pub fn get_secret(encoded: Option<&str>, domain: &str) -> Result<SubspaceSecret> {
let secret_string = match encoded {
Some(secret) => secret.to_owned(),
None => {
user_input(
&format!(
"Please provide the {} for {}:",
secret_style("secret key"),
domain_style(domain)
),
Some(secret_style),
)
.context("failed to retrieve user input")?
.unwrap_or_default()
}
};
match base16::decode(&secret_string) {
Ok(decoded) if decoded.len() == 32 => Ok(SubspaceSecret::from_bytes(
decoded.as_array().expect("this is a vec of 32 bytes"),
)),
_ => bail!("invalid secret"),
}
}