mod storage; mod editor; use storage::{Storage, Item}; use clap::{Parser, Subcommand}; use rpassword; use std::io::{self, Write}; use std::process; #[derive(Parser)] #[command(name = "mps", version = "0.0.1", about = "MyPasswordStorage: Tool for storing your passwords locally with git synchronization")] struct Cli { #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { /// Initialization of storage and config, use this in first time of usage of mps Init, /// Lists all ids stored in db List, /// Show content of item Show { #[arg(value_name="item_id")] id: String }, /// Adds new item with unique id to the db Add { #[arg(value_name="item_id")] id: String }, /// Edit item content Edit { #[arg(value_name="item_id")] id: String } } enum PROMPT { YES, NO } fn get_prompt(question: &str) -> io::Result { print!("{} [Y/n] ", question); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; let input = input.trim().to_lowercase(); match input.as_str() { "y" => Ok(PROMPT::YES), "yes" => Ok(PROMPT::YES), "n" => Ok(PROMPT::NO), "no" => Ok(PROMPT::NO), _ => Ok(PROMPT::YES), } } // TODO: change password functionality // TODO: return storage inited fn login() -> io::Result { print!("Enter passphrase for storage: "); io::stdout().flush()?; // TODO: check in db // TODO: return error if db is not inited let password = rpassword::read_password()?; if password != "pass" { return Err(io::Error::new(io::ErrorKind::InvalidData, "Wrong passphrase")); } print!("\x1B[1A\x1B[2K"); // Move cursor up one line and clear that line io::stdout().flush()?; // Ensure the changes are reflected immediately Ok(String::from(password)) } fn init() -> io::Result<()> { if Storage::is_inited() { return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted")); } print!("Enter passphrase for storage: "); io::stdout().flush()?; // TODO: rename to passphrase let password = rpassword::read_password()?; print!("Reenter passphrase: "); io::stdout().flush()?; let password2 = rpassword::read_password()?; if password != password2 { return Err(io::Error::new(io::ErrorKind::InvalidInput, "Passwords must be equal")); } Storage::init(password)?; Ok(()) } fn list() -> io:: Result<()> { // TODO: get storage from login let passphrase = login()?; let st = Storage::from_db(passphrase)?; for id in st.ids() { println!("{}", id); } Ok(()) } fn add(id: &String) -> io::Result<()> { let passphrase = login()?; let mut st = Storage::from_db(passphrase)?; if st.contains(id) { // TODO: ask to edit existing in outer function which invoked this one return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("Dublicate item id: {}. Item id's must be unique.", id) // TODO: ask to edit [Y/n] )) } let content = editor::open_to_edit()?; st.add(Item::from(id.clone(), content)); st.dump()?; Ok(()) } fn edit(id: &String) -> io::Result<()> { // TODO: implement Ok(()) } fn show(id: &String) -> io::Result<()> { let passphrase = login()?; let mut st = Storage::from_db(passphrase)?; if !st.contains(id) { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("Can`t find id: {}", id) )); } let item = st.get(id); editor::open_to_show(&item.content)?; Ok(()) } fn run_command() -> io::Result<()> { let cli = Cli::parse(); match &cli.command { Some(Commands::Init) => { init()?; } Some(Commands::List) => { list()?; } Some(Commands::Show { id }) => { show(id)?; } Some(Commands::Add { id }) => { add(id)?; } Some(Commands::Edit { id }) => { edit(id)? } None => { if !Storage::is_inited() { match get_prompt("Do you want to init your storage?")? { PROMPT::YES => init()?, PROMPT::NO => Storage::print_init_hint(), } } else { list()? } } } Ok(()) } fn main() { match run_command() { Ok(()) => return, Err(e) => { println!("{}", e); process::exit(2); // TODO: better codes for different errors } } }