commit 60b29f71046403860488862a7fd8f5ae77068766
parent e388a24af038e201332b16f8bf9f6739bfa7a001
Author: alex wennerberg <alex@alexwennerberg.com>
Date: Sat, 19 Feb 2022 16:38:56 -0800
Update arg.rs
Diffstat:
M | arg.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(),
}
}