:: commit 249b17332a82ffc0161596cd82e69da225e7fe13

Kamila Szewczyk <kspalaiologos@gmail.com> — 2024-12-16 14:53

parents: fd258cde2b

rip out getopt and replace it with yarg.

yarg is a public domain argument parser written by the author of bzip3.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd6d070..4ac224c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -88,10 +88,6 @@ install(
 if(BZIP3_BUILD_APPS)
   add_executable(bzip3)
   target_sources(bzip3 PRIVATE src/main.c)
-  check_symbol_exists(getopt_long "getopt.h" HAVE_GETOPT_LONG)
-  if(HAVE_GETOPT_LONG)
-    target_compile_definitions(bzip3 PRIVATE HAVE_GETOPT_LONG)
-  endif()
   if(BZIP3_ENABLE_STATIC_EXE)
     if(BUILD_SHARED_LIBS)
       message(
diff --git a/Makefile.am b/Makefile.am
index 26db061..f7ca2c9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,7 +9,7 @@ pkgconfig_DATA = bzip3.pc
 include_HEADERS = include/libbz3.h
 noinst_HEADERS = include/common.h \
 						 include/libsais.h \
-						 include/getopt-shim.h
+						 include/yarg.h
 
 lib_LTLIBRARIES = libbzip3.la
 libbzip3_la_SOURCES = src/libbz3.c
diff --git a/README.md b/README.md
index e77abed..f3934e7 100644
--- a/README.md
+++ b/README.md
@@ -133,7 +133,6 @@ A breakdown of components and their licenses follows:
 - (compile-time) `build-aux/ax_check_compile_flag.m4`: Copyright 2008, Guido U. Draheim (guidod@gmx.de), 2011, Maarten Bosmans (mkbosmans@gmail.com); FSFAP
 - (compile-time) `build-aux/git-version-gen`: Copyright 2007-2012, Free Software Foundation, Inc; GPLv3
 - (runtime) `bz3grep`: Copyright 2003, Thomas Klausner; BSD-2-clause
-- (runtime) `include/getopt-shim.h`: Copyright 2005-2014, Rich Felker; Expat
 
 `bzip3` as a whole is licensed under LGPLv3 only. It is not dual-licensed under LGPLv3 and Apache 2.0.
 
diff --git a/configure.ac b/configure.ac
index af7c136..b846095 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,9 +16,6 @@ LT_INIT
 PKG_PROG_PKG_CONFIG
 PKG_INSTALLDIR
 
-AC_CHECK_HEADERS([getopt.h])
-AC_CHECK_FUNCS([getopt_long])
-
 AC_C_RESTRICT
 
 AC_ARG_WITH([pthread],
diff --git a/include/getopt-shim.h b/include/getopt-shim.h
deleted file mode 100644
index 6a7a9db..0000000
--- a/include/getopt-shim.h
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
-  Copyright 2005-2014 Rich Felker, et al.
-
-  Permission is hereby granted, free of charge, to any person obtaining
-  a copy of this software and associated documentation files (the
-  "Software"), to deal in the Software without restriction, including
-  without limitation the rights to use, copy, modify, merge, publish,
-  distribute, sublicense, and/or sell copies of the Software, and to
-  permit persons to whom the Software is furnished to do so, subject to
-  the following conditions:
-
-  The above copyright notice and this permission notice shall be
-  included in all copies or substantial portions of the Software.
-
-  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-#ifndef _GETOPT_H
-#define _GETOPT_H
-
-#ifdef WIN32
-static void flockfile(FILE * f) { _lock_file(f); }
-static void funlockfile(FILE * f) { _unlock_file(f); }
-#endif
-
-int getopt(int, char * const[], const char *);
-extern char * optarg;
-extern int optind, opterr, optopt, optreset;
-
-struct option {
-    const char * name;
-    int has_arg;
-    int * flag;
-    int val;
-};
-
-int getopt_long(int, char * const *, const char *, const struct option *, int *);
-int getopt_long_only(int, char * const *, const char *, const struct option *, int *);
-
-#define no_argument 0
-#define required_argument 1
-#define optional_argument 2
-
-char * optarg;
-int optind = 1, opterr = 1, optopt, __optpos, optreset = 0;
-
-#define optpos __optpos
-
-static void __getopt_msg(const char * a, const char * b, const char * c, size_t l) {
-    FILE * f = stderr;
-    flockfile(f);
-    fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l && putc('\n', f);
-    funlockfile(f);
-}
-
-int getopt(int argc, char * const argv[], const char * optstring) {
-    int i, c, d;
-    int k, l;
-    char * optchar;
-
-    if (!optind || optreset) {
-        optreset = 0;
-        __optpos = 0;
-        optind = 1;
-    }
-
-    if (optind >= argc || !argv[optind]) return -1;
-
-    if (argv[optind][0] != '-') {
-        if (optstring[0] == '-') {
-            optarg = argv[optind++];
-            return 1;
-        }
-        return -1;
-    }
-
-    if (!argv[optind][1]) return -1;
-
-    if (argv[optind][1] == '-' && !argv[optind][2]) return optind++, -1;
-
-    if (!optpos) optpos++;
-    c = argv[optind][optpos], k = 1;
-    optchar = argv[optind] + optpos;
-    optopt = c;
-    optpos += k;
-
-    if (!argv[optind][optpos]) {
-        optind++;
-        optpos = 0;
-    }
-
-    if (optstring[0] == '-' || optstring[0] == '+') optstring++;
-
-    i = 0;
-    d = 0;
-    do {
-        d = optstring[i], l = 1;
-        if (l > 0)
-            i += l;
-        else
-            i++;
-    } while (l && d != c);
-
-    if (d != c) {
-        if (optstring[0] != ':' && opterr) __getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
-        return '?';
-    }
-    if (optstring[i] == ':') {
-        if (optstring[i + 1] == ':')
-            optarg = 0;
-        else if (optind >= argc) {
-            if (optstring[0] == ':') return ':';
-            if (opterr) __getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
-            return '?';
-        }
-        if (optstring[i + 1] != ':' || optpos) {
-            optarg = argv[optind++] + optpos;
-            optpos = 0;
-        }
-    }
-    return c;
-}
-
-static void permute(char * const * argv, int dest, int src) {
-    char ** av = (char **)argv;
-    char * tmp = av[src];
-    int i;
-    for (i = src; i > dest; i--) av[i] = av[i - 1];
-    av[dest] = tmp;
-}
-
-static int __getopt_long_core(int argc, char * const * argv, const char * optstring, const struct option * longopts,
-                              int * idx, int longonly) {
-    optarg = 0;
-    if (longopts && argv[optind][0] == '-' &&
-        ((longonly && argv[optind][1] && argv[optind][1] != '-') || (argv[optind][1] == '-' && argv[optind][2]))) {
-        int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
-        int i, cnt, match;
-        char * opt;
-        for (cnt = i = 0; longopts[i].name; i++) {
-            const char * name = longopts[i].name;
-            opt = argv[optind] + 1;
-            if (*opt == '-') opt++;
-            for (; *name && *name == *opt; name++, opt++)
-                ;
-            if (*opt && *opt != '=') continue;
-            match = i;
-            if (!*name) {
-                cnt = 1;
-                break;
-            }
-            cnt++;
-        }
-        if (cnt == 1) {
-            i = match;
-            optind++;
-            optopt = longopts[i].val;
-            if (*opt == '=') {
-                if (!longopts[i].has_arg) {
-                    if (colon || !opterr) return '?';
-                    __getopt_msg(argv[0], ": option does not take an argument: ", longopts[i].name,
-                                 strlen(longopts[i].name));
-                    return '?';
-                }
-                optarg = opt + 1;
-            } else if (longopts[i].has_arg == required_argument) {
-                if (!(optarg = argv[optind])) {
-                    if (colon) return ':';
-                    if (!opterr) return '?';
-                    __getopt_msg(argv[0], ": option requires an argument: ", longopts[i].name,
-                                 strlen(longopts[i].name));
-                    return '?';
-                }
-                optind++;
-            }
-            if (idx) *idx = i;
-            if (longopts[i].flag) {
-                *longopts[i].flag = longopts[i].val;
-                return 0;
-            }
-            return longopts[i].val;
-        }
-        if (argv[optind][1] == '-') {
-            if (!colon && opterr)
-                __getopt_msg(argv[0], cnt ? ": option is ambiguous: " : ": unrecognized option: ", argv[optind] + 2,
-                             strlen(argv[optind] + 2));
-            optind++;
-            return '?';
-        }
-    }
-    return getopt(argc, argv, optstring);
-}
-
-static int __getopt_long(int argc, char * const * argv, const char * optstring, const struct option * longopts,
-                         int * idx, int longonly) {
-    int ret, skipped, resumed;
-    if (!optind || optreset) {
-        optreset = 0;
-        __optpos = 0;
-        optind = 1;
-    }
-    if (optind >= argc || !argv[optind]) return -1;
-    skipped = optind;
-    if (optstring[0] != '+' && optstring[0] != '-') {
-        int i;
-        for (i = optind;; i++) {
-            if (i >= argc || !argv[i]) return -1;
-            if (argv[i][0] == '-' && argv[i][1]) break;
-        }
-        optind = i;
-    }
-    resumed = optind;
-    ret = __getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
-    if (resumed > skipped) {
-        int i, cnt = optind - resumed;
-        for (i = 0; i < cnt; i++) permute(argv, skipped, optind - 1);
-        optind = skipped + cnt;
-    }
-    return ret;
-}
-
-int getopt_long(int argc, char * const * argv, const char * optstring, const struct option * longopts, int * idx) {
-    return __getopt_long(argc, argv, optstring, longopts, idx, 0);
-}
-
-int getopt_long_only(int argc, char * const * argv, const char * optstring, const struct option * longopts, int * idx) {
-    return __getopt_long(argc, argv, optstring, longopts, idx, 1);
-}
-
-#endif
diff --git a/include/yarg.h b/include/yarg.h
new file mode 100644
index 0000000..8ce9028
--- /dev/null
+++ b/include/yarg.h
@@ -0,0 +1,297 @@
+/* Written by Kamila Szewczyk (kspalaiologos@gmail.com) */
+
+#ifndef _YARG_H
+#define _YARG_H
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+typedef enum {
+  no_argument,
+  required_argument,
+  optional_argument
+} yarg_arg_type;
+
+typedef struct {
+  int opt;
+  yarg_arg_type type;
+  const char * long_opt;
+} yarg_options;
+
+typedef enum {
+  YARG_STYLE_WINDOWS,
+  YARG_STYLE_UNIX,
+  YARG_STYLE_UNIX_SHORT
+} yarg_style;
+
+typedef struct {
+  bool dash_dash;
+  yarg_style style;
+} yarg_settings;
+
+typedef struct {
+  int opt;
+  const char * long_opt;
+  char * arg;
+} yarg_option;
+
+typedef struct {
+  yarg_option * args;
+  int argc;
+  char ** pos_args;
+  int pos_argc;
+  char * error;
+} yarg_result;
+
+static void * yarg_alloc(size_t size) {
+  void * ptr = calloc(size, 1);
+  if (!ptr) { perror("calloc"); exit(1); }
+  return ptr;
+}
+
+static int yarg_asprintf(char ** strp, const char * fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  int len = vsnprintf(NULL, 0, fmt, ap);
+  va_end(ap);
+  if (len < 0) return -1;
+  *strp = (char *) malloc(len + 1);
+  if (!*strp) return -1;
+  va_start(ap, fmt);
+  len = vsnprintf(*strp, len + 1, fmt, ap);
+  va_end(ap);
+  return len;
+}
+
+static char * yarg_strdup(const char * str) {
+  char * new_str = (char *) yarg_alloc(strlen(str) + 1);
+  strcpy(new_str, str);
+  return new_str;
+}
+
+static void yarg_parse_unix(int argc, char * argv[], yarg_options opt[],
+                            yarg_result * res, bool dash_dash) {
+  int no_args = 0, no_pos_args = 0;
+  for (int i = 1; i < argc; i++) {
+    if (argv[i][0] == '-') {
+      if (argv[i][1] == '-') {
+        if (dash_dash && argv[i][2] == '\0')
+          { no_pos_args += argc - i - 1; break; }
+        char * long_opt = argv[i] + 2; yarg_options * o = NULL;
+        int len = 0; while (long_opt[len] && long_opt[len] != '=') len++;
+        for (int j = 0; opt[j].opt; j++)
+          if (opt[j].long_opt && !strncmp(opt[j].long_opt, long_opt, len))
+            { o = &opt[j]; break; }
+        if (!o) {
+          asprintf(&res->error, "--%.*s -- unknown option\n", len, long_opt);
+          return;
+        }
+        if (o->type == required_argument) {
+          if (long_opt[len] == '=') {
+            // Ignore.
+          } else if (argv[i + 1] && argv[i + 1][0] != '-') {
+            i++;
+          } else {
+            asprintf(&res->error, "--%s -- missing argument\n", o->long_opt);
+            return;
+          }
+        } else if (o->type == optional_argument) {
+          if (long_opt[len] == '=') {
+          } else if (argv[i + 1] && argv[i + 1][0] != '-') {
+            i++;
+          }
+        }
+        no_args++;
+      } else {
+        for (int j = 1; argv[i][j]; j++) {
+          char c = argv[i][j]; yarg_options * o = NULL;
+          for (int k = 0; opt[k].opt; k++)
+            if (opt[k].opt == c)
+              { o = &opt[k]; break; }
+          if (!o) {
+            asprintf(&res->error, "-%c -- unknown option\n", c);
+            return;
+          }
+          if (o->type == required_argument) {
+            if (argv[i][j + 1]) {
+              // Ignore.
+            } else if (argv[i + 1] && argv[i + 1][0] != '-') {
+              i++;
+            } else {
+              asprintf(&res->error, "-%c -- missing argument\n", c);
+              return;
+            }
+            no_args++;
+            break;
+          } else if(o->type == optional_argument) {
+            if (argv[i][j + 1]) {
+              // Ignore.
+              no_args++;
+              break;
+            } else if (argv[i + 1] && argv[i + 1][0] != '-') {
+              i++;
+              no_args++;
+              break;
+            }
+          }
+          no_args++;
+        }
+      }
+    } else no_pos_args++;
+  }
+
+  res->args = (yarg_option *) yarg_alloc((no_args + 1) * sizeof(yarg_option));
+  res->pos_args = (char **) yarg_alloc((no_pos_args + 1) * sizeof(char *));
+
+  for (int i = 1; i < argc; i++) {
+    if (argv[i][0] == '-') {
+      if (argv[i][1] == '-') {
+        if (dash_dash && argv[i][2] == '\0') {
+          for (int j = i + 1; j < argc; j++)
+            res->pos_args[res->pos_argc++] = yarg_strdup(argv[j]);
+          break;
+        }
+        char * long_opt = argv[i] + 2; yarg_options * o = NULL;
+        int len = 0; while (long_opt[len] && long_opt[len] != '=') len++;
+        for (int j = 0; opt[j].opt; j++)
+          if (opt[j].long_opt && !strncmp(opt[j].long_opt, long_opt, len))
+            { o = &opt[j]; break; }
+        res->args[res->argc].opt = o->opt;
+        res->args[res->argc].long_opt = o->long_opt;
+        if (o->type == required_argument || o->type == optional_argument) {
+          if (long_opt[len] == '=') {
+            res->args[res->argc].arg = yarg_strdup(long_opt + len + 1);
+          } else if (argv[i + 1] && argv[i + 1][0] != '-') {
+            res->args[res->argc].arg = yarg_strdup(argv[++i]);
+          }
+        }
+        res->argc++;
+      } else {
+        for (int j = 1; argv[i][j]; j++) {
+          char c = argv[i][j]; yarg_options * o = NULL;
+          for (int k = 0; opt[k].opt; k++)
+            if (opt[k].opt == c)
+              { o = &opt[k]; break; }
+          if (!o) {
+            asprintf(&res->error, "-%c -- unknown option\n", c);
+            return;
+          }
+          res->args[res->argc].opt = c;
+          res->args[res->argc].long_opt = o->long_opt;
+          if (o->type == required_argument || o->type == optional_argument) {
+            if (argv[i][j + 1]) {
+              res->args[res->argc++].arg = yarg_strdup(argv[i] + j + 1);
+              break;
+            } else if (argv[i + 1] && argv[i + 1][0] != '-') {
+              res->args[res->argc++].arg = yarg_strdup(argv[++i]);
+              break;
+            }
+          }
+          res->argc++;
+        }
+      }
+    } else res->pos_args[res->pos_argc++] = yarg_strdup(argv[i]);
+  }
+}
+
+static void yarg_parse_unix_short(int argc, char * argv[], yarg_options opt[],
+                                  yarg_result * res, bool dash_dash, char opt_char) {
+  int no_args = 0, no_pos_args = 0;
+  for (int i = 1; i < argc; i++) {
+    if (argv[i][0] == opt_char) {
+      if (dash_dash && argv[i][1] == '\0') {
+        no_pos_args += argc - i - 1;
+        break;
+      }
+      char * long_opt = argv[i] + 1; yarg_options * o = NULL;
+      int len = 0; while (long_opt[len] && long_opt[len] != '=') len++;
+      for (int j = 0; opt[j].opt; j++)
+        if (opt[j].long_opt && !strncmp(opt[j].long_opt, long_opt, len))
+          { o = &opt[j]; break; }
+      if (!o) {
+        asprintf(&res->error, "%c%.*s -- unknown option\n", opt_char, len, long_opt);
+        return;
+      }
+      if (o->type == required_argument) {
+        if (long_opt[len] == '=') {
+          // Ignore.
+        } else if (argv[i + 1] && argv[i + 1][0] != opt_char) {
+          i++;
+        } else {
+          asprintf(&res->error, "%c%s -- missing argument\n", opt_char, o->long_opt);
+          return;
+        }
+      } else if (o->type == optional_argument) {
+        if (long_opt[len] == '=') {
+          // Ignore.
+        } else if (argv[i + 1] && argv[i + 1][0] != opt_char) {
+          i++;
+        }
+      }
+      no_args++;
+    } else no_pos_args++;
+  }
+  
+  res->args = (yarg_option *) yarg_alloc((no_args + 1) * sizeof(yarg_option));
+  res->pos_args = (char **) yarg_alloc((no_pos_args + 1) * sizeof(char *));
+
+  for (int i = 1; i < argc; i++) {
+    if (argv[i][0] == opt_char) {
+      if (dash_dash && argv[i][1] == '\0') {
+        for (int j = i + 1; j < argc; j++)
+          res->pos_args[res->pos_argc++] = yarg_strdup(argv[j]);
+        break;
+      }
+      char * long_opt = argv[i] + 1; yarg_options * o = NULL;
+      int len = 0; while (long_opt[len] && long_opt[len] != '=') len++;
+      for (int j = 0; opt[j].opt; j++)
+        if (opt[j].long_opt && !strncmp(opt[j].long_opt, long_opt, len))
+          { o = &opt[j]; break; }
+      res->args[res->argc].opt = o->opt;
+      res->args[res->argc].long_opt = o->long_opt;
+      if (o->type == required_argument || o->type == optional_argument) {
+        if (long_opt[len] == '=') {
+          res->args[res->argc].arg = yarg_strdup(long_opt + len + 1);
+        } else if (argv[i + 1] && argv[i + 1][0] != opt_char) {
+          res->args[res->argc].arg = yarg_strdup(argv[++i]);
+        }
+      }
+      res->argc++;
+    } else res->pos_args[res->pos_argc++] = yarg_strdup(argv[i]);
+  }
+}
+
+void yarg_destroy(yarg_result * r) {
+  for (int i = 0; i < r->argc; i++) {
+    free(r->args[i].arg);
+  }
+  free(r->args);
+  for (int i = 0; i < r->pos_argc; i++) {
+    free(r->pos_args[i]);
+  }
+  free(r->pos_args);
+  free(r->error);
+  free(r);
+}
+
+yarg_result * yarg_parse(int argc, char * argv[], yarg_options opt[], yarg_settings settings) {
+  yarg_result * res = (yarg_result *) yarg_alloc(sizeof(yarg_result));
+  switch (settings.style) {
+    case YARG_STYLE_WINDOWS:
+      yarg_parse_unix_short(argc, argv, opt, res, false, '/');
+      break;
+    case YARG_STYLE_UNIX:
+      yarg_parse_unix(argc, argv, opt, res, settings.dash_dash);
+      break;
+    case YARG_STYLE_UNIX_SHORT:
+      yarg_parse_unix_short(argc, argv, opt, res, settings.dash_dash, '-');
+      break;
+  }
+  return res;
+}
+
+#endif
diff --git a/src/main.c b/src/main.c
index cb7108b..17116f3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -27,12 +27,6 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#ifdef HAVE_GETOPT_LONG
-    #include <getopt.h>
-#else
-    #include "getopt-shim.h"
-#endif
-
 #if defined __MSVCRT__
     #include <fcntl.h>
     #include <io.h>
@@ -40,6 +34,7 @@
 
 #include "common.h"
 #include "libbz3.h"
+#include "yarg.h"
 
 #define MODE_DECODE 0
 #define MODE_ENCODE 1
@@ -561,91 +556,68 @@ int main(int argc, char * argv[]) {
     // the block size
     u32 block_size = MiB(16);
 
-#ifdef PTHREAD
-    const char * short_options = "Bb:cdefhj:krtvVz";
-#else
-    const char * short_options = "Bb:cdefhkrtvVz";
-#endif
-
     enum { RM_OPTION = CHAR_MAX + 1 };
 
-    static struct option long_options[] = { { "encode", no_argument, 0, 'e' },
-                                            { "decode", no_argument, 0, 'd' },
-                                            { "test", no_argument, 0, 't' },
-                                            { "stdout", no_argument, 0, 'c' },
-                                            { "force", no_argument, 0, 'f' },
-                                            { "recover", no_argument, 0, 'r' },
-                                            { "help", no_argument, 0, 'h' },
-                                            { "rm", no_argument, 0, RM_OPTION },
-                                            { "keep", no_argument, 0, 'k' },
-                                            { "version", no_argument, 0, 'V' },
-                                            { "verbose", no_argument, 0, 'v' },
-                                            { "block", required_argument, 0, 'b' },
-                                            { "batch", no_argument, 0, 'B' },
+    yarg_options opt[] = {
+        {       'e', no_argument,       "encode" },
+        {       'z', no_argument,       "encode" }, /* alias */
+        {       'd', no_argument,       "decode" },
+        {       't', no_argument,       "test" },
+        {       'c', no_argument,       "stdout" },
+        {       'f', no_argument,       "force" },
+        {       'r', no_argument,       "recover" },
+        {       'h', no_argument,       "help" },
+        { RM_OPTION, no_argument,       "rm" },
+        {       'k', no_argument,       "keep" },
+        {       'V', no_argument,       "version" },
+        {       'v', no_argument,       "verbose" },
+        {       'b', required_argument, "block" },
+        {       'B', no_argument,       "batch" },
 #ifdef PTHREAD
-                                            { "jobs", required_argument, 0, 'j' },
+        {       'j', required_argument, "jobs" },
 #endif
-                                            { 0, 0, 0, 0 } };
-
-    while (1) {
-        int option_index = 0;
-        int c = getopt_long(argc, argv, short_options, long_options, &option_index);
-        if (c == -1) break;
-
-        switch (c) {
-            case '?':
-                fprintf(stderr, "Try 'bzip3 --help' for more information.\n");
-                return 1;
-            case 'e':
-            case 'z':
-                mode = MODE_ENCODE;
-                break;
-            case 'd':
-                mode = MODE_DECODE;
-                break;
-            case 'r':
-                mode = MODE_RECOVER;
-                break;
-            case 't':
-                mode = MODE_TEST;
-                break;
-            case 'c':
-                force_stdstreams = 1;
-                break;
-            case 'f':
-                force = 1;
-                break;
-            case RM_OPTION:
-                remove_input_file = 1;
-                break;
-            case 'k':
-                break;
-            case 'h':
-                help();
-                return 0;
-            case 'V':
-                version();
-                return 0;
-            case 'B':
-                batch = 1;
-                break;
-            case 'v':
-                verbose = 1;
-                break;
+        {         0, no_argument,       NULL }
+    };
+    yarg_settings settings = {
+        .dash_dash = true,
+        .style = YARG_STYLE_UNIX,
+    };
+    yarg_result * res = yarg_parse(argc, argv, opt, settings);
+    if (res->error) {
+        fputs(res->error, stderr);
+        fputs("Try 'bzip3 --help' for more information.\n", stderr);
+        return 1;
+    }
+    // `res' is not freed later on as it has the approximate lifetime
+    // equal to the lifetime of the program overall.
+    for (int i = 0; i < res->argc; i++) {
+        switch(res->args[i].opt) {
+            case 'e': case 'z': mode = MODE_ENCODE; break;
+            case 'd': mode = MODE_DECODE; break;
+            case 'r': mode = MODE_RECOVER; break;
+            case 't': mode = MODE_TEST; break;
+            case 'c': force_stdstreams = 1; break;
+            case 'f': force = 1; break;
+            case RM_OPTION: remove_input_file = 1; break;
+            case 'k': break;
+            case 'h': help(); return 0;
+            case 'V': version(); return 0;
+            case 'B': batch = 1; break;
+            case 'v': verbose = 1; break;
             case 'b':
-                if (!is_numeric(optarg)) {
-                    fprintf(stderr, "bzip3: invalid block size: %s\n", optarg);
+                if (!is_numeric(res->args[i].arg)) {
+                    fprintf(stderr, "bzip3: invalid block size: %s\n", res->args[i].arg);
                     return 1;
                 }
-                block_size = MiB(atoi(optarg));
+                block_size = MiB(atoi(res->args[i].arg));
                 break;
 #ifdef PTHREAD
             case 'j':
-                if (!is_numeric(optarg)) {
-                    fprintf(stderr, "bzip3: invalid amount of jobs: %s\n", optarg);
+                if (!is_numeric(res->args[i].arg)) {
+                    fprintf(stderr, "bzip3: invalid amount of jobs: %s\n", res->args[i].arg);
                     return 1;
                 }
-                workers = atoi(optarg);
+                workers = atoi(res->args[i].arg);
                 break;
 #endif
         }
@@ -665,8 +637,8 @@ int main(int argc, char * argv[]) {
         switch (mode) {
             case MODE_ENCODE:
                 /* Encode each of the files. */
-                while (optind < argc) {
-                    char * arg = argv[optind++];
+                for (int i = 0; i < res->pos_argc; i++) {
+                    char * arg = res->pos_args[i];
 
                     FILE * input_des = open_input(arg);
                     char * output_name;
@@ -692,8 +664,8 @@ int main(int argc, char * argv[]) {
             case MODE_RECOVER:
             case MODE_DECODE:
                 /* Decode each of the files. */
-                while (optind < argc) {
-                    char * arg = argv[optind++];
+                for (int i = 0; i < res->pos_argc; i++) {
+                    char * arg = res->pos_args[i];
 
                     FILE * input_des = open_input(arg);
                     char * output_name;
@@ -723,8 +695,8 @@ int main(int argc, char * argv[]) {
                 break;
             case MODE_TEST:
                 /* Test each of the files. */
-                while (optind < argc) {
-                    char * arg = argv[optind++];
+                for (int i = 0; i < res->pos_argc; i++) {
+                    char * arg = res->pos_args[i];
 
                     FILE * input_des = open_input(arg);
                     process(input_des, NULL, mode, block_size, workers, verbose, arg);
@@ -741,9 +713,8 @@ int main(int argc, char * argv[]) {
         return 0;
     }
 
-    while (optind < argc) {
-        // Positional argument. Likely a file name.
-        char * arg = argv[optind++];
+    for (int i = 0; i < res->pos_argc; i++) {
+        char * arg = res->pos_args[i];
 
         if (f1 != NULL && f2 != NULL) {
             fprintf(stderr, "Error: too many files specified.\n");
@@ -819,6 +790,5 @@ int main(int argc, char * argv[]) {
     if (remove_input_file) {
         remove_in_file(input, output_des);
     }
-
     return r;
 }
\ No newline at end of file
tab: 248 wrap: offon