Skip to content

Commit

Permalink
22 allow skip input validation (#27)
Browse files Browse the repository at this point in the history
added:
- Shift + Enter as force
- Shift + Back as clear all


* reworked input to use macros
* additional keys and fixes for windows
* reworked input example
* fixed test failure
* some more changes to the new key combinations
  • Loading branch information
Arteiii authored Apr 17, 2024
1 parent 79081d2 commit c7fc765
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 103 deletions.
6 changes: 4 additions & 2 deletions examples/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use regex::Regex;
use zenity::menu::input::{valid_path, valid_regex};

fn main() {
println!("Input Preview:");

println!(
"\n\nReturn: {}",
valid_regex(Regex::new(r"^\d{3}$").unwrap())
valid_regex(Regex::new(r"^\d{3}$").unwrap(), Some("369"), false)
);
println!("\n\nPath: {:?}", valid_path());
println!("\n\nPath: {:?}", valid_path(None, true));
}
Empty file added results.xml
Empty file.
211 changes: 131 additions & 80 deletions src/menu/input.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
//! Input Validation Widgets
//!
//! This module provides functions for validating user input with various criteria, such as regex patterns or file paths
//! **Note:** This module is a work in progress,
//! and breaking changes could be made soon without increasing the major version
//! for different reasons, such as improvements or bug fixes.
//!
//!
//! This module provides functions for validating user input with various criteria,
//! such as regex patterns or file paths
//! The functions prompt the user for input, validate it, and return the validated input
//!
//! # Examples
Expand Down Expand Up @@ -35,28 +41,85 @@
//! This module is a work in progress, and contributions are welcome
//!
use std::io;
use std::io::stdout;
use std::path::{Path, PathBuf};

use crossterm::{
cursor::MoveTo,
execute,
cursor, execute,
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
};
use regex::Regex;

use crate::menu::handle_key_input;
use crate::style::{Color, Print, SetForegroundColor};

macro_rules! input_loop {
($buffer:expr, $validate:expr, $default:expr, $allow_force:expr) => {
let mut force: bool = false;

// clear console enter release in windows
let mut invalid_buffer = String::new();
handle_key_input(&mut invalid_buffer, &mut force);

loop {
render_input_prompt(&$buffer, &$validate, $default);

if handle_key_input(&mut $buffer, &mut force)
&& $default.is_some()
&& $buffer.is_empty()
{
$buffer = $default.unwrap().to_string();
break;
}

if force && $allow_force {
println!("Force!!");
break;
}

if handle_key_input(&mut $buffer, &mut force) && $buffer.is_empty() && $validate {
break;
}
}
};
}

macro_rules! raw_mode_wrapper {
($content:expr) => {
enable_raw_mode().expect("Failed to enable raw-mode");

$content;

disable_raw_mode().expect("Failed to disable raw-mode");
execute!(
stdout(),
cursor::MoveTo(0, 0),
Clear(ClearType::FromCursorDown),
cursor::DisableBlinking
)
.unwrap();
};
}

/// Validates and returns a string that matches the specified regex pattern.
///
/// This function prompts the user to enter input and validates the input against the provided
/// regex pattern. It continues to prompt the user until the entered input matches the regex pattern.
/// The function returns the validated input as a string.
///
/// If `default` is provided and the user enters an empty string, the default value will be used.
///
/// Note: The `allow_force` option is currently not fully supported due to console issues. See
/// [this issue](https://github.com/crossterm-rs/crossterm/issues/685) for more details. However,
/// users can force input by pressing Shift+Enter. Pressing Shift+Enter will clear the full input.
///
///
/// # Arguments
///
/// * `regex` - A `Regex` object representing the regex pattern to match against the user input.
/// * `default` - An optional default value to be used if the user enters an empty string.
/// * `allow_force` - A boolean indicating whether to allow the user to force input (not fully supported).
///
/// # Returns
///
Expand All @@ -72,37 +135,19 @@ use crate::style::{Color, Print, SetForegroundColor};
/// let regex = Regex::new(r"^\d{3}$").unwrap();
///
/// // Prompt the user to enter input matching the regex pattern
/// let input = valid_regex(regex);
/// let input = valid_regex(regex, Some("default_value"), false);
///
/// println!("Valid input: {}", input);
/// ```
pub fn valid_regex(regex: Regex) -> String {
enable_raw_mode().expect("Failed to enable raw-mode");

pub fn valid_regex(regex: Regex, default: Option<&str>, allow_force: bool) -> String {
let mut buffer = String::new();

loop {
if handle_key_input(&mut buffer) && validate_input(&buffer, &regex) {
break;
}

execute!(
stdout(),
MoveTo(0, 0),
Clear(ClearType::CurrentLine),
Print("Enter input: "),
if !regex.is_match(&buffer) {
SetForegroundColor(Color::DarkRed)
} else {
SetForegroundColor(Color::Green)
},
Print(&buffer),
SetForegroundColor(Color::Reset)
)
.unwrap();
}

disable_raw_mode().expect("Failed to disable raw-mode");
raw_mode_wrapper!(input_loop!(
buffer,
validate_input(&buffer, &regex),
default,
allow_force
));

buffer
}
Expand All @@ -113,28 +158,38 @@ pub fn valid_regex(regex: Regex) -> String {
/// it returns a `PathBuf` containing the path. Otherwise, it continues prompting the user until a valid
/// path is entered.
///
/// If `default` is provided and the user enters an empty string, the default value will be used.
///
/// Note: The `allow_force` option is currently not fully supported due to console issues. See
/// [this issue](https://github.com/crossterm-rs/crossterm/issues/685) for more details. However,
/// users can force input by pressing Shift+Enter. Pressing Shift+Enter will clear the full input.
///
/// # Arguments
///
/// * `default` - An optional default value to be used if the user enters an empty string.
/// * `allow_force` - A boolean indicating whether to allow the user to force input (not fully supported).
///
/// # Returns
///
/// A `Box<PathBuf>` representing the validated path entered by the user.
///
/// # Examples
///
/// ```rust,ignore
/// use zenity::menu::input::valid_path;
///
/// let path = valid_path();
/// let path = valid_path(Some("/home/user"), true);
/// println!("Entered path: {:?}", path);
/// ```
pub fn valid_path() -> Box<PathBuf> {
enable_raw_mode().expect("Failed to enable raw-mode");

pub fn valid_path(default: Option<&str>, allow_force: bool) -> Box<PathBuf> {
let mut buffer = String::new();

loop {
if handle_key_input(&mut buffer) && validate_path(&buffer) {
break;
}

render_input_prompt(&buffer, &validate_path(&buffer));
}

disable_raw_mode().expect("Failed to disable raw-mode");
raw_mode_wrapper!(input_loop!(
buffer,
validate_path(&buffer),
default,
allow_force
));

let path = PathBuf::from(buffer);

Expand All @@ -150,56 +205,52 @@ fn validate_input(buffer: &str, regex: &Regex) -> bool {
if regex.is_match(buffer) {
true
} else {
execute!(stdout(), MoveTo(0, 0), Clear(ClearType::CurrentLine)).unwrap();
execute!(
io::stdout(),
cursor::MoveTo(0, 0),
Clear(ClearType::CurrentLine)
)
.unwrap();
false
}
}

fn render_input_prompt(buffer: &str, is_valid: &bool) {
fn render_input_prompt(buffer: &str, is_valid: &bool, default: Option<&str>) {
execute!(
stdout(),
MoveTo(0, 0),
io::stdout(),
cursor::MoveTo(0, 6),
Clear(ClearType::CurrentLine),
Print("Enter path: "),
if !is_valid {
SetForegroundColor(Color::DarkRed)
} else {
SetForegroundColor(Color::Green)
},
Print(buffer),
SetForegroundColor(Color::Reset)
)
.unwrap();
if !buffer.is_empty() || default.is_none() {
execute!(
io::stdout(),
Print("Enter path: "),
if !is_valid {
SetForegroundColor(Color::DarkRed)
} else {
SetForegroundColor(Color::Green)
},
Print(buffer),
)
.unwrap();
} else {
execute!(
io::stdout(),
Print("Enter path: "),
SetForegroundColor(Color::Grey),
Print(default.unwrap()),
Print(" (Default)"),
)
.unwrap();
}
execute!(io::stdout(), SetForegroundColor(Color::Reset),).unwrap();
}

#[cfg(test)]
mod tests {
use std::thread;
use std::time::Duration;

use super::*;

// TODO!: better testing for the valid_regex and valid_path functions
#[test]
fn test_valid_regex() {
let _handle = thread::spawn(|| {
valid_regex(Regex::new(r"^\d{3}$").unwrap());
});

thread::sleep(Duration::from_secs(5));

// If the test reaches this point without crashing, consider it a success
}

#[test]
fn test_valid_path() {
let _handle = thread::spawn(|| {
valid_path();
});

thread::sleep(Duration::from_secs(5));
}

#[test]
fn test_validate_path_existing_file() {
// Create a temporary file for testing
Expand All @@ -219,12 +270,12 @@ mod tests {
let file_path = "nonexistent_file.txt";

// Validate the path of the nonexistent file
assert!(validate_path(file_path));
assert!(!validate_path(file_path));
}

#[test]
fn test_render_input_prompt() {
// Call the render_input_prompt function with a mock Stdout
render_input_prompt("123", &true);
render_input_prompt("123", &true, None);
}
}
Loading

0 comments on commit c7fc765

Please sign in to comment.