use std::{
fs::File,
io::{self, stdin, stdout, Read, Write},
};
use crate::{
gen_pass::{self, Mode},
output,
};
use clap::{command, CommandFactory, Parser};
use clap_complete::{generate, Shell};
const EXAMPLE_COMMANDS: &str = r#"Examples:
fpas hello
fpas -f path_file
"#;
#[derive(Parser, Default)]
#[command(
name = "fpas",
author, // Uses authors from Cargo.toml
version, // Uses version from Cargo.toml
about, // Uses description from Cargo.toml
long_about = None, // Uses `about` for long description as well
after_help = EXAMPLE_COMMANDS // Appends examples to the help message
)]
pub struct Cli {
pub msg: Option<String>,
#[clap(long)]
pub len: Option<u8>,
#[clap(short = 'f', long = "file", value_name = "file")]
pub file: Option<String>,
#[clap(short, long, default_value_t, value_enum)]
pub mode: Mode,
#[clap(long, short)]
pub completions: Option<Shell>,
#[clap(short = 'l', long = "loop", value_name = "COUNT")]
pub loop_count: Option<u32>,
#[clap(long, default_value_t = false)]
pub chain: bool,
#[clap(short, long, default_value_t = false)]
input: bool,
#[clap(long, default_value_t = false)]
license: bool,
}
#[derive(Debug)]
pub enum InputSource<'a> {
File(File),
Stdin(io::StdinLock<'a>),
}
pub fn read_source_to_string(source: InputSource, source_name: &str) -> String {
let mut buffer = Vec::new();
let read_result = match source {
InputSource::File(mut f) => f.read_to_end(&mut buffer),
InputSource::Stdin(mut s) => s.read_to_end(&mut buffer),
};
if let Err(e) = read_result {
eprintln!("Error: Failed to read from {}: {}", source_name, e);
std::process::exit(1);
}
let msg = buffer.iter().map(|f| *f as char).collect::<Vec<char>>();
String::from_iter(msg)
}
pub fn run() {
let mut cli = Cli::parse();
if cli.license {
let license = include_str!("../LICENSE");
print!("{}", license);
return;
}
if cli.input {
eprint!("> ");
let _ = stdout().flush();
let mut input = String::new();
let _ = stdin().read_line(&mut input);
let input = input.trim();
let commands = match shell_words::split(input) {
Ok(mut v) => {
v.insert(0, "fpas".to_string());
v
}
Err(_) => Vec::new(),
};
match Cli::try_parse_from(commands) {
Ok(v) => {
cli = v;
}
Err(err) => {
println!("{}", err);
return;
}
}
}
if let Some(shell) = cli.completions {
let mut cli_gen = Cli::command();
generate(shell, &mut cli_gen, "fpas", &mut io::stdout());
return;
}
let input_data: String = if let Some(message_arg) = cli.msg {
message_arg
} else if let Some(file_path_str) = cli.file {
match File::open(&file_path_str) {
Ok(file) => read_source_to_string(InputSource::File(file), &file_path_str),
Err(e) => {
eprintln!("Error: Could not open file '{}': {}", file_path_str, e);
std::process::exit(1);
}
}
} else {
let stdin = std::io::stdin();
let handle = stdin.lock();
read_source_to_string(InputSource::Stdin(handle), "stdin")
};
let iterations = cli.loop_count.unwrap_or(1);
let passwd = gen_pass::process(input_data, cli.mode, iterations, cli.chain);
output::print_passwd(&passwd, cli.len);
}