|
|
|
@ -7,15 +7,18 @@ mod git;
|
|
|
|
|
|
|
|
|
|
use storage::{Storage, Item}; |
|
|
|
|
|
|
|
|
|
use clap::{Parser, Subcommand}; |
|
|
|
|
use clap::{Parser, Subcommand, ArgGroup}; |
|
|
|
|
use rpassword; |
|
|
|
|
|
|
|
|
|
use std::io::{self, Write}; |
|
|
|
|
use std::process; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const VERSION: &str = "0.0.1"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Parser)] |
|
|
|
|
#[command(name = "mps", version = "0.0.1", about = "MyPasswordStorage: Tool for storing your passwords locally with git synchronization")] |
|
|
|
|
#[command(name = "mps", version = VERSION, about = "MyPasswordStorage: Tool for storing your passwords locally with git synchronization")] |
|
|
|
|
struct Cli { |
|
|
|
|
#[command(subcommand)] |
|
|
|
|
command: Option<Commands>, |
|
|
|
@ -31,10 +34,7 @@ enum Commands {
|
|
|
|
|
List, |
|
|
|
|
|
|
|
|
|
/// Show content of an item
|
|
|
|
|
Show { |
|
|
|
|
#[arg(value_name="item_id")] |
|
|
|
|
id: String |
|
|
|
|
}, |
|
|
|
|
Show(ItemIdArgs), |
|
|
|
|
|
|
|
|
|
/// Adds new item with unique id to the storage
|
|
|
|
|
Add { |
|
|
|
@ -43,18 +43,31 @@ enum Commands {
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
/// Edit item content
|
|
|
|
|
Edit { |
|
|
|
|
#[arg(value_name="item_id")] |
|
|
|
|
id: String |
|
|
|
|
}, |
|
|
|
|
Edit(ItemIdArgs), |
|
|
|
|
|
|
|
|
|
Delete { |
|
|
|
|
#[arg(value_name="item_id")] |
|
|
|
|
id: String |
|
|
|
|
} |
|
|
|
|
/// Delete item
|
|
|
|
|
Delete(ItemIdArgs), |
|
|
|
|
|
|
|
|
|
/// Set new passphrase
|
|
|
|
|
Password |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Parser)] |
|
|
|
|
#[command(group(
|
|
|
|
|
ArgGroup::new("item") |
|
|
|
|
.required(true) |
|
|
|
|
.args(&["id", "number"]) |
|
|
|
|
))] |
|
|
|
|
struct ItemIdArgs { |
|
|
|
|
/// Item id
|
|
|
|
|
#[arg(required=false)] |
|
|
|
|
id: Option<String>, |
|
|
|
|
|
|
|
|
|
#[arg(short='n', long, value_name="number")] |
|
|
|
|
number: Option<u32> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum PROMPT { |
|
|
|
|
YES, |
|
|
|
|
NO |
|
|
|
@ -86,11 +99,10 @@ impl MPS {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn print_init_hint() { |
|
|
|
|
println!("1. Create empty repository for storing your passwords on gitlab or somewhere you prefer"); |
|
|
|
|
println!("2. Create dir `mps`: `mkdir ~/mps`"); |
|
|
|
|
println!("3. Clone to your home folder `~/.mps/storage`: `git clone <your_storage_url> storage`"); |
|
|
|
|
println!("4. Export varable `$MPS_HOME`: `export MPS_HOME=\"/home/<your_username>/.mps\"`"); |
|
|
|
|
println!("5. Run `mps init`"); |
|
|
|
|
println!("1. Run `mps init`"); |
|
|
|
|
println!("2. Init `storage` under git:"); |
|
|
|
|
println!(" cd storage"); |
|
|
|
|
println!(" git init"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn prompt_new_password() -> io::Result<String> { |
|
|
|
@ -112,10 +124,7 @@ impl MPS {
|
|
|
|
|
Ok(ps) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn init() -> io::Result<()> { |
|
|
|
|
if Storage::is_inited()? { |
|
|
|
|
return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted")); |
|
|
|
|
} |
|
|
|
|
fn read_new_password() -> io::Result<String> { |
|
|
|
|
let passphrase; |
|
|
|
|
loop { |
|
|
|
|
match MPS::prompt_new_password() { |
|
|
|
@ -129,8 +138,15 @@ impl MPS {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Storage::init(passphrase)?; |
|
|
|
|
Ok(()) |
|
|
|
|
Ok(passphrase) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn init() -> io::Result<()> { |
|
|
|
|
if Storage::is_inited()? { |
|
|
|
|
return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted")); |
|
|
|
|
} |
|
|
|
|
let passphrase = MPS::read_new_password()?; |
|
|
|
|
Storage::init(passphrase) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn login(&mut self) -> io::Result<()> { |
|
|
|
@ -147,33 +163,35 @@ impl MPS {
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn password(&mut self) -> io::Result<()> { |
|
|
|
|
self.login()?; |
|
|
|
|
let st = self.storage.as_ref().unwrap(); |
|
|
|
|
let passphrase = MPS::read_new_password()?; |
|
|
|
|
let new_st = st.new_from_passphrase(&passphrase); |
|
|
|
|
new_st.dump() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn list(&mut self) -> io:: Result<()> { |
|
|
|
|
self.login()?; |
|
|
|
|
let ids = self.storage.as_ref().unwrap().ids(); |
|
|
|
|
// let mut counter = 1;
|
|
|
|
|
let mut counter = 1; |
|
|
|
|
if ids.len() == 0 { |
|
|
|
|
println!("No items"); |
|
|
|
|
} else { |
|
|
|
|
for id in ids { |
|
|
|
|
println!("{}", id); |
|
|
|
|
//counter += 1;
|
|
|
|
|
println!("[{}] {}", counter, id); |
|
|
|
|
counter += 1; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Show item. Id must exists
|
|
|
|
|
fn show(&mut self, id: &String) -> io::Result<()> { |
|
|
|
|
self.login()?; |
|
|
|
|
let st = self.storage.as_ref().unwrap(); |
|
|
|
|
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(()) |
|
|
|
|
editor::open_to_show(&item.content) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn add(&mut self, id: &String) -> io::Result<()> { |
|
|
|
@ -192,10 +210,10 @@ impl MPS {
|
|
|
|
|
// set empty string because there is no content yet
|
|
|
|
|
let content = editor::open_to_edit(&String::from(""))?; |
|
|
|
|
st.add(Item::from(id.clone(), content)); |
|
|
|
|
st.dump()?; |
|
|
|
|
Ok(()) |
|
|
|
|
st.dump() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Edit item, id need not to exist
|
|
|
|
|
fn edit(&mut self, id: &String) -> io::Result<()> { |
|
|
|
|
self.login()?; |
|
|
|
|
let st = self.storage.as_mut().unwrap(); |
|
|
|
@ -213,26 +231,49 @@ impl MPS {
|
|
|
|
|
let new_content = editor::open_to_edit(&item.content)?; |
|
|
|
|
item.content = new_content; |
|
|
|
|
st.update(item); |
|
|
|
|
st.dump()?; |
|
|
|
|
Ok(()) |
|
|
|
|
st.dump() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Delete item by id, is must exist
|
|
|
|
|
fn delete(&mut self, id: &String) -> io::Result<()> { |
|
|
|
|
self.login()?; |
|
|
|
|
let st = self.storage.as_mut().unwrap(); |
|
|
|
|
if !st.contains(id) { |
|
|
|
|
return Err(io::Error::new( |
|
|
|
|
st.remove(id); |
|
|
|
|
st.dump() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Resolve id by ItemArgs.
|
|
|
|
|
/// # Arguments
|
|
|
|
|
/// * `args` - arguments to parse
|
|
|
|
|
/// * `check` - check that id existing
|
|
|
|
|
fn item_id_by_item_id_args(&mut self, args: &ItemIdArgs, check: bool) -> io::Result<String> { |
|
|
|
|
self.login()?; |
|
|
|
|
let st = self.storage.as_mut().unwrap(); |
|
|
|
|
let mut item_id: String = "NOT_INITIALIZED".to_string(); |
|
|
|
|
if let Some(id) = &args.id { |
|
|
|
|
item_id = id.clone(); |
|
|
|
|
if check && !st.contains(&id) { |
|
|
|
|
return Err(io::Error::new( |
|
|
|
|
io::ErrorKind::InvalidInput, |
|
|
|
|
format!("No such item {}", id) |
|
|
|
|
)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if let Some(number) = &args.number { |
|
|
|
|
item_id = st.get_id_by_number(*number)?; |
|
|
|
|
// we can guarantee that id exists because we take it by id
|
|
|
|
|
} |
|
|
|
|
if args.id.is_none() && args.number.is_none() { |
|
|
|
|
return Err(io::Error::new( |
|
|
|
|
io::ErrorKind::InvalidInput, |
|
|
|
|
"No such item" |
|
|
|
|
format!("Bag arguments") |
|
|
|
|
)); |
|
|
|
|
} |
|
|
|
|
st.remove(id); |
|
|
|
|
st.dump()?; |
|
|
|
|
Ok(()) |
|
|
|
|
Ok(item_id.clone()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn run_command() -> io::Result<()> { |
|
|
|
|
let cli = Cli::parse(); |
|
|
|
|
match &cli.command { |
|
|
|
@ -243,21 +284,28 @@ fn run_command() -> io::Result<()> {
|
|
|
|
|
let mut mps = MPS::new(); |
|
|
|
|
mps.list()?; |
|
|
|
|
} |
|
|
|
|
Some(Commands::Show { id }) => { |
|
|
|
|
Some(Commands::Show(args)) => { |
|
|
|
|
let mut mps = MPS::new(); |
|
|
|
|
mps.show(id)?; |
|
|
|
|
let id = mps.item_id_by_item_id_args(args, true)?; |
|
|
|
|
mps.show(&id)?; |
|
|
|
|
} |
|
|
|
|
Some(Commands::Add { id }) => { |
|
|
|
|
let mut mps = MPS::new(); |
|
|
|
|
mps.add(id)?; |
|
|
|
|
} |
|
|
|
|
Some(Commands::Edit { id }) => { |
|
|
|
|
Some(Commands::Edit(args)) => { |
|
|
|
|
let mut mps = MPS::new(); |
|
|
|
|
let id = mps.item_id_by_item_id_args(args, false)?; |
|
|
|
|
mps.edit(&id)?; |
|
|
|
|
} |
|
|
|
|
Some(Commands::Delete(args)) => { |
|
|
|
|
let mut mps = MPS::new(); |
|
|
|
|
mps.edit(id)? |
|
|
|
|
let id = mps.item_id_by_item_id_args(args, true)?; |
|
|
|
|
mps.delete(&id)?; |
|
|
|
|
} |
|
|
|
|
Some(Commands::Delete { id }) => { |
|
|
|
|
Some(Commands::Password) => { |
|
|
|
|
let mut mps = MPS::new(); |
|
|
|
|
mps.delete(id)? |
|
|
|
|
mps.password()?; |
|
|
|
|
} |
|
|
|
|
None => { |
|
|
|
|
match Storage::check_installed() { |
|
|
|
|