feat: Support entering custom keys value via STDIN (#64)
Reviewed-on: https://git.4rs.nl///awiteb/lprs/pulls/64 Co-authored-by: Awiteb <a@4rs.nl> Co-committed-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
04614aff57
commit
5e4fb0ea7c
12 changed files with 70 additions and 47 deletions
|
@ -35,9 +35,11 @@ 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
23
src/utils.rs
23
src/utils.rs
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue