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,9 +35,11 @@ Options:
-t, --totp-secret [<TOTP_SECRET>]
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
If there is no value, you will enter it through a prompt
-f, --force
Force add, will not return error if there is a problem with the args.
@ -45,9 +47,6 @@ Options:
-h, --help
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

View file

@ -4,10 +4,6 @@
```
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

View file

@ -14,24 +14,28 @@ Options:
The new vault name
-u, --username <USERNAME>
The new vault username
The new vault username, make it empty string to delete it
-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>
The new vault service
The new vault service, make it empty string to delete it
-o, --note <NOTE>
The new vault note
-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>
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
Force edit, will not return error if there is a problem with the args.
@ -40,9 +44,6 @@ Options:
-h, --help
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

View file

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

View file

@ -19,9 +19,6 @@ Arguments:
Options:
-h, --help
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

View file

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

View file

@ -10,7 +10,6 @@ Options:
-r, --regex Enable regex when use `--filter` option
--json Returns the output as `json` list of vaults
-h, --help Print help
-V, --version Print version
```
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
<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:
-f, --force Force remove, will not return error if there is no vault with the given index or name
-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

View file

@ -19,12 +19,14 @@ use crate::{LprsError, LprsResult};
/// Parse the key & value arguments.
/// ## Errors
/// - 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('=') {
Ok((key.trim().to_owned(), value.trim().to_owned()))
} else {
Ok((key.trim().to_owned(), Some(value.trim().to_owned())))
} else if value.trim().is_empty() {
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)]
totp_secret: Option<Option<String>>,
/// 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)]
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.
///
/// 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.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.try_export()?;
}
@ -95,23 +99,23 @@ impl LprsCommand for Add {
if self
.password
.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
.username
.as_ref()
.is_some_and(|u| u.is_empty())
.is_some_and(String::is_empty)
|| self
.vault_info
.service
.as_ref()
.is_some_and(|s| s.is_empty())
|| self.vault_info.note.as_ref().is_some_and(|n| n.is_empty())
.is_some_and(String::is_empty)
|| self.vault_info.note.as_ref().is_some_and(String::is_empty)
|| self
.custom_fields
.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);
}

View file

@ -36,28 +36,33 @@ pub struct Edit {
/// The new vault name
name: Option<String>,
#[arg(short, long)]
/// The new vault username
/// The new vault username, make it empty string to delete it
username: Option<String>,
#[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)]
password: Option<Option<String>>,
#[arg(short, long)]
/// The new vault service
/// The new vault service, make it empty string to delete it
service: Option<String>,
#[arg(short = 'o', long)]
/// The new vault note
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)]
#[allow(clippy::option_option)]
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(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.
///
/// 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);
}
}
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()
}

View file

@ -181,7 +181,7 @@ pub fn lprs_version() -> LprsResult<Option<String>> {
}
/// 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, _)| {
if fields.iter().filter(|(k, _)| key == k).count() > 1 {
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
///
/// ## Errors