diff --git a/README.md b/README.md index 0c14658..c3d2997 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,15 @@ fa list --store bingus_store # list all credentials on bingus store. fa search meow # search for credentials beginning with 'meow' on default store fa search bingus --store bingus_store # search for credentials beginning with 'bingus' on 'bingus_store' +# store usage +fs store list # list all stores +fs store create spoingus_store # create a new store 'spoingus_store'. +fs store default spoingus_store # set the default store to 'spoingus_store'. +fs store remove spoingus_store # remove the spoingus store. + # other fa config view # display the configuration utilized by 'fa'. -fa store list # list all stores. -fa help add # get help for a specific command +fa help store create # get help for a specific command fa -V # print the version ``` @@ -51,6 +56,7 @@ gpg_fingerprint = "ABCDEF0123456789" # you can also use the full fingerprint - [x] use [thiserror](https://docs.rs/thiserror/latest/thiserror/index.html) and tidy up slightly. - [x] better output and input +- [x] create, remove and default store command. - [ ] copy to clipboard - [ ] better internal data structure instead of `Hashmap`. it might give me more ways to store more information. such as the diff --git a/src/cli.rs b/src/cli.rs index cd0e2d2..ffa6186 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -88,4 +88,22 @@ pub enum FaCommandConfig { pub enum FaCommandStore { #[command(about = "list all the stores.")] List, + + #[command(about = "remove a store.")] + Remove { + #[arg(index = 1, help = "a required store name.")] + store: String, + }, + + #[command(about = "create an empty store.")] + Create { + #[arg(index = 1, help = "a required store name.")] + store: String, + }, + + #[command(about = "default a store.")] + Default { + #[arg(index = 1, help = "a required store name.")] + store: String, + }, } diff --git a/src/fa.rs b/src/fa.rs index 287c84a..1665a4e 100644 --- a/src/fa.rs +++ b/src/fa.rs @@ -49,14 +49,14 @@ impl Fa { } // initialize state. - let state = FaApplicationState { + let mut state = FaApplicationState { configuration: config, }; match cloned_command { // command group Some(FaCommands::Config(fc)) => self.command_group_config(fc, &state), - Some(FaCommands::Store(fs)) => self.command_group_store(fs, &state), + Some(FaCommands::Store(fs)) => self.command_group_store(fs, &mut state), // command Some(FaCommands::List { store }) => self.command_list(store, &state), @@ -140,37 +140,105 @@ impl Fa { fn command_group_store( &self, command_store: &FaCommandStore, - state: &FaApplicationState, + state: &mut FaApplicationState, ) -> Result<(), FaError> { match command_store { FaCommandStore::List => { let store_path = &state.configuration._inner.store.base_path; - for entry in (fs::read_dir(store_path)?).flatten() { - if let Ok(file_type) = entry.file_type() { - if file_type.is_file() { - let file_name_osstring = entry.file_name(); - let file_name_with_extension = - file_name_osstring.to_str().ok_or(FaError::UnexpectedNone)?; - let (file_name, _) = file_name_with_extension - .rsplit_once('.') - .ok_or(FaError::UnexpectedNone)?; - let extension = Path::new(file_name_with_extension) - .extension() - .and_then(OsStr::to_str) - .ok_or(FaError::UnexpectedNone)?; - - if extension == "fa" { - println!( - "{} | Using store directory '{}'", - style("fa").bold().dim(), - style(&store_path).bold().bright() - ); - println!("{} | {}", style("fa").bold().dim(), file_name); + println!( + "{} | Using store directory '{}'", + style("fa").bold().dim(), + style(&store_path).bold().bright() + ); + if fs::read_dir(store_path)?.count() == 0 { + println!("{} | There are no stores yet.", style("fa").bold().dim()); + } else { + for entry in fs::read_dir(store_path)?.flatten() { + if let Ok(file_type) = entry.file_type() { + if file_type.is_file() { + let file_name_osstring = entry.file_name(); + let file_name_with_extension = + file_name_osstring.to_str().ok_or(FaError::UnexpectedNone)?; + let (file_name, _) = file_name_with_extension + .rsplit_once('.') + .ok_or(FaError::UnexpectedNone)?; + let extension = Path::new(file_name_with_extension) + .extension() + .and_then(OsStr::to_str) + .ok_or(FaError::UnexpectedNone)?; + + if extension == "fa" { + println!("{} | {}", style("fa").bold().dim(), file_name); + } } } } } } + FaCommandStore::Remove { store } => { + let store_path = + Store::get_file_path(store, &state.configuration._inner.store.base_path)?; + if !Store::check_if_exists(&store_path) { + return Err(FaError::NoStore { path: store_path }); + } else { + // prompt for password before decrypting. + Store::load( + store, + store_path.clone(), + &state.configuration._inner.security.gpg_fingerprint, + )?; + fs::remove_file(&store_path)?; + println!( + "{} | {} removed {} store.", + style("fa").bold().dim(), + style("Successfully").bold().green(), + style(store).bright() + ); + } + } + FaCommandStore::Create { store } => { + let store_path = + Store::get_file_path(store, &state.configuration._inner.store.base_path)?; + Store::new( + store, + store_path, + &state.configuration._inner.security.gpg_fingerprint, + )?; + println!( + "{} | {} added {} store.", + style("fa").bold().dim(), + style("Successfully").bold().green(), + style(store).bright() + ); + } + FaCommandStore::Default { store } => { + // check if exists. + let store_path = + Store::get_file_path(store, &state.configuration._inner.store.base_path)?; + match Store::check_if_exists(&store_path) { + true => { + let config = Config::new( + state.configuration._inner.store.base_path.clone(), + store.clone(), + state.configuration._inner.security.gpg_fingerprint.clone(), + )?; + state.configuration = config; + println!( + "{} | {} is now your {} store.", + style("fa").bold().dim(), + style(store).bold().green(), + style("default").bold().bright(), + ); + } + false => { + println!( + "{} | The store {} does not exist.", + style("fa").bold().dim(), + style(store).bold().red() + ); + } + } + } } Ok(()) }