use crate::haskell::{run_haskell_demo, DemoArgs}; use std::ffi::OsString; use tracing::error; pub fn run(args: impl IntoIterator) -> Result<(), i32> { let args: Vec = args .into_iter() .map(|arg| arg.to_string_lossy().into_owned()) .collect(); match parse_command(&args) { Ok(Command::Help) => { print_usage(args.first().map(String::as_str).unwrap_or("integrations")); Ok(()) } Ok(Command::RustCallsHaskell(demo_args)) => match run_haskell_demo(&demo_args) { Ok(output) => { println!("{output}"); Ok(()) } Err(message) => { error!("{message}"); Err(1) } }, Err(message) => { error!("{message}"); print_usage(args.first().map(String::as_str).unwrap_or("integrations")); Err(1) } } } #[derive(Debug, PartialEq, Eq)] enum Command { Help, RustCallsHaskell(DemoArgs), } fn parse_command(args: &[String]) -> Result { match args { [_program] => Ok(Command::Help), [_program, command] if command == "help" || command == "--help" || command == "-h" => { Ok(Command::Help) } [_program, command, rest @ ..] if command == "rust-calls-haskell" => { parse_demo_args(rest).map(Command::RustCallsHaskell) } [_program, command, ..] => Err(format!("unknown command: {command}")), [] => Ok(Command::Help), } } fn parse_demo_args(args: &[String]) -> Result { let name = args .first() .cloned() .unwrap_or_else(|| "Ada".to_string()); let left = parse_i32_arg(args.get(1), "left operand", 7)?; let right = parse_i32_arg(args.get(2), "right operand", 5)?; let library_path = args.get(3).cloned(); Ok(DemoArgs { name, left, right, library_path, }) } fn parse_i32_arg(raw: Option<&String>, label: &str, default: i32) -> Result { match raw { Some(value) => value .parse::() .map_err(|_| format!("invalid {label}: {value}")), None => Ok(default), } } fn print_usage(program: &str) { println!("Usage:"); println!(" {program} rust-calls-haskell [name] [left] [right] [haskell-lib-path]"); println!(); println!("Examples:"); println!(" {program} rust-calls-haskell"); println!(" {program} rust-calls-haskell Grace 8 3"); } #[cfg(test)] mod tests { use super::*; #[test] fn parse_demo_args_uses_defaults() { let args: Vec = Vec::new(); let parsed = parse_demo_args(&args).expect("defaults should parse"); assert_eq!( parsed, DemoArgs { name: "Ada".to_string(), left: 7, right: 5, library_path: None, } ); } #[test] fn parse_demo_args_accepts_explicit_values() { let args = vec![ "Linus".to_string(), "9".to_string(), "4".to_string(), "haskell/dist-newstyle/demo/libinterop_hs.so".to_string(), ]; let parsed = parse_demo_args(&args).expect("explicit args should parse"); assert_eq!( parsed, DemoArgs { name: "Linus".to_string(), left: 9, right: 4, library_path: Some("haskell/dist-newstyle/demo/libinterop_hs.so".to_string()), } ); } #[test] fn parse_command_rejects_unknown_commands() { let args = vec!["integrations".to_string(), "wat".to_string()]; let parsed = parse_command(&args); assert!(parsed.is_err()); } }