use clap::{Parser, Subcommand}; use std::collections::HashSet; use std::fs; use std::io::{self, Write, BufRead}; use std::path::Path; const STORAGE_FOLDER: &str = "storage"; const STORAGE_PATH: &str = "storage/db.mps"; // TODO: concat from STORAGE_FOLDER type DB = HashSet; #[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 { /// Initialisation of storage and config, use this in first time of usage of mps Init, /// Adds new item with unique id to the db Add { #[arg(value_name="item_id")] id: String }, /// Lists all ids stored in db List } fn read_db() -> io::Result { let file = fs::File::open(STORAGE_PATH)?; let reader = io::BufReader::new(file); let mut db = DB::new(); for line in reader.lines() { match line { Ok(content) => { db.insert(content); }, Err(e) => { eprintln!("Error reading line , {}", e); } } } Ok(db) } fn is_inited() -> bool { let path = Path::new(STORAGE_FOLDER); return path.exists(); } fn init() -> io::Result<()> { if is_inited() { return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted")); } fs::create_dir(STORAGE_FOLDER)?; println!("Storage folder created"); fs::File::create(STORAGE_PATH)?; println!("Storage db created"); println!("Initialization complete."); println!("Now it's required to add folder `{}` under git manually. Don't worry it's going to be encrypted", STORAGE_FOLDER); Ok(()) } fn add(id: &String) -> io::Result<()> { let db = read_db()?; if db.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) )) } let mut file = fs::OpenOptions::new() .write(true) .append(true) .open(STORAGE_PATH)?; writeln!(file, "{}", id)?; Ok(()) } fn list() -> io:: Result<()> { let db = read_db()?; for item in db.iter() { println!("{}", item); } Ok(()) } fn main() -> io::Result<()> { let cli = Cli::parse(); match &cli.command { Some(Commands::Init) => { init()?; } Some(Commands::Add{ id }) => { add(id)?; } Some(Commands::List) => { list()?; } None => { if !is_inited() { print!("Do you want to init your storage? [Y/n] "); io::stdout().flush().expect("Failed to flush stdout"); let mut input = String::new(); io::stdin() .read_line(&mut input) .expect("Failed to read answer"); let input = input.trim().to_lowercase(); match input.as_str() { "y" => init()?, "n" => { println!("mps can work only when storage inited."); println!("Hint: you cant do"); println!(" git clone {}", STORAGE_FOLDER); println!("to init manually your storage and config") }, _ => init()?, } } else { println!("login, not implemented yet") } } } Ok(()) }