arg.rs (3302B) - raw
1 // Extremely minimalist command line interface, inspired by 2 // [sbase](https://git.suckless.org/sbase/)'s 3 // [arg.h](https://git.suckless.org/sbase/file/arg.h.html) 4 // 5 // I believe this has basically the same behavior, which is: 6 // * flags can be grouped (-abc) 7 // * missing arg -> print usage, exit 8 // * invalid flag -> print usage, exit 9 // 10 // Unlike arg.h, does not support -kvalue arguments (although it could with 11 // some modification 12 // 13 // This is, of course, aggressively minimalist, perhaps even too much so. 14 // Feel free to modify to meet your needs. 15 // 16 // Goals are: 17 // 1. As simple as possible 18 // 2. No use of macros 19 // 20 // Copy/paste this code and you have a CLI! No library needed! 21 // 22 // This file is licensed under the terms of 0BSD: 23 // 24 // Permission to use, copy, modify, and/or distribute this software for any purpose with or without 25 // fee is hereby granted. 26 // 27 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 28 // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 29 // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 30 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 31 // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 32 // OF THIS SOFTWARE. 33 34 use std::env; 35 use std::ffi::OsString; 36 use std::os::unix::ffi::OsStrExt; 37 use std::path::PathBuf; 38 use std::process::exit; 39 use std::str::FromStr; 40 41 // Example usage 42 fn main() { 43 let args = Args::from_env(); 44 println!( 45 "a: {}, b: {}, f: {:?}, positional: {:?}", 46 args.a, args.b, args.f, args.positional 47 ) 48 } 49 50 fn usage() -> ! { 51 let name = env::args().next().unwrap(); 52 eprintln!( 53 "usage: {} [-a] [-f FOO] [-b BAR] FOOBAR... 54 55 FOOBAR: Variable number of FOOBARs 56 57 FLAGS: 58 -a turn on a flag 59 60 ARGS: 61 -f filepath arg f set to FOO 62 -b integer arg b set to BAR. Default: 7 63 ", 64 name 65 ); 66 exit(1) 67 } 68 69 #[derive(Default)] 70 pub struct Args { 71 pub a: bool, 72 pub f: PathBuf, 73 pub b: i32, 74 pub positional: Vec<OsString>, 75 } 76 77 impl Args { 78 pub fn default() -> Self { 79 Args { 80 b: 7, 81 ..Default::default() 82 } 83 } 84 85 pub fn from_env() -> Self { 86 let mut out = Self::default(); 87 let mut args = env::args_os().skip(1); 88 while let Some(arg) = args.next() { 89 let s = arg.to_string_lossy(); 90 let mut ch_iter = s.chars(); 91 if ch_iter.next() != Some('-') { 92 out.positional.push(arg); 93 continue; 94 } 95 ch_iter.for_each(|m| match m { 96 // Edit these lines // 97 'a' => out.a = true, 98 'f' => out.f = parse_os_arg(args.next()), 99 'b' => out.b = parse_arg(args.next()), 100 // Stop editing // 101 _ => usage(), 102 }) 103 } 104 out 105 } 106 } 107 108 fn parse_arg<T: FromStr>(a: Option<OsString>) -> T { 109 a.and_then(|a| a.into_string().ok()) 110 .and_then(|a| a.parse().ok()) 111 .unwrap_or_else(|| usage()) 112 } 113 114 fn parse_os_arg<T: From<OsString>>(a: Option<OsString>) -> T { 115 match a { 116 Some(s) => T::from(s), 117 None => usage(), 118 } 119 }