Compare commits

..

No commits in common. "3fd992d5826a93e76dd230d73d4343d52f6c466a" and "c868afedbf6ced096b36f026310566d1602a4cb8" have entirely different histories.

6 changed files with 30 additions and 125 deletions

4
Cargo.lock generated
View File

@ -1674,7 +1674,7 @@ dependencies = [
[[package]] [[package]]
name = "keyfork" name = "keyfork"
version = "0.2.2" version = "0.2.1"
dependencies = [ dependencies = [
"card-backend-pcsc", "card-backend-pcsc",
"clap", "clap",
@ -1744,7 +1744,7 @@ dependencies = [
[[package]] [[package]]
name = "keyfork-derive-openpgp" name = "keyfork-derive-openpgp"
version = "0.1.2" version = "0.1.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ed25519-dalek", "ed25519-dalek",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyfork-derive-openpgp" name = "keyfork-derive-openpgp"
version = "0.1.2" version = "0.1.1"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyfork" name = "keyfork"
version = "0.2.2" version = "0.2.1"
edition = "2021" edition = "2021"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"

View File

@ -3,34 +3,36 @@
use std::io::{stdin, stdout}; use std::io::{stdin, stdout};
use keyfork_prompt::{ use keyfork_prompt::{
MaybeIdentifier, PromptHandler, Terminal, validators::{mnemonic, Validator},
Terminal, PromptHandler,
}; };
#[derive(PartialEq, Eq, Debug)] use keyfork_mnemonic_util::English;
pub enum Example {
RetryQR,
UseMnemonic,
}
impl MaybeIdentifier for Example {}
impl std::fmt::Display for Example {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Example::RetryQR => f.write_str("Retry QR Code"),
Example::UseMnemonic => f.write_str("Use Mnemonic"),
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut mgr = Terminal::new(stdin(), stdout())?; let mut mgr = Terminal::new(stdin(), stdout())?;
let transport_validator = mnemonic::MnemonicSetValidator {
word_lengths: [9, 24],
};
let combine_validator = mnemonic::MnemonicSetValidator {
word_lengths: [24, 48],
};
let choice = mgr.prompt_choice( let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
"Unable to detect QR code.", "Enter a 9-word and 24-word mnemonic: ",
&[Example::RetryQR, Example::UseMnemonic], 3,
transport_validator.to_fn(),
)?; )?;
dbg!(choice); assert_eq!(mnemonics[0].as_bytes().len(), 12);
assert_eq!(mnemonics[1].as_bytes().len(), 32);
let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
"Enter a 24 and 48-word mnemonic: ",
3,
combine_validator.to_fn(),
)?;
assert_eq!(mnemonics[0].as_bytes().len(), 32);
assert_eq!(mnemonics[1].as_bytes().len(), 64);
Ok(()) Ok(())
} }

View File

@ -8,7 +8,7 @@ use keyfork_mnemonic_util::Wordlist;
/// ///
pub mod terminal; pub mod terminal;
pub mod validators; pub mod validators;
pub use terminal::{default_terminal, DefaultTerminal, Terminal}; pub use terminal::{Terminal, DefaultTerminal, default_terminal};
/// An error occurred while displaying a prompt. /// An error occurred while displaying a prompt.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -42,12 +42,6 @@ pub enum Message {
Data(String), Data(String),
} }
pub trait MaybeIdentifier {
fn identifier(&self) -> Option<char> {
None
}
}
/// A trait to allow displaying prompts and accepting input. /// A trait to allow displaying prompts and accepting input.
pub trait PromptHandler { pub trait PromptHandler {
/// Prompt the user for input. /// Prompt the user for input.
@ -64,9 +58,7 @@ pub trait PromptHandler {
/// The method may return an error if the message was not able to be displayed or if the input /// The method may return an error if the message was not able to be displayed or if the input
/// could not be read. /// could not be read.
#[cfg(feature = "mnemonic")] #[cfg(feature = "mnemonic")]
fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String> fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String> where X: Wordlist;
where
X: Wordlist;
/// Prompt the user for input based on a wordlist, while validating the wordlist using a /// Prompt the user for input based on a wordlist, while validating the wordlist using a
/// provided parser function, returning the type from the parser. A language must be specified /// provided parser function, returning the type from the parser. A language must be specified
@ -105,19 +97,6 @@ pub trait PromptHandler {
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>, validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
) -> Result<V, Error>; ) -> Result<V, Error>;
/// Prompt the user to select a choice between multiple options.
///
/// # Errors
/// The metho may return an error if the message was not able to be displayed or if a choice
/// could not be received.
fn prompt_choice<'a, T>(
&mut self,
prompt: &str,
choices: &'a [T],
) -> Result<&'a T, Error>
where
T: std::fmt::Display + PartialEq + MaybeIdentifier;
/// Prompt the user with a [`Message`]. /// Prompt the user with a [`Message`].
/// ///
/// # Errors /// # Errors

View File

@ -15,7 +15,7 @@ use keyfork_crossterm::{
use keyfork_bug::bug; use keyfork_bug::bug;
use crate::{Error, MaybeIdentifier, Message, PromptHandler, Wordlist}; use crate::{Error, Message, PromptHandler, Wordlist};
#[allow(missing_docs)] #[allow(missing_docs)]
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;
@ -122,9 +122,6 @@ where
W: Write + AsRawFd, W: Write + AsRawFd,
{ {
fn drop(&mut self) { fn drop(&mut self) {
self.write
.execute(cursor::Show)
.expect(bug!("can't enable cursor blinking"));
self.write self.write
.execute(DisableBracketedPaste) .execute(DisableBracketedPaste)
.expect(bug!("can't restore bracketed paste")); .expect(bug!("can't restore bracketed paste"));
@ -458,79 +455,6 @@ where
Ok(passphrase) Ok(passphrase)
} }
fn prompt_choice<'a, T>(&mut self, prompt: &str, choices: &'a [T]) -> Result<&'a T, Error>
where
T: std::fmt::Display + PartialEq + MaybeIdentifier,
{
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::MoveTo(0, 0))?
.queue(cursor::Hide)?;
for line in prompt.lines() {
terminal.queue(Print(line))?;
terminal
.queue(cursor::MoveDown(1))?
.queue(cursor::MoveToColumn(0))?;
}
terminal.flush()?;
let mut active_choice = 0;
let mut redraw = |active_choice| {
terminal.queue(cursor::MoveToColumn(0))?;
let mut iter = choices.iter().enumerate().peekable();
while let Some((i, choice)) = iter.next() {
// if active choice, flip foreground and background
// if active choice, wrap in []
// if not, wrap in spaces, to preserve spacing
if i == active_choice {
terminal.queue(PrintStyledContent(format!("[{choice}]").to_string().reverse()))?;
} else {
terminal.queue(Print(format!(" {choice} ").to_string()))?;
}
if iter.peek().is_some() {
terminal.queue(Print(" "))?;
}
}
terminal.flush()?;
Ok::<_, Error>(())
};
redraw(active_choice)?;
loop {
if let Event::Key(k) = read()? {
match k.code {
KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => {
return Err(Error::CtrlC);
}
KeyCode::Left => {
eprintln!("{active_choice}");
// prevent underflow
// if 0, max is 1, -1 is 0, no underflow
// if 1, max is 1, -1 is 0
// if 2 or higher, max is 2 or higher, -1 is fine
active_choice = std::cmp::max(1, active_choice) - 1;
}
KeyCode::Right => {
eprintln!("{active_choice}");
active_choice = std::cmp::min(choices.len() - 1, active_choice + 1);
}
KeyCode::Enter => {
return Ok(&choices[active_choice]);
}
_ => {}
}
}
redraw(active_choice)?;
}
}
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()> { fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()> {
let mut terminal = self.lock().alternate_screen()?.raw_mode()?; let mut terminal = self.lock().alternate_screen()?.raw_mode()?;