use anyhow::anyhow;
use clap::{Args, ValueEnum};
use copypasta::{ClipboardContext, ClipboardProvider};
use entrust_core::{Backend, generate_passphrase, generate_password};
use std::io::IsTerminal;
use std::path::{Path, PathBuf};
use std::{fs, io};
use crate::animation::animate;
use crate::command::{BackendValueEnum, clear_clipboard};
use entrust_core::git;
pub(crate) const ABOUT: &str = "Generate a random password";
#[derive(Args, Debug)]
pub struct GenerateArgs {
#[arg(default_value = "phrase")]
pub(super) r#type: Type,
#[arg(short, long)]
pub(super) clipboard: bool,
#[arg(short = 'd', long, default_value = "10")]
pub(super) clear_clipboard_delay: u32,
#[arg(short, long, value_name = "KEY")]
pub(super) store: Option<String>,
#[arg(short, long)]
length: Option<u8>,
#[arg(long = "sep", default_value = " ")]
pub(super) separator: String,
#[arg(short, long, value_enum, default_value_t = BackendValueEnum::Age)]
pub(super) backend: BackendValueEnum,
#[arg(short, long)]
pub(super) no_anim: bool,
#[arg(long)]
pub(super) no_git: bool,
}
#[derive(ValueEnum, Clone, Debug)]
pub(super) enum Type {
Phrase,
Word,
}
impl GenerateArgs {
pub(super) fn length(&self) -> u8 {
self.length.unwrap_or(match self.r#type {
Type::Phrase => 7,
Type::Word => 20,
})
}
pub(crate) fn needs_backend(&self) -> Option<Backend> {
if self.store.is_some() {
Some(self.backend.into())
} else {
None
}
}
}
pub fn run(store: PathBuf, args: GenerateArgs) -> anyhow::Result<()> {
let pass = match &args.r#type {
Type::Phrase => generate_passphrase(args.length(), &args.separator),
Type::Word => generate_password(args.length()),
};
output(&store, args, pass)?;
Ok(())
}
fn output(store: &Path, args: GenerateArgs, pass: String) -> anyhow::Result<()> {
if !args.clipboard && args.store.is_none() {
if !args.no_anim && io::stdout().is_terminal() {
animate(&pass);
} else if io::stdout().is_terminal() {
println!("{pass}");
} else {
print!("{pass}");
}
}
if let Some(key) = args.store.as_ref() {
let location = entrust_core::resolve_new_location(store, key)?;
if let Some(parent) = location.parent() {
fs::create_dir_all(parent)?;
}
Backend::from(args.backend).encrypt(pass.as_bytes(), store, &location)?;
if !args.no_git {
git::add(store, key)?;
}
}
if args.clipboard && args.clear_clipboard_delay > 0 {
copy_to_clipboard(pass, args.clear_clipboard_delay)?;
}
Ok(())
}
fn copy_to_clipboard(pass: String, clear_delay_seconds: u32) -> anyhow::Result<()> {
clear_clipboard::clear_in_new_process(pass.as_str(), clear_delay_seconds)?;
ClipboardContext::new()
.and_then(|mut ctx| ctx.set_contents(pass))
.map_err(|_| anyhow!("Could not access clipboard"))?;
Ok(())
}