feat: Support entering custom keys value via STDIN #64

Merged
awiteb merged 4 commits from awiteb/fix-56 into master 2024-08-17 17:23:33 +02:00 AGit
12 changed files with 70 additions and 47 deletions

View file

@ -35,8 +35,10 @@ Options:
-t, --totp-secret [<TOTP_SECRET>] -t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, if there is no value you will prompt it The TOTP secret, if there is no value you will prompt it
-c, --custom <KEY=VALUE> -c, --custom <KEY(=VALUE)?>
Add a custom field to the vault Add a custom field to the vault
If there is no value, you will enter it through a prompt
-f, --force -f, --force
Force add, will not return error if there is a problem with the args. Force add, will not return error if there is a problem with the args.
@ -45,9 +47,6 @@ Options:
-h, --help -h, --help
Print help (see a summary with '-h') Print help (see a summary with '-h')
-V, --version
Print version
``` ```
So, to add a vault you need to provide a name for the vault, and you can provide So, to add a vault you need to provide a name for the vault, and you can provide

View file

@ -4,10 +4,6 @@
``` ```
Usage: lprs clean Usage: lprs clean
Options:
-h, --help Print help
-V, --version Print version
``` ```
Is simple, just run `lprs clean` and the vaults file will be cleaned, removing Is simple, just run `lprs clean` and the vaults file will be cleaned, removing

View file

@ -14,24 +14,28 @@ Options:
The new vault name The new vault name
-u, --username <USERNAME> -u, --username <USERNAME>
The new vault username The new vault username, make it empty string to delete it
-p, --password [<PASSWORD>] -p, --password [<PASSWORD>]
The new password, if there is no value for it you will prompt it The new password, make it empty string to delete it
If there is no value for it you will prompt it
-s, --service <SERVICE> -s, --service <SERVICE>
The new vault service The new vault service, make it empty string to delete it
-o, --note <NOTE> -o, --note <NOTE>
The new vault note The new vault note
-t, --totp-secret [<TOTP_SECRET>] -t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, if there is no value you will prompt it The TOTP secret, make it empty string to delete it
If there is no value you will prompt it
-c, --custom <KEY=VALUE> -c, --custom <KEY=VALUE>
The custom field, make its value empty to delete it The custom field, make it empty string to delete it
If the custom field not exist will created it, if it's will update it If the custom field not exist will created it, if it's will update it, if there is no value, you will enter it through a prompt (e.g `-c key`)
-f, --force -f, --force
Force edit, will not return error if there is a problem with the args. Force edit, will not return error if there is a problem with the args.
@ -40,9 +44,6 @@ Options:
-h, --help -h, --help
Print help (see a summary with '-h') Print help (see a summary with '-h')
-V, --version
Print version
``` ```
To edit a vault you need to provide the index or the name of the vault. If you To edit a vault you need to provide the index or the name of the vault. If you

View file

@ -14,7 +14,6 @@ Options:
-n, --numbers With numbers (0-9) -n, --numbers With numbers (0-9)
-s, --symbols With symbols (!,# ...) -s, --symbols With symbols (!,# ...)
-h, --help Print help -h, --help Print help
-V, --version Print version
``` ```
Generate a password with the specified length, by default the length is `18`, Generate a password with the specified length, by default the length is `18`,

View file

@ -19,9 +19,6 @@ Arguments:
Options: Options:
-h, --help -h, --help
Print help (see a summary with '-h') Print help (see a summary with '-h')
-V, --version
Print version
``` ```
Get a single field from a vault, if the field is not provided, the whole vault Get a single field from a vault, if the field is not provided, the whole vault

View file

@ -24,9 +24,6 @@ Options:
-h, --help -h, --help
Print help (see a summary with '-h') Print help (see a summary with '-h')
-V, --version
Print version
``` ```
## Export usage ## Export usage

View file

@ -10,7 +10,6 @@ Options:
-r, --regex Enable regex when use `--filter` option -r, --regex Enable regex when use `--filter` option
--json Returns the output as `json` list of vaults --json Returns the output as `json` list of vaults
-h, --help Print help -h, --help Print help
-V, --version Print version
``` ```
Lprs `list` command is used to list all vaults in the vaults file, you can also Lprs `list` command is used to list all vaults in the vaults file, you can also
@ -21,4 +20,5 @@ work with it with `jq`).
### Examples ### Examples
<script src="https://asciinema.org/a/eEVkDi0NroBjKNILg7KW3hSKY.js" id="asciicast-eEVkDi0NroBjKNILg7KW3hSKY" async="true" data-cols=48 data-rows=10></script> <script src="https://asciinema.org/a/eEVkDi0NroBjKNILg7KW3hSKY.js" id="asciicast-eEVkDi0NroBjKNILg7KW3hSKY" async="true" data-cols=48 data-rows=10></script>

View file

@ -11,7 +11,6 @@ Arguments:
Options: Options:
-f, --force Force remove, will not return error if there is no vault with the given index or name -f, --force Force remove, will not return error if there is no vault with the given index or name
-h, --help Print help -h, --help Print help
-V, --version Print version
``` ```
To remove a vault you need to provide the index or the name of the vault. If you To remove a vault you need to provide the index or the name of the vault. If you

View file

@ -19,12 +19,14 @@ use crate::{LprsError, LprsResult};
/// Parse the key & value arguments. /// Parse the key & value arguments.
/// ## Errors /// ## Errors
/// - If the argument value syntax not `key=value` /// - If the argument value syntax not `key=value`
pub fn kv_parser(value: &str) -> LprsResult<(String, String)> { pub fn kv_parser(value: &str) -> LprsResult<(String, Option<String>)> {
if let Some((key, value)) = value.split_once('=') { if let Some((key, value)) = value.split_once('=') {
Ok((key.trim().to_owned(), value.trim().to_owned())) Ok((key.trim().to_owned(), Some(value.trim().to_owned())))
} else { } else if value.trim().is_empty() {
Err(LprsError::ArgParse( Err(LprsError::ArgParse(
"There is no value, the syntax is `KEY=VALUE`".to_owned(), "Invalid key, the syntax is `KEY(=VALUE)?`".to_owned(),
)) ))
} else {
Ok((value.trim().to_owned(), None))
} }
} }

View file

@ -39,9 +39,11 @@ pub struct Add {
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
totp_secret: Option<Option<String>>, totp_secret: Option<Option<String>>,
/// Add a custom field to the vault /// Add a custom field to the vault
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")] ///
/// If there is no value, you will enter it through a prompt
#[arg(name = "KEY(=VALUE)?", short = 'c', long = "custom")]
#[arg(value_parser = clap_parsers::kv_parser)] #[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, String)>, custom_fields: Vec<(String, Option<String>)>,
/// Force add, will not return error if there is a problem with the args. /// Force add, will not return error if there is a problem with the args.
/// ///
/// For example, duplication in the custom fields and try to adding empty /// For example, duplication in the custom fields and try to adding empty
@ -73,7 +75,9 @@ impl LprsCommand for Add {
self.vault_info.name = self.vault_info.name.trim().to_string(); self.vault_info.name = self.vault_info.name.trim().to_string();
self.vault_info.password = utils::user_secret(self.password, "Vault password:", false)?; self.vault_info.password = utils::user_secret(self.password, "Vault password:", false)?;
self.vault_info.custom_fields = self.custom_fields.into_iter().collect(); self.vault_info.custom_fields = utils::prompt_custom(self.custom_fields)?
.into_iter()
.collect();
vault_manager.add_vault(self.vault_info); vault_manager.add_vault(self.vault_info);
vault_manager.try_export()?; vault_manager.try_export()?;
} }
@ -95,23 +99,23 @@ impl LprsCommand for Add {
if self if self
.password .password
.as_ref() .as_ref()
.is_some_and(|p| p.as_ref().is_some_and(|p| p.is_empty())) .is_some_and(|p| p.as_ref().is_some_and(String::is_empty))
|| self.vault_info.name.is_empty() || self.vault_info.name.is_empty()
|| self || self
.vault_info .vault_info
.username .username
.as_ref() .as_ref()
.is_some_and(|u| u.is_empty()) .is_some_and(String::is_empty)
|| self || self
.vault_info .vault_info
.service .service
.as_ref() .as_ref()
.is_some_and(|s| s.is_empty()) .is_some_and(String::is_empty)
|| self.vault_info.note.as_ref().is_some_and(|n| n.is_empty()) || self.vault_info.note.as_ref().is_some_and(String::is_empty)
|| self || self
.custom_fields .custom_fields
.iter() .iter()
.any(|(k, v)| k.is_empty() || v.is_empty()) .any(|(k, v)| k.is_empty() || v.as_ref().is_some_and(String::is_empty))
{ {
return Err(LprsError::EmptyValue); return Err(LprsError::EmptyValue);
} }

View file

@ -36,28 +36,33 @@ pub struct Edit {
/// The new vault name /// The new vault name
name: Option<String>, name: Option<String>,
#[arg(short, long)] #[arg(short, long)]
/// The new vault username /// The new vault username, make it empty string to delete it
username: Option<String>, username: Option<String>,
#[arg(short, long)] #[arg(short, long)]
/// The new password, if there is no value for it you will prompt it /// The new password, make it empty string to delete it
///
/// If there is no value for it you will prompt it
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
password: Option<Option<String>>, password: Option<Option<String>>,
#[arg(short, long)] #[arg(short, long)]
/// The new vault service /// The new vault service, make it empty string to delete it
service: Option<String>, service: Option<String>,
#[arg(short = 'o', long)] #[arg(short = 'o', long)]
/// The new vault note /// The new vault note
note: Option<String>, note: Option<String>,
/// The TOTP secret, if there is no value you will prompt it /// The TOTP secret, make it empty string to delete it
///
/// If there is no value you will prompt it
#[arg(short, long)] #[arg(short, long)]
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
totp_secret: Option<Option<String>>, totp_secret: Option<Option<String>>,
/// The custom field, make its value empty to delete it /// The custom field, make it empty string to delete it
/// ///
/// If the custom field not exist will created it, if it's will update it /// If the custom field not exist will created it, if it's will update it,
/// if there is no value, you will enter it through a prompt (e.g `-c key`)
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")] #[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
#[arg(value_parser = clap_parsers::kv_parser)] #[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, String)>, custom_fields: Vec<(String, Option<String>)>,
/// Force edit, will not return error if there is a problem with the args. /// Force edit, will not return error if there is a problem with the args.
/// ///
/// For example, duplication in the custom fields and try to editing nothing /// For example, duplication in the custom fields and try to editing nothing
@ -120,7 +125,10 @@ impl LprsCommand for Edit {
vault.note = Some(new_note); vault.note = Some(new_note);
} }
} }
utils::apply_custom_fields(&mut vault.custom_fields, self.custom_fields); utils::apply_custom_fields(
&mut vault.custom_fields,
utils::prompt_custom(self.custom_fields)?,
);
vault_manager.try_export() vault_manager.try_export()
} }

View file

@ -181,7 +181,7 @@ pub fn lprs_version() -> LprsResult<Option<String>> {
} }
/// Returns the duplicated field from the custom field (unprocessed fields) /// Returns the duplicated field from the custom field (unprocessed fields)
pub fn get_duplicated_field(fields: &[(String, String)]) -> Option<&str> { pub fn get_duplicated_field(fields: &[(String, Option<String>)]) -> Option<&str> {
fields.iter().find_map(|(key, _)| { fields.iter().find_map(|(key, _)| {
if fields.iter().filter(|(k, _)| key == k).count() > 1 { if fields.iter().filter(|(k, _)| key == k).count() > 1 {
return Some(key.as_str()); return Some(key.as_str());
@ -210,6 +210,27 @@ pub fn apply_custom_fields(
} }
} }
/// Make sure all custom field values are there, if not, ask the user for it
///
/// ## Errors
/// - If can't read the user input
pub fn prompt_custom(
custom_fields: Vec<(String, Option<String>)>,
) -> LprsResult<Vec<(String, String)>> {
let mut new_fields = Vec::new();
for (key, value) in custom_fields {
if let Some(value) = value {
new_fields.push((key, value));
} else {
let value = inquire::Text::new(&format!("Value of `{key}`:")).prompt()?;
new_fields.push((key, value));
}
}
Ok(new_fields)
}
/// Returns the vault with its index by its index or name /// Returns the vault with its index by its index or name
/// ///
/// ## Errors /// ## Errors