#![allow(dead_code)] extern crate app; #[macro_use] extern crate stderr; use app::{App, AppError, Args, Cmd, Opt, OptTypo, OptValue, OptValueParse}; trait IsParse { fn is_parse(&self) -> bool; } impl IsParse for AppError { fn is_parse(&self) -> bool { match *self { AppError::Parse(_) => true, _ => false, } } } impl IsParse for Result<(), AppError> { fn is_parse(&self) -> bool { match *self { Ok(_) => false, Err(ref e) => e.is_parse(), } } } // cargo t -- --nocapture #[test] fn inner() { errln!("pkg!: {:?}", pkg!()); fun("", Err(AppError::Parse(String::new())), Fht2p::default()); fun( "/path0 -p 8080 -p 8000 -p 80 /path1 -k /path2 --user Loli,16,./ -V r", Err(AppError::Version), Fht2p::default(), ); fun( "/path0 -p 8080 -p 8000 -p 80 /path1 -k /path2 --user Loli,16,./ r -V", Err(AppError::Parse(String::new())), Fht2p::default(), ); fun( "/path0 -p 8080 -p 8000 -p 80 /path1 -k /path2 --user Loli,16,./ -h", Err(AppError::Help(None)), Fht2p::default(), ); fun( "src/ -p 8080 -p 8000 -p 80 tests/ -k examples/ --user Loli,16,./ .git run -home $HOME -h", Err(AppError::Help(Some("run".to_owned()))), Fht2p::default(), ); fun( "/path0 -p 8080 -p 8000 -p 80 /path1 -k /path2 --user Loli,16,./ b -h", Err(AppError::Help(Some("build".to_owned()))), Fht2p::default(), ); fun( "src -p 8080 -p 8000 -p 80 examples -k tests --user Loli,16,./", Ok(()), Fht2p::new( vec![8080, 8000, 80u32], true, vec!["src", "examples", "tests"], User::new("Loli", 16, "./"), Run::default(), Build::default(), ), ); fun( "src -p 8080 -p 8000 -p 80 examples -k tests", Ok(()), Fht2p::new( vec![8080, 8000, 80u32], true, vec!["src", "examples", "tests"], User::new("", 0, ""), Run::default(), Build::default(), ), ); fun( "src -p 8080 -p 8000 -p 80 examples -k tests --user Loli,16,./ r --home $HOME", Ok(()), Fht2p::new( vec![8080, 8000, 80u32], true, vec!["src", "examples", "tests"], User::new("Loli", 16, "./"), Run::new("$HOME", false), Build::default(), ), ); fun( "src -p 8080 -p 8000 -p 80 examples -k tests --user Loli,16,./ build -r sec ssx", Ok(()), Fht2p::new( vec![8080, 8000, 80u32], true, vec!["src", "examples", "tests"], User::new("Loli", 16, "./"), Run::default(), Build::new(true, vec!["sec", "ssx"]), ), ); } fn fun(msg: &str, rest: Result<(), AppError>, value: Fht2p) { let args: Vec = msg.split_whitespace().map(|s| s.to_string()).collect(); let mut fht2p = Fht2p::default(); println!("parse-before: {:?}", fht2p); let rest_parse = { App::new("fht2p") .version("0.5.0") .desc("A HTTP Server for Static File.") .author("Wspsxing", "biluohc@qq.com") .author("Xyz.org", "moz@mio.org") .addr("GitHub", "https://biluohc.github.com/fht2p") .opt( Opt::new("keep-alive", &mut fht2p.keep_alive) .short('k') .long("keep-alive") .help("open keep-alive"), ) .opt( Opt::new("ports", &mut fht2p.ports) .short('p') .long("port") .help("Sets listenning port"), ) .opt( Opt::new("user", &mut fht2p.user) .short('u') .long("user") .optional() .help("Sets user information"), ) .args(Args::new("PATHS", &mut fht2p.dirs).help(r#"Sets the path to share"#)) .cmd( Cmd::new("run") .short("r") .desc("run the sub_cmd") .opt( Opt::new("home", &mut fht2p.run.home) .short('H') .long("home") .help("running in the home"), ) .opt( Opt::new("log", &mut fht2p.run.log) .long("long") .short('l') .help("running and print log"), ), ) .cmd( Cmd::new("build") .short("b") .desc("build the file") .opt( Opt::new("release", &mut fht2p.build.release) .short('r') .long("release") .help("Build artifacts in release mode, with optimizations"), ) .args(Args::new("File", &mut fht2p.build.files).help("File to build")), ) .parse_strings(&args[..]) }; dbln!("msg_args: {:?}", args); dbln!("rest: {:?}\t\trest_parse: {:?}", rest, rest_parse); dbln!("fht2p_value: {:?}", value); dbln!("fht2p_parse: {:?}", fht2p); dbln!(); if rest.is_ok() && rest_parse.is_ok() { assert_eq!(value, fht2p); } else if rest.is_parse() { } else { assert_eq!(rest, rest_parse); } } #[derive(Debug, Default, PartialEq)] struct Fht2p { ports: Vec, keep_alive: bool, dirs: Vec, user: User, run: Run, build: Build, } impl Fht2p { fn new(ports: Vec, keep_alive: bool, dirs: Vec<&str>, u: User, r: Run, b: Build) -> Self { let vs: Vec = dirs.iter().map(|s| s.to_string()).collect(); Fht2p { ports: ports, keep_alive: keep_alive, dirs: vs, user: u, run: r, build: b, } } } #[derive(Debug, Default, PartialEq)] struct Run { home: String, log: bool, } impl Run { fn new(home: &str, log: bool) -> Self { Run { home: home.to_owned(), log: log, } } } #[derive(Debug, Default, PartialEq)] struct Build { release: bool, files: Vec, } impl Build { fn new(r: bool, files: Vec<&str>) -> Self { let vs: Vec = files.iter().map(|s| s.to_string()).collect(); Build { release: r, files: vs, } } } #[derive(Debug, Default, PartialEq)] struct User { name: String, age: u8, address: String, } impl User { fn new(name: &str, age: u8, addr: &str) -> Self { User { name: name.to_owned(), age: age, address: addr.to_owned(), } } } /// Custom `OptValue` by impl `OptValueParse` impl<'app, 's: 'app> OptValueParse<'app> for &'s mut User { fn into(self) -> OptValue<'app> { OptValue::new(Box::from(self)) } fn is_bool(&self) -> bool { false } fn default(&self) -> Option { if self.name.is_empty() { None } else { Some(format!("{},{},{}", self.name, self.age, self.address)) } } fn parse(&mut self, opt_name: &str, msg: &str, count: &mut usize, typo: &mut OptTypo) -> Result<(), String> { if *count == 0 || typo.is_covered() || typo.is_multiple() { self.name.clear(); self.address.clear(); let vs: Vec<&str> = msg.split(',').filter(|s| !s.is_empty()).collect(); if vs.len() != 3 || vs[0].trim().is_empty() { return Err(format!( "OPTION(<{}>) parse fails: \"{}\"", opt_name, msg )); } self.name.push_str(vs[0]); self.age = vs[1] .parse::() .map_err(|_| format!("OPTION(<{}>) parse fails: \"{}\"", opt_name, msg))?; self.address.push_str(vs[2]); } else if typo.is_single() { Err(format!( "OPTION(<{}>) can only occurs once, but second: {:?}", opt_name, msg ))?; } Ok(()) } /// env::arg could is `""` fn check(&self, opt_name: &str, optional: &bool, count: &usize, _: &OptTypo) -> Result<(), String> { if !optional && *count == 0 && self.default().is_none() { Err(format!("OPTION(<{}>) missing", opt_name))?; } Ok(()) } }