Skip to content

Commit 9a4dcbf

Browse files
authored
fix: read delete confirmation from tty when stdin is consumed (#642)
Closes #627
2 parents dbd0d09 + f65ea62 commit 9a4dcbf

3 files changed

Lines changed: 67 additions & 27 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ rpassword = "7.4.0"
3434
data-encoding = "2.10.0"
3535
copypasta-ext = "0.4.4"
3636
zeroize = { version = "1.8.2", features = ["zeroize_derive"] }
37-
clap = { version = "4.5.60", features = ["derive"] }
37+
clap = { version = "4.6.0", features = ["derive"] }
3838
hmac = "0.12.1"
3939
sha1 = "0.10.6"
4040
sha2 = "0.10.9"

src/arguments/delete.rs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use std::io::{self, Write};
1+
use std::io::{self, BufRead, Write};
2+
3+
#[cfg(unix)]
4+
use std::fs::File;
5+
#[cfg(windows)]
6+
use std::fs::OpenOptions;
27

38
use clap::Args;
49
use color_eyre::eyre::eyre;
@@ -24,34 +29,69 @@ pub struct DeleteArgs {
2429

2530
impl SubcommandExecutor for DeleteArgs {
2631
fn run_command(self, mut otp_database: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
32+
if otp_database.elements_ref().is_empty() {
33+
return Err(eyre!("There are no elements to delete"));
34+
}
35+
2736
let index_to_delete = self
2837
.index
2938
.and_then(|i| i.checked_sub(1))
39+
// Match by issues or label if index filter is missing
3040
.or_else(|| get_first_matching_element(&otp_database, &self))
3141
.ok_or(eyre!("No code has been found using the given arguments"))?;
3242

33-
let mut output = String::with_capacity(1);
34-
35-
let element = otp_database.elements_ref().get(index_to_delete).unwrap();
36-
print!(
37-
"Are you sure you want to delete the {}th code ({}, {}) [Y,N]: ",
38-
index_to_delete + 1,
39-
element.issuer,
40-
element.label
41-
);
42-
io::stdout().flush()?;
43+
if let Some(element) = otp_database.elements_ref().get(index_to_delete) {
44+
print!(
45+
"Are you sure you want to delete the {}th code ({}, {}) [Y,N]: ",
46+
index_to_delete + 1,
47+
element.issuer,
48+
element.label
49+
);
50+
io::stdout().flush()?;
4351

44-
io::stdin().read_line(&mut output)?;
52+
let output = read_confirmation_line()?;
4553

46-
if output.trim().eq_ignore_ascii_case("y") {
47-
otp_database.delete_element(index_to_delete);
48-
Ok(otp_database)
54+
if output.trim().eq_ignore_ascii_case("y") {
55+
otp_database.delete_element(index_to_delete);
56+
Ok(otp_database)
57+
} else {
58+
Err(eyre!("Operation interrupt by the user"))
59+
}
4960
} else {
50-
Err(eyre!("Operation interrupt by the user"))
61+
Err(eyre!("Missing {}th code to delete", index_to_delete + 1))
5162
}
5263
}
5364
}
5465

66+
fn read_confirmation_line() -> color_eyre::Result<String> {
67+
let mut output = String::with_capacity(1);
68+
69+
if io::stdin().read_line(&mut output)? > 0 {
70+
return Ok(output);
71+
}
72+
73+
#[cfg(unix)]
74+
{
75+
output.clear();
76+
let mut tty = io::BufReader::new(File::open("/dev/tty")?);
77+
tty.read_line(&mut output)?;
78+
return Ok(output);
79+
}
80+
81+
#[cfg(windows)]
82+
{
83+
output.clear();
84+
let mut tty = io::BufReader::new(OpenOptions::new().read(true).open("CONIN$")?);
85+
tty.read_line(&mut output)?;
86+
return Ok(output);
87+
}
88+
89+
#[allow(unreachable_code)]
90+
Err(eyre!(
91+
"Unable to read confirmation answer from standard input or terminal"
92+
))
93+
}
94+
5595
fn get_first_matching_element(
5696
otp_database: &OTPDatabase,
5797
delete_args: &DeleteArgs,

0 commit comments

Comments
 (0)