Repository: pacak/bpaf (rust)

Issue:
instead of panicking on a stranger short options - parse them
Current code would split `-Obits=2048` into `Obits` and `2048` which fails, splitting it into `O` and `bits=2048` is strictly more useful.


see https://github.com/pacak/bpaf/discussions/120

Pull request description:
handle stranger things like -Obits=2048 without crashig
Fixes #121

Patch:
diff --git a/Changelog.md b/Changelog.md
index be3abf1..d2dd48d 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,7 +2,8 @@
 
 ## bpaf [0.7.3] - unreleased
 - `try_run`
-  thanks to @ysndr
+   thanks to @ysndr
+- `-Obits=2048` is now parsed as short flag `O` with a value of `bits=2048` instad of crashing
 
 ## bpaf [0.7.2] - 2022-10-27
 - drop tainting logic, should be redundant
diff --git a/src/arg.rs b/src/arg.rs
index 9e5c9b5..688f245 100644
--- a/src/arg.rs
+++ b/src/arg.rs
@@ -216,7 +216,17 @@ pub(crate) fn split_os_argument(input: &std::ffi::OsStr) -> Option<(ArgType, Str
         // keep collecting until = or the end of the input
         loop {
             match items.next() {
-                Some(EQUALS) => break,
+                Some(EQUALS) => {
+                    if ty == ArgType::Short && name.len() > 1 {
+                        let mut body = name.drain(1..).collect::<Vec<_>>();
+                        body.push(EQUALS);
+                        body.extend(items);
+                        name.truncate(1);
+                        let os = Arg::Word(os_from_vec(body));
+                        return Some((ty, str_from_vec(name)?, Some(os)));
+                    }
+                    break;
+                }
                 Some(val) => name.push(val),
                 None => {
                     if name.is_empty() {
@@ -241,7 +251,7 @@ pub(crate) fn split_os_argument(input: &std::ffi::OsStr) -> Option<(ArgType, Str
 }
 
 /// similar to [`split_os_argument`] but only works for utf8 values, used as a fallback function
-/// on non
+/// on non windows/unix OSes
 #[cfg(any(all(not(windows), not(unix)), test))]
 pub(crate) fn split_os_argument_fallback(
     input: &std::ffi::OsStr,
@@ -271,7 +281,18 @@ pub(crate) fn split_os_argument_fallback(
     // if it's a flag
     loop {
         match chars.next() {
-            Some('=') => break,
+            Some('=') => {
+                if ty == ArgType::Short && name.len() > 1 {
+                    let mut body = name.drain(1..).collect::<String>();
+                    body.push('=');
+                    body.extend(chars);
+                    name.truncate(1);
+                    let os = Arg::Word(OsString::from(body));
+                    return Some((ty, name, Some(os)));
+                }
+                break;
+            }
+
             Some(val) => name.push(val),
             None => {
                 if name.is_empty() {


Test patch:
diff --git a/src/tests.rs b/src/tests.rs
index db84428..781847e 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -74,6 +74,15 @@ fn wtf_shenanigans_2() {
     }
 }
 
+#[test]
+fn fallback_with_strange_args() {
+    use crate::args::{split_os_argument, split_os_argument_fallback};
+    let s = std::ffi::OsString::from("-Obits=2048");
+    let r1 = split_os_argument(&s);
+    let r2 = split_os_argument_fallback(&s);
+    assert_eq!(r1, r2);
+}
+
 #[test]
 fn different_methods_of_args_cration_are_identical() {
     use crate::Args;
diff --git a/tests/tests.rs b/tests/tests.rs
index 2f4f0a0..12980ec 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1796,3 +1796,10 @@ Available options:
 "
     );
 }
+
+#[test]
+fn strange_short_option() {
+    let parser = short('O').argument::<String>("ARG").to_options();
+    let r = parser.run_inner(Args::from(&["-Obits=2048"])).unwrap();
+    assert_eq!(r, "bits=2048");
+}