use std::env; use crate::constants::{DEFAULT_BREAK_MINS, DEFAULT_WORK_MINS}; pub const FLAG_HELP: &str = "-h"; pub const FLAG_HELP_LONG: &str = "--help"; pub const FLAG_VERSION: &str = "-v"; pub const FLAG_VERSION_LONG: &str = "--version"; pub const FLAG_NOTIFY: &str = "-n"; pub const FLAG_NOTIFY_LONG: &str = "--notify"; pub const FLAG_VISUALIZER: &str = "-z"; pub const FLAG_VISUALIZER_LONG: &str = "--visualizer"; #[derive(Debug, PartialEq, Eq)] pub struct CliConfig { pub work_mins: u32, pub break_mins: u32, pub enable_notifications: bool, pub enable_visualizer: bool, } #[derive(Debug, PartialEq, Eq)] pub enum CliAction { Run(CliConfig), PrintHelp, PrintVersion, Error(String), } pub struct Cli; impl Cli { pub fn parse() -> CliAction { Self::parse_args(env::args().collect()) } pub fn parse_args(args: Vec) -> CliAction { let mut enable_notifications = false; let mut enable_visualizer = false; let mut positional = Vec::new(); for arg in args.into_iter().skip(1) { match arg.as_str() { FLAG_HELP | FLAG_HELP_LONG => return CliAction::PrintHelp, FLAG_VERSION | FLAG_VERSION_LONG => return CliAction::PrintVersion, FLAG_NOTIFY | FLAG_NOTIFY_LONG => enable_notifications = true, FLAG_VISUALIZER | FLAG_VISUALIZER_LONG => enable_visualizer = true, "--" => {} s if s.starts_with('-') => { return CliAction::Error(format!("Unknown option: '{}'", s)); } s => positional.push(s.to_string()), } } let numbers: Vec = positional .iter() .filter_map(|s| s.parse::().ok()) .collect(); let work_mins = numbers.first().copied().unwrap_or(DEFAULT_WORK_MINS); let break_mins = numbers.get(1).copied().unwrap_or(DEFAULT_BREAK_MINS); if numbers.len() > 2 { return CliAction::Error(format!("Unexpected argument: '{}'", positional[2])); } CliAction::Run(CliConfig { work_mins, break_mins, enable_notifications, enable_visualizer, }) } pub fn print_help() { println!("Usage: termato [options] [work_minutes] [break_minutes]"); println!(); println!("Options:"); println!( " {:<19} Enable desktop notifications", format_args!("{}, {}", FLAG_NOTIFY, FLAG_NOTIFY_LONG) ); println!( " {:<19} Display visualizer of playing audio", format_args!("{}, {}", FLAG_VISUALIZER, FLAG_VISUALIZER_LONG) ); println!( " {:<19} Print this help message", format_args!("{}, {}", FLAG_HELP, FLAG_HELP_LONG) ); println!( " {:<19} Print the version number", format_args!("{}, {}", FLAG_VERSION, FLAG_VERSION_LONG) ); println!(); println!( "Defaults: work_minutes = {}, break_minutes = {}", DEFAULT_WORK_MINS, DEFAULT_BREAK_MINS ); } pub fn print_version() { println!("termato version {}", env!("CARGO_PKG_VERSION")); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_defaults() { let args = vec!["termato".to_string()]; assert_eq!( Cli::parse_args(args), CliAction::Run(CliConfig { work_mins: 25, break_mins: 5, enable_notifications: false, enable_visualizer: false, }) ); } #[test] fn test_cargo_run_syntax_with_dot_and_dash_dash() { let args = vec![ "target/debug/termato.exe".to_string(), ".".to_string(), "--".to_string(), "-z".to_string(), ]; assert_eq!( Cli::parse_args(args), CliAction::Run(CliConfig { work_mins: 25, break_mins: 5, enable_notifications: false, enable_visualizer: true, }) ); } #[test] fn test_custom_time_and_flags() { let args = vec![ "termato".to_string(), "-n".to_string(), "-z".to_string(), "50".to_string(), "10".to_string(), ]; assert_eq!( Cli::parse_args(args), CliAction::Run(CliConfig { work_mins: 50, break_mins: 10, enable_notifications: true, enable_visualizer: true, }) ); } #[test] fn test_help_flag() { let args = vec!["termato".to_string(), "--help".to_string()]; assert_eq!(Cli::parse_args(args), CliAction::PrintHelp); } #[test] fn test_unknown_flag() { let args = vec!["termato".to_string(), "--foo".to_string()]; assert_eq!( Cli::parse_args(args), CliAction::Error("Unknown option: '--foo'".to_string()) ); } #[test] fn test_non_numeric_positional_defaults() { let args = vec!["termato".to_string(), "abc".to_string()]; assert_eq!( Cli::parse_args(args), CliAction::Run(CliConfig { work_mins: 25, break_mins: 5, enable_notifications: false, enable_visualizer: false, }) ); } }