use crate::constants::{
DEFAULT_LINE_COUNT, MAX_CUSTOM_TIME, MAX_CUSTOM_WORD_COUNT, MIN_CUSTOM_TIME,
MIN_CUSTOM_WORD_COUNT,
};
use clap::Parser;
#[derive(Parser, Debug, Default, Clone)]
#[command(name = "termitype", about = "Terminal-based typing game.", version)]
#[command(group(
clap::ArgGroup::new("mode")
.args(&["time", "words"])
.required(false)
.multiple(false)
))]
pub struct Cli {
#[arg(short = 't', long = "time", group = "mode", value_name = "SECONDS")]
pub time: Option<u64>,
#[arg(short = 'w', long = "words", group = "mode", value_name = "WORDS")]
pub words: Option<String>,
#[arg(short = 'c', long = "count", group = "mode", value_name = "COUNT")]
pub words_count: Option<usize>,
#[arg(short = 'n', long = "numbers")]
pub use_numbers: bool,
#[arg(short = 's', long = "symbols")]
pub use_symbols: bool,
#[arg(short = 'p', long = "punctuation")]
pub use_punctuation: bool,
#[arg(short = 'l', long, value_name = "LANG")]
pub language: Option<String>,
#[arg(long = "theme")]
pub theme: Option<String>,
#[arg(long = "ascii")]
pub ascii: Option<String>,
#[arg(long = "cursor", value_name = "STYLE")]
pub cursor: Option<String>,
#[arg(long = "picker", value_name = "STYLE")]
pub picker: Option<String>,
#[arg(long = "results", value_name = "STYLE")]
pub results: Option<String>,
#[arg(
long = "lines",
default_value_t = DEFAULT_LINE_COUNT,
value_name = "COUNT",
)]
pub visible_lines: u8,
#[cfg(debug_assertions)]
#[arg(short = 'd', long = "debug")]
pub debug: bool,
#[cfg(debug_assertions)]
#[arg(long = "show-results")]
pub show_results: bool,
#[arg(long = "hide-live-wpm")]
pub hide_live_wpm: bool,
#[arg(long = "hide-notifications")]
pub hide_notifications: bool,
#[arg(long = "hide-hostname")]
pub hide_hostname: bool,
#[arg(long = "no-save")]
pub no_save: bool,
#[arg(long = "reset")]
pub reset: bool,
}
impl Cli {
pub fn validate(&self) -> Result<(), String> {
if let Some(t) = self.time {
if t < MIN_CUSTOM_TIME as u64 || t > MAX_CUSTOM_TIME as u64 {
return Err(format!(
"Time must be between {} and {} seconds",
MIN_CUSTOM_TIME, MAX_CUSTOM_TIME
));
}
}
if let Some(c) = self.words_count {
if !(MIN_CUSTOM_WORD_COUNT..=MAX_CUSTOM_WORD_COUNT).contains(&c) {
return Err(format!(
"Word count must be between {} and {}",
MIN_CUSTOM_WORD_COUNT, MAX_CUSTOM_WORD_COUNT
));
}
}
if let Some(c) = &self.words {
if !(MIN_CUSTOM_WORD_COUNT..=MAX_CUSTOM_WORD_COUNT).contains(&c.len()) {
return Err(format!(
"Word count must be between {} and {}",
MIN_CUSTOM_WORD_COUNT, MAX_CUSTOM_WORD_COUNT
));
}
}
Ok(())
}
pub fn clear_reset_flag(&mut self) {
self.reset = false;
}
pub fn clear_custom_words_flag(&mut self) {
if self.words.is_some() {
self.words = None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_default() {
let cli = Cli::default();
assert!(cli.validate().is_ok());
}
#[test]
fn test_validate_valid_time() {
let cli = Cli {
time: Some(30),
words_count: None,
..Default::default()
};
assert!(cli.validate().is_ok());
let cli = Cli {
time: Some(0),
words_count: None,
..Default::default()
};
assert!(cli.validate().is_err());
assert_eq!(
cli.validate().unwrap_err(),
"Time must be between 1 and 300 seconds"
);
let cli = Cli {
time: Some(301),
words_count: None,
..Default::default()
};
assert!(cli.validate().is_err());
}
#[test]
fn test_validate_valid_count() {
let cli = Cli {
time: None,
words_count: Some(100),
..Default::default()
};
assert!(cli.validate().is_ok());
let cli = Cli {
time: None,
words_count: Some(0),
..Default::default()
};
assert!(cli.validate().is_err());
assert_eq!(
cli.validate().unwrap_err(),
"Word count must be between 1 and 5000"
);
let cli = Cli {
time: None,
words_count: Some(5001),
..Default::default()
};
assert!(cli.validate().is_err());
}
#[test]
fn test_validate_words_should_respect_count_rules() {
let normal_text = "*".repeat(MAX_CUSTOM_WORD_COUNT);
let empty_text = "".to_string();
let long_text = "*".repeat(MAX_CUSTOM_WORD_COUNT + 1);
let cli = Cli {
time: None,
words: Some(normal_text),
..Default::default()
};
assert!(cli.validate().is_ok());
let cli = Cli {
time: None,
words: Some(empty_text),
..Default::default()
};
assert!(cli.validate().is_err());
assert_eq!(
cli.validate().unwrap_err(),
"Word count must be between 1 and 5000"
);
let cli = Cli {
time: None,
words: Some(long_text.clone()),
..Default::default()
};
assert!(cli.validate().is_err());
assert_eq!(
cli.validate().unwrap_err(),
"Word count must be between 1 and 5000"
);
}
#[test]
fn test_reset_flag() {
let cli = Cli {
reset: true,
..Default::default()
};
assert!(cli.reset);
}
}