//! Clap validators module //! //! Used for validating different CLI args (like width, height and delay) //! Ref: https://docs.rs/clap/3.0.0-beta.2/clap/struct.Arg.html#method.validator use terminal_size::{terminal_size, Height, Width}; #[cfg(test)] use mocktopus::macros::mockable; /// An internal method used to allow mocking terminal size in tests #[cfg_attr(test, mockable)] fn terminal_size_wrapper() -> (Width, Height) { terminal_size().unwrap() } /// Validator to check if width configuration can be used /// /// The maximum width allowed is the current terminal window width pub(crate) fn is_fitting_term_width(val: &str) -> Result<(), String> { let (Width(term_width), Height(_)) = terminal_size_wrapper(); match val.parse::() { Ok(n) if n > 0 && n <= term_width => Ok(()), Ok(_) => Err(format!( "must be between 1 and {} (current terminal width)", term_width )), Err(e) => Err(e.to_string()), } } #[cfg(test)] mod is_fitting_term_width { use super::*; use mocktopus::mocking::*; #[test] fn positive_value() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_width("13").is_ok()); } #[test] fn value_too_large() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_width("1337").is_err()); } #[test] fn negative_value() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_width("-13").is_err()); } #[test] fn string_value() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_width("not a number").is_err()); } } /// Validator to check if height configuration can be used /// /// The maximum height allowed is the current terminal window height - 2 /// The last 2 lines are used for status reporting (seed/input) and space /// for ease of use pub(crate) fn is_fitting_term_height(val: &str) -> Result<(), String> { let (Width(_), Height(term_height)) = terminal_size_wrapper(); let term_height = term_height - 2; match val.parse::() { Ok(n) if n > 0 && n <= term_height => Ok(()), Ok(_) => Err(format!( "must be between 1 and {} (current terminal height)", term_height )), Err(e) => Err(e.to_string()), } } #[cfg(test)] mod is_fitting_term_height { use super::*; use mocktopus::mocking::*; #[test] fn positive_value() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_height("13").is_ok()); } #[test] fn value_too_large() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_height("1337").is_err()); } #[test] fn negative_value() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_height("-13").is_err()); } #[test] fn string_value() { terminal_size_wrapper.mock_safe(|| MockResult::Return((Width(42), Height(42)))); assert!(is_fitting_term_height("not a number").is_err()); } } /// Validator to check if delay value is positive pub(crate) fn is_positive(val: &str) -> Result<(), String> { match val.parse::() { Ok(n) if n.is_positive() => Ok(()), Ok(_) => Err(String::from("must be postive value (so far)")), Err(e) => Err(e.to_string()), } } #[cfg(test)] mod is_positive { use super::*; #[test] fn positive_value() { assert!(is_positive("1337").is_ok()); } #[test] fn negative_value() { assert!(is_positive("-1337").is_err()); } #[test] fn string_value() { assert!(is_positive("not a number").is_err()); } }