use clap::Parser; use clap::builder::ArgPredicate; use harmony; use harmony::{score::Score, topology::Topology}; use inquire::Confirm; #[cfg(feature = "tui")] use harmony_tui; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct Args { /// Run score(s) without prompt #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] yes: bool, /// Filter query #[arg(short, long, conflicts_with = "interactive")] filter: Option, /// Run interactive TUI or not #[arg(short, long, default_value_t = false)] interactive: bool, /// Run all or nth, defaults to all #[arg( short, long, default_value_t = true, default_value_if("number", ArgPredicate::IsPresent, "false"), conflicts_with = "number", conflicts_with = "interactive" )] all: bool, /// Run nth matching, zero indexed #[arg(short, long, default_value_t = 0, conflicts_with = "interactive")] number: usize, /// list scores, will also be affected by run filter #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] list: bool, } fn maestro_scores_filter( maestro: &harmony::maestro::Maestro, all: bool, filter: Option, number: usize, ) -> Vec>> { let scores = maestro.scores(); let scores_read = scores.read().expect("Should be able to read scores"); let mut scores_vec: Vec>> = match filter { Some(f) => scores_read .iter() .filter(|s| s.name().contains(&f)) .map(|s| s.clone_box()) .collect(), None => scores_read.iter().map(|s| s.clone_box()).collect(), }; if !all { let score = scores_vec.get(number); match score { Some(s) => scores_vec = vec![s.clone_box()], None => return vec![], } }; return scores_vec; } // TODO: consider adding doctest for this function fn list_scores_with_index(scores_vec: &Vec>>) -> String { let mut display_str = String::new(); for (i, s) in scores_vec.iter().enumerate() { let name = s.name(); display_str.push_str(&format!("\n{i}: {name}")); } return display_str; } pub async fn init( maestro: harmony::maestro::Maestro, args_struct: Option, ) -> Result<(), Box> { let args = match args_struct { Some(args) => args, None => Args::parse(), }; #[cfg(feature = "tui")] if args.interactive { return harmony_tui::init(maestro).await; } #[cfg(not(feature = "tui"))] if args.interactive { return Err("Not compiled with interactive support".into()); } env_logger::builder().init(); let scores_vec = maestro_scores_filter(&maestro, args.all, args.filter, args.number); if scores_vec.len() == 0 { return Err("No score found".into()); } // if list option is specified, print filtered list and exit if args.list { println!("Available scores:"); println!("{}", list_scores_with_index(&scores_vec)); return Ok(()); } // prompt user if --yes is not specified if !args.yes { let confirmation = Confirm::new( format!( "This will run the following scores: {}\n", list_scores_with_index(&scores_vec) ) .as_str(), ) .with_default(true) .prompt() .expect("Unexpected prompt error"); if !confirmation { return Ok(()); } } // Run filtered scores for s in scores_vec { println!("Running: {}", s.name()); maestro.interpret(s).await?; } Ok(()) } #[cfg(test)] mod test { use harmony::{ inventory::Inventory, maestro::Maestro, modules::dummy::{ErrorScore, PanicScore, SuccessScore}, topology::HAClusterTopology, }; fn init_test_maestro() -> Maestro { let inventory = Inventory::autoload(); let topology = HAClusterTopology::autoload(); let mut maestro = Maestro::new(inventory, topology); maestro.register_all(vec![ Box::new(SuccessScore {}), Box::new(ErrorScore {}), Box::new(PanicScore {}), ]); maestro } #[tokio::test] async fn test_init_success_score() { let maestro = init_test_maestro(); let res = crate::init( maestro, Some(crate::Args { yes: true, filter: Some("SuccessScore".to_owned()), interactive: false, all: true, number: 0, list: false, }), ) .await; assert!(res.is_ok()); } #[tokio::test] async fn test_init_error_score() { let maestro = init_test_maestro(); let res = crate::init( maestro, Some(crate::Args { yes: true, filter: Some("ErrorScore".to_owned()), interactive: false, all: true, number: 0, list: false, }), ) .await; assert!(res.is_err()); } #[tokio::test] async fn test_init_number_score() { let maestro = init_test_maestro(); let res = crate::init( maestro, Some(crate::Args { yes: true, filter: None, interactive: false, all: false, number: 0, list: false, }), ) .await; assert!(res.is_ok()); } #[tokio::test] async fn test_filter_fn_all() { let maestro = init_test_maestro(); let res = crate::maestro_scores_filter(&maestro, true, None, 0); assert!(res.len() == 3); } #[tokio::test] async fn test_filter_fn_all_success() { let maestro = init_test_maestro(); let res = crate::maestro_scores_filter(&maestro, true, Some("Success".to_owned()), 0); assert!(res.len() == 1); assert!( maestro .interpret(res.get(0).unwrap().clone_box()) .await .is_ok() ); } #[tokio::test] async fn test_filter_fn_all_error() { let maestro = init_test_maestro(); let res = crate::maestro_scores_filter(&maestro, true, Some("Error".to_owned()), 0); assert!(res.len() == 1); assert!( maestro .interpret(res.get(0).unwrap().clone_box()) .await .is_err() ); } #[tokio::test] async fn test_filter_fn_all_score() { let maestro = init_test_maestro(); let res = crate::maestro_scores_filter(&maestro, true, Some("Score".to_owned()), 0); assert!(res.len() == 3); assert!( maestro .interpret(res.get(0).unwrap().clone_box()) .await .is_ok() ); assert!( maestro .interpret(res.get(1).unwrap().clone_box()) .await .is_err() ); } #[tokio::test] async fn test_filter_fn_number() { let maestro = init_test_maestro(); let res = crate::maestro_scores_filter(&maestro, false, None, 0); assert!(res.len() == 1); assert!( maestro .interpret(res.get(0).unwrap().clone_box()) .await .is_ok() ); } #[tokio::test] async fn test_filter_fn_number_invalid() { let maestro = init_test_maestro(); let res = crate::maestro_scores_filter(&maestro, false, None, 11); assert!(res.len() == 0); } }