misc

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.alexwennerberg.com/misc
Log | Files | Refs | README | LICENSE

commit 60b29f71046403860488862a7fd8f5ae77068766
parent e388a24af038e201332b16f8bf9f6739bfa7a001
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Sat, 19 Feb 2022 16:38:56 -0800

Update arg.rs

Diffstat:
Marg.rs | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
1 file changed, 88 insertions(+), 42 deletions(-)

diff --git a/arg.rs b/arg.rs @@ -1,3 +1,15 @@ +// This file is licensed under the terms of 0BSD: +// +// Permission to use, copy, modify, and/or distribute this software for any purpose with or without +// fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +// OF THIS SOFTWARE. + // Extremely minimalist command line interface, inspired by // [sbase](https://git.suckless.org/sbase/)'s // [arg.h](https://git.suckless.org/sbase/file/arg.h.html) @@ -7,66 +19,100 @@ // * missing arg -> print usage, exit // * invalid flag -> print usage, exit // -// This is, of course, aggressively minimalist, perhaps even too much so. +// This is, of course, aggressively minimalist, perhaps even too much so. It is a WIP, and I'm +// working on refining it to meet more use cases +// +// Goals are: +// +// 1. As simple as possible +// 2. No use of macros // // Copy/paste this code and you have a CLI! No library needed! use std::env; +use std::ffi::OsString; +use std::os::unix::ffi::OsStrExt; +use std::path::PathBuf; use std::process::exit; +use std::str::FromStr; + +// Example usage +fn main() { + let args = Args::from_env(); + println!( + "a: {}, b: {}, f: {:?}, positional: {:?}", + args.a, args.b, args.f, args.positional + ) +} fn usage() -> ! { + let name = env::args().next().unwrap(); eprintln!( - r#"./main [-ab] [-c var] [positional] + "usage: {} [-a] [-f FOO] [-b BAR] FOOBAR... + +FOOBAR: Variable number of FOOBARs --a enables a flag --b enables b flag --c NUM sets var c to NUM --d STR sets var c to STR - "# +FLAGS: +-a turn on a flag + +ARGS: +-f filepath arg f set to FOO +-b integer arg b set to BAR. Default: 7 +", + name ); exit(1) } -fn main() { - // Initialize your variables here; - // Modify as needed - let mut flags = String::new(); - let mut positional: Option<String> = None; // TODO vec - let mut var: Option<i32> = None; // TODO btree? - - let mut args = env::args().skip(1); - - let parsenext = - |a: Option<String>| Some(a.and_then(|a| a.parse().ok()).unwrap_or_else(|| usage())); +#[derive(Default)] +pub struct Args { + pub a: bool, + pub f: PathBuf, + pub b: i32, + pub positional: Vec<OsString>, +} - while let Some(arg) = args.next() { - let mut chars = arg.chars(); - if chars.next() != Some('-') { - positional = Some(arg); - continue; +impl Args { + pub fn default() -> Self { + Args { + b: 7, + ..Default::default() } - chars.for_each(|m| match m { - 'c' => var = parsenext(args.next()), - 'a' | 'b' => flags.push(m), - _ => { - usage(); + } + + pub fn from_env() -> Self { + let mut out = Self::default(); + let mut args = env::args_os().skip(1); + while let Some(arg) = args.next() { + let mut bytes = arg.as_os_str().as_bytes(); + if bytes[0] != b'-' { + out.positional.push(arg); + continue; } - }) + bytes[1..].iter().for_each(|m| match m { + // Edit these lines // + b'a' => out.a = true, + b'f' => out.f = parse_os_arg(args.next()), + b'b' => out.b = parse_arg(args.next()), + // Stop editing // + _ => { + usage(); + } + }) + } + out } +} - // Check mandatory arguments +fn parse_arg<T: FromStr>(a: Option<OsString>) -> T { + a.and_then(|a| a.into_string().ok()) + .and_then(|a| a.parse().ok()) + .unwrap_or_else(|| usage()) +} - // Check values of variables, use to direct behavior - if flags.contains('a') { - println!("a enabled"); - } - if flags.contains('b') { - println!("b enabled"); - } - if let Some(v) = var { - println!("var: {}", v); - } - if let Some(p) = positional { - println!("positional: {}", p); +fn parse_os_arg<T: From<OsString>>(a: Option<OsString>) -> T { + match a { + Some(s) => T::from(s), + None => usage(), } }