1#![deny(missing_docs)]
2
3use std::ffi::OsString;
13use std::path::PathBuf;
14#[cfg(not(feature = "meval"))]
15use std::str::FromStr;
16
17pub mod guide;
18
19#[doc(hidden)]
20pub use auto_args_derive::*;
21
22fn align_tabs(inp: &str) -> String {
23 let mut out = String::with_capacity(inp.len());
24 let mut stop1 = 0;
25 let mut stop2 = 0;
26 for l in inp.lines() {
27 let v: Vec<_> = l.splitn(3, '\t').collect();
28 if v.len() > 2 {
29 stop1 = std::cmp::max(stop1, v[0].len() + 2);
30 stop2 = std::cmp::max(stop2, v[1].len() + 1);
31 }
32 }
33 for l in inp.lines() {
35 let v: Vec<_> = l.splitn(3, '\t').collect();
36 if v.len() > 2 {
37 out.push_str(&format!(
38 "{:a$}{:b$}{}\n",
39 v[0],
40 v[1],
41 v[2],
42 a = stop1,
43 b = stop2
44 ));
45 } else {
46 out.push_str(l);
47 out.push('\n');
48 }
49 }
50 out
51}
52
53pub trait AutoArgs: Sized {
56 fn from_args() -> Self {
60 let mut v: Vec<_> = std::env::args_os().collect();
61 v.remove(0);
62 if v.iter().any(|v| v == "--help") {
63 println!("{}", Self::help());
64 std::process::exit(0);
65 }
66 match Self::parse_vec(v) {
67 Ok(val) => val,
68 Err(e) => {
69 println!("error: {}\n", e);
70 println!("{}", Self::usage());
71 std::process::exit(1)
72 }
73 }
74 }
75 fn parse_vec(mut args: Vec<OsString>) -> Result<Self, Error> {
80 let v = Self::parse_internal("", &mut args)?;
81 if args.len() > 0 {
82 Err(Error::UnexpectedOption(format!("{:?}", args)))
83 } else {
84 Ok(v)
85 }
86 }
87 fn from_iter<I, T>(args: I) -> Result<Self, Error>
89 where
90 I: IntoIterator<Item = T>,
91 T: Into<OsString> + Clone,
92 {
93 let mut v: Vec<_> = args.into_iter().map(|v| v.into()).collect();
94 v.remove(0);
95 Self::parse_vec(v)
96 }
97 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error>;
104 const REQUIRES_INPUT: bool;
109 fn tiny_help_message(key: &str) -> String;
111 fn help_message(key: &str, doc: &str) -> String {
113 format!("\t{}\t{}", Self::tiny_help_message(key), doc)
114 }
115 fn usage() -> String {
117 format!(
118 "USAGE:
119 {} {}
120
121For more information try --help",
122 std::env::args_os()
123 .next()
124 .unwrap()
125 .to_string_lossy()
126 .rsplit("/")
127 .next()
128 .unwrap()
129 .to_string(),
130 Self::tiny_help_message("")
131 )
132 }
133 fn help() -> String {
135 format!(
136 "USAGE:
137 {} {}
138
139{}
140
141For more information try --help",
142 std::env::args_os()
143 .next()
144 .unwrap()
145 .to_string_lossy()
146 .rsplit("/")
147 .next()
148 .unwrap()
149 .to_string(),
150 Self::tiny_help_message(""),
151 align_tabs(&Self::help_message("", ""))
152 )
153 }
154}
155
156#[derive(Clone, Debug, PartialEq)]
158pub enum Error {
159 OptionValueParsingFailed(String, String),
161
162 InvalidUTF8(String),
164
165 OptionWithoutAValue(String),
167
168 MissingOption(String),
170
171 UnexpectedOption(String),
173}
174
175impl std::fmt::Display for Error {
176 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
177 match self {
178 Error::OptionValueParsingFailed(key, e) => {
179 write!(f, "error parsing option '{}': {}", key, e)
180 }
181 Error::MissingOption(key) => {
182 write!(f, "the required '{}' option is missing", key)
183 }
184 Error::InvalidUTF8(e) => {
185 write!(f, "invalid UTF-8: '{}'", e)
186 }
187 Error::OptionWithoutAValue(key) => {
188 write!(f, "the option '{}' is missing a value", key)
189 }
190 Error::UnexpectedOption(o) => {
191 write!(f, "unexpected option: {}", o)
192 }
193 }
194 }
195}
196
197impl std::error::Error for Error {}
198
199macro_rules! impl_from_osstr {
200 ($t:ty, $tyname:expr, $conv:expr) => {
201 impl AutoArgs for $t {
202 const REQUIRES_INPUT: bool = true;
203 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
204 let convert = $conv;
205 if key == "" {
206 if args.len() == 0 {
207 Err(Error::MissingOption("".to_string()))
208 } else {
209 let arg = if args[0] == "--" {
210 if args.len() > 1 {
211 args.remove(1)
212 } else {
213 return Err(Error::OptionWithoutAValue("".to_string()));
214 }
215 } else {
216 args.remove(0)
217 };
218 convert(arg)
219 }
220 } else {
221 let eqthing = format!("{}=", key);
222 if let Some(i) = args
223 .iter()
224 .position(|v| v == key || v.to_string_lossy().starts_with(&eqthing))
225 {
226 let thing = args
227 .remove(i)
228 .into_string()
229 .map_err(|e| Error::InvalidUTF8(format!("{:?}", e)))?;
230 if thing == key {
231 if args.len() > i {
232 convert(args.remove(i))
233 } else {
234 Err(Error::OptionWithoutAValue(key.to_string()))
235 }
236 } else {
237 convert(thing.split_at(eqthing.len()).1.into())
238 }
239 } else {
240 Err(Error::MissingOption(key.to_string()))
241 }
242 }
243 }
244 fn tiny_help_message(key: &str) -> String {
245 if key == "" {
246 "STRING".to_string()
247 } else {
248 format!("{} STRING", key)
249 }
250 }
251 }
252
253 impl AutoArgs for Vec<$t> {
254 const REQUIRES_INPUT: bool = false;
255 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
256 let mut res: Self = Vec::new();
257 loop {
258 match <$t>::parse_internal(key, args) {
259 Ok(the_arg) => {
260 res.push(the_arg);
261 }
262 Err(Error::MissingOption(_)) => {
263 return Ok(res);
264 }
265 Err(e) => {
266 return Err(e);
267 }
268 }
269 }
270 }
271 fn tiny_help_message(key: &str) -> String {
272 if key == "" {
273 format!("{}...", $tyname)
274 } else {
275 format!("{} {} ...", key, $tyname)
276 }
277 }
278 }
279 };
280}
281
282impl_from_osstr!(String, "STRING", |osstring: OsString| {
283 osstring
284 .into_string()
285 .map_err(|e| Error::InvalidUTF8(format!("{:?}", e)))
286});
287impl_from_osstr!(PathBuf, "PATH", |osstring: OsString| {
288 Ok(osstring.into())
289});
290
291impl AutoArgs for bool {
292 const REQUIRES_INPUT: bool = false;
293 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
294 if key == "" {
295 if args.len() == 0 {
296 Err(Error::OptionWithoutAValue("".to_string()))
297 } else {
298 if args[0] == "--" {
299 return Err(Error::OptionWithoutAValue("bool".to_string()));
300 }
301 let arg = args.remove(0);
302 if arg == "false" {
303 Ok(false)
304 } else if arg == "true" {
305 Ok(true)
306 } else {
307 Err(Error::MissingOption("bool".to_string()))
308 }
309 }
310 } else {
311 if args
312 .iter()
313 .filter(|v| v.to_string_lossy() == key)
314 .next()
315 .is_some()
316 {
317 *args = args
318 .iter()
319 .filter(|v| v.to_string_lossy() != key)
320 .cloned()
321 .collect();
322 Ok(true)
323 } else {
324 Ok(false)
325 }
326 }
327 }
328 fn tiny_help_message(key: &str) -> String {
329 if key == "" {
330 "(true|false)".to_string()
331 } else {
332 format!("[{}]", key)
333 }
334 }
335}
336
337impl<T: AutoArgs> AutoArgs for Option<T> {
338 const REQUIRES_INPUT: bool = false;
339 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
340 Ok(T::parse_internal(key, args).ok())
341 }
342 fn tiny_help_message(key: &str) -> String {
343 format!("[{}]", T::tiny_help_message(key))
344 }
345}
346
347macro_rules! impl_from {
348 ($t:ty, $tyname:expr) => {
349 impl AutoArgs for $t {
350 const REQUIRES_INPUT: bool = true;
351 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
352 use std::str::FromStr;
353 let the_arg = String::parse_internal(key, args)?;
354 match Self::from_str(&the_arg) {
355 Ok(val) => Ok(val),
356 Err(e) => {
357 let e = Err(Error::OptionValueParsingFailed(
358 key.to_string(),
359 e.to_string(),
360 ));
361 #[cfg(feature = "meval")]
362 let out = if let Ok(x) = meval::eval_str(&the_arg) {
363 if (x as $t) as f64 == x {
364 Ok(x as $t)
365 } else {
366 e
367 }
368 } else {
369 e
370 };
371 #[cfg(not(feature = "meval"))]
372 let out = e;
373 out
374 }
375 }
376 }
377 fn tiny_help_message(key: &str) -> String {
378 if key == "" {
379 $tyname.to_string()
380 } else {
381 format!("{} {}", key, $tyname)
382 }
383 }
384 }
385
386 impl AutoArgs for Vec<$t> {
387 const REQUIRES_INPUT: bool = false;
388 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
389 let mut res: Self = Vec::new();
390 loop {
391 match <$t>::parse_internal(key, args) {
392 Ok(val) => {
393 res.push(val);
394 }
395 Err(Error::MissingOption(_)) => {
396 return Ok(res);
397 }
398 Err(e) => {
399 return Err(e);
400 }
401 }
402 }
403 }
404 fn tiny_help_message(key: &str) -> String {
405 if key == "" {
406 format!("{}...", $tyname.to_string())
407 } else {
408 format!("{} {} ...", key, $tyname)
409 }
410 }
411 }
412 };
413}
414
415impl_from!(u8, "u8");
416impl_from!(u16, "u16");
417impl_from!(u32, "u32");
418impl_from!(u64, "u64");
419impl_from!(usize, "usize");
420
421impl_from!(i8, "i8");
422impl_from!(i16, "i16");
423impl_from!(i32, "i32");
424impl_from!(i64, "i64");
425impl_from!(isize, "isize");
426
427impl AutoArgs for f64 {
428 const REQUIRES_INPUT: bool = true;
429 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
430 let the_arg = String::parse_internal(key, args)?;
431 #[cfg(feature = "meval")]
432 let value = meval::eval_str(the_arg)
433 .map_err(|e| Error::OptionValueParsingFailed(key.to_string(), e.to_string()));
434 #[cfg(not(feature = "meval"))]
435 let value = f64::from_str(&the_arg)
436 .map_err(|e| Error::OptionValueParsingFailed(key.to_string(), e.to_string()));
437 value
438 }
439 fn tiny_help_message(key: &str) -> String {
440 if key == "" {
441 "FLOAT".to_string()
442 } else {
443 format!("{} FLOAT", key)
444 }
445 }
446}
447
448impl AutoArgs for Vec<f64> {
449 const REQUIRES_INPUT: bool = false;
450 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
451 let mut res: Self = Vec::new();
452 loop {
453 match <f64>::parse_internal(key, args) {
454 Ok(val) => {
455 res.push(val);
456 }
457 Err(Error::MissingOption(_)) => {
458 return Ok(res);
459 }
460 Err(e) => {
461 return Err(e);
462 }
463 }
464 }
465 }
466 fn tiny_help_message(key: &str) -> String {
467 format!("{} ...", f64::tiny_help_message(key))
468 }
469}
470
471impl AutoArgs for f32 {
472 const REQUIRES_INPUT: bool = true;
473 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
474 f64::parse_internal(key, args).map(|v| v as f32)
475 }
476 fn tiny_help_message(key: &str) -> String {
477 f64::tiny_help_message(key)
478 }
479}
480
481impl AutoArgs for Vec<f32> {
482 const REQUIRES_INPUT: bool = false;
483 fn parse_internal(key: &str, args: &mut Vec<OsString>) -> Result<Self, Error> {
484 let mut res: Self = Vec::new();
485 loop {
486 match <f32>::parse_internal(key, args) {
487 Ok(val) => {
488 res.push(val);
489 }
490 Err(Error::MissingOption(_)) => {
491 return Ok(res);
492 }
493 Err(e) => {
494 return Err(e);
495 }
496 }
497 }
498 }
499 fn tiny_help_message(key: &str) -> String {
500 Vec::<f64>::tiny_help_message(key)
501 }
502}
503
504impl<T> AutoArgs for std::marker::PhantomData<T> {
505 const REQUIRES_INPUT: bool = false;
506 fn parse_internal(_key: &str, _args: &mut Vec<OsString>) -> Result<Self, Error> {
507 Ok(std::marker::PhantomData)
508 }
509 fn tiny_help_message(_key: &str) -> String {
510 "".to_string()
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use crate as auto_args;
518 fn should_parse<T: PartialEq + AutoArgs + std::fmt::Debug>(
519 args: &'static [&'static str],
520 key: &'static str,
521 result: T,
522 ) {
523 let mut args: Vec<_> = args.iter().map(|s| OsString::from(s)).collect();
524 assert_eq!(T::parse_internal(key, &mut args).unwrap(), result);
525 }
526 fn should_parse_completely<T: PartialEq + AutoArgs + std::fmt::Debug>(
527 args: &'static [&'static str],
528 key: &'static str,
529 result: T,
530 ) {
531 let mut args: Vec<_> = args.iter().map(|s| OsString::from(s)).collect();
532 assert_eq!(T::parse_internal(key, &mut args).unwrap(), result);
533 if args.len() != 0 {
534 println!("args remaining: {:?}", args);
535 assert_eq!(args.len(), 0);
536 }
537 }
538
539 fn shouldnt_parse<T: PartialEq + AutoArgs + std::fmt::Debug>(
540 args: &'static [&'static str],
541 key: &'static str,
542 ) {
543 let mut args: Vec<_> = args.iter().map(|s| OsString::from(s)).collect();
544 assert!(T::parse_internal(key, &mut args).is_err());
545 }
546
547 #[test]
548 fn hello_world() {
549 let flags = &["--hello", "world", "--bad"];
550 should_parse(flags, "--hello", "world".to_string());
551 shouldnt_parse::<String>(flags, "--helloo");
552 shouldnt_parse::<u8>(flags, "--hello");
553 }
554 #[test]
555 fn hello_world_complete() {
556 let flags = &["--hello", "world"];
557 should_parse_completely(flags, "--hello", "world".to_string());
558 }
559 #[test]
560 fn hello_list() {
561 let flags = &["--hello", "big", "--hello", "bad", "--hello", "wolf"];
562 should_parse(
563 flags,
564 "--hello",
565 vec!["big".to_string(), "bad".to_string(), "wolf".to_string()],
566 );
567 shouldnt_parse::<String>(flags, "--helloo");
568 shouldnt_parse::<u8>(flags, "--hello");
569 }
570 #[test]
571 fn positional_arg() {
572 let flags = &["bad"];
573 should_parse(flags, "", "bad".to_string());
574 }
575 #[test]
576 fn arg_u8() {
577 let flags = &["--hello", "8", "--goodbye", "255", "--bad"];
578 should_parse(flags, "--hello", 8u8);
579 should_parse(flags, "--goodbye", 255u8);
580 shouldnt_parse::<String>(flags, "--helloo");
581 }
582 #[test]
583 fn arg_i32() {
584 let flags = &["--hello", "-100008", "--goodbye", "255", "--bad"];
585 should_parse(flags, "--hello", -100008i32);
586 should_parse(flags, "--hello", -100008i64);
587 should_parse(flags, "--goodbye", 255i32);
588 shouldnt_parse::<String>(flags, "--helloo");
589 shouldnt_parse::<u32>(flags, "--hello");
590 }
591 #[test]
592 fn arg_equal_i32() {
593 let flags = &["--hello=-100008", "--goodbye", "255", "--bad"];
594 should_parse(flags, "--hello", -100008i32);
595 should_parse(flags, "--hello", -100008i64);
596 should_parse(flags, "--goodbye", 255i32);
597 shouldnt_parse::<String>(flags, "--helloo");
598 shouldnt_parse::<u32>(flags, "--hello");
599 }
600 #[test]
601 fn arg_f64() {
602 let flags = &["--hello=3e13", "--goodbye", "2^10", "--bad"];
603 should_parse(flags, "--hello", 3e13);
604 #[cfg(feature = "meval")]
605 should_parse(flags, "--goodbye", 1024.0);
606 shouldnt_parse::<String>(flags, "--helloo");
607 shouldnt_parse::<u32>(flags, "--hello");
608 }
609 #[test]
610 fn arg_pathbuf() {
611 let flags = &["--hello=3e13", "--goodbye", "2^10", "--bad"];
612 should_parse(flags, "--hello", PathBuf::from("3e13"));
613 should_parse(flags, "--goodbye", PathBuf::from("2^10"));
614 shouldnt_parse::<String>(flags, "--helloo");
615 shouldnt_parse::<u32>(flags, "--hello");
616 }
617 #[derive(AutoArgs, PartialEq, Debug)]
618 struct Test {
619 a: String,
620 b: String,
621 }
622 #[test]
623 fn derive_test() {
624 println!("help:\n{}", Test::help_message("", "this is the help"));
625 println!(
626 "help prefix --foo:\n{}",
627 Test::help_message("--foo", "this is the help")
628 );
629 let flags = &["--a=foo", "--b", "bar"];
630 should_parse_completely(
631 flags,
632 "",
633 Test {
634 a: "foo".to_string(),
635 b: "bar".to_string(),
636 },
637 );
638 shouldnt_parse::<String>(flags, "--helloo");
639
640 let foo_flags = &["--foo-a=foo", "--foo-b", "bar"];
641 should_parse_completely(
642 foo_flags,
643 "--foo",
644 Test {
645 a: "foo".to_string(),
646 b: "bar".to_string(),
647 },
648 );
649 shouldnt_parse::<Test>(foo_flags, "");
650 }
651 #[derive(AutoArgs, PartialEq, Debug)]
652 struct Pair<T> {
653 first: T,
654 second: T,
655 }
656 #[test]
657 fn derive_test_pair() {
658 println!(
659 "help:\n{}",
660 Pair::<Test>::help_message("", "this is the help")
661 );
662 let flags = &[
663 "--first-a=a1",
664 "--first-b",
665 "b1",
666 "--second-a",
667 "a2",
668 "--second-b",
669 "b2",
670 ];
671 should_parse_completely(
672 flags,
673 "",
674 Pair {
675 first: Test {
676 a: "a1".to_string(),
677 b: "b1".to_string(),
678 },
679 second: Test {
680 a: "a2".to_string(),
681 b: "b2".to_string(),
682 },
683 },
684 );
685 shouldnt_parse::<String>(flags, "--helloo");
686 assert!(!Pair::<Option<String>>::REQUIRES_INPUT);
687 assert!(Pair::<String>::REQUIRES_INPUT);
688 }
689 #[derive(AutoArgs, PartialEq, Debug)]
690 enum Either<A, B> {
691 Left(A),
692 Right(B),
693 }
694 #[test]
695 fn derive_either() {
696 let flags = &["--left", "37"];
697 should_parse_completely(flags, "", Either::<u8, u16>::Left(37u8));
698 }
699 #[test]
700 fn derive_pair_either() {
701 let flags = &["--first-left", "37", "--second-right", "3"];
702 should_parse_completely(
703 flags,
704 "",
705 Pair {
706 first: Either::Left(37),
707 second: Either::Right(3),
708 },
709 );
710 }
711 #[test]
712 fn derive_either_either() {
713 let flags = &["--right-left", "37"];
714 should_parse_completely(
715 flags,
716 "",
717 Either::<u32, Either<u8, u16>>::Right(Either::Left(37)),
718 );
719 }
720 #[test]
721 fn derive_either_option() {
722 let flags = &["--right-left", "7"];
723 should_parse_completely(
724 flags,
725 "",
726 Either::<u32, Either<u8, Option<u32>>>::Right(Either::Left(7)),
727 );
728
729 let flags = &["--right-right"];
730 should_parse_completely(
731 flags,
732 "",
733 Either::<u32, Either<u8, Option<u32>>>::Right(Either::Right(None)),
734 );
735
736 let flags = &["--right-right", "5"];
737 should_parse_completely(
738 flags,
739 "",
740 Either::<u32, Either<u8, Option<u32>>>::Right(Either::Right(Some(5))),
741 );
742 }
743 #[derive(AutoArgs, PartialEq, Debug)]
744 enum MyEnum {
745 Hello { foo: String, bar: u8 },
746 _Goodbye { baz: String },
747 }
748 #[test]
749 fn derive_myenum() {
750 let flags = &["--hello-foo", "good", "--hello-bar", "7"];
751 should_parse(
752 flags,
753 "",
754 MyEnum::Hello {
755 foo: "good".to_string(),
756 bar: 7,
757 },
758 );
759 }
760 #[test]
761 fn option() {
762 let flags = &["--foo", "good"];
763 should_parse(flags, "--foo", Some("good".to_string()));
764 should_parse(flags, "--bar", Option::<String>::None);
765 assert!(String::REQUIRES_INPUT);
766 assert!(!Option::<String>::REQUIRES_INPUT);
767 }
768 #[derive(AutoArgs, PartialEq, Debug)]
769 struct TupleStruct(usize);
770 #[test]
771 fn tuple_struct() {
772 let flags = &["--foo", "5"];
773 should_parse_completely(flags, "--foo", TupleStruct(5));
774 }
775}