Krolyxon
Commit 216f312

Branches

Refs →

nsxiv216f312

add support for long-opts (#332)

Uses [optparse] to add support for long-opts. optparse is posix
compliant with getopt(3) and thus would be backwards compatible.
It does not have any dependency (not even the c standard library!) and
is C89 compatible and thus fits our current code-style.

[optparse]: https://github.com/skeeto/optparse

Note that we're using a couple `pragma`-s to silence some harmless
warnings. This should be portable because these pragma-s don't change the
behavior of the program. Furthermore, C standard mandates that unknown
pragma's should be ignored by the compiler and thus would not result in
build failure on compilers which do not recognize them.
Author
NRK
Date
2022-08-16 10:43 +0200
Parent
d9f20a4
Commit
216f312578be5c959527c03ccc70d27eebcc76aa

+500 −53

4 files changed

  1. M Makefile +1 −1
  2. M etc/nsxiv.1 +28 −28
  3. M options.c +68 −24
  4. A optparse.h +403 −0
M Makefile View
@@ -41,7 +41,7 @@ nsxiv: $(objs)
4141 $(CC) $(CFLAGS) $(nsxiv_cppflags) -c -o $@ $<
4242
4343 $(objs): Makefile config.mk nsxiv.h config.h commands.h
44−options.o: version.h
44+options.o: version.h optparse.h
4545 window.o: icon/data.h utf8.h
4646
4747 config.h:
M etc/nsxiv.1 View
@@ -34,84 +34,84 @@ Please note, that the fullscreen mode requires an EWMH/NetWM-compliant window
3434 manager.
3535 .SH OPTIONS
3636 .TP
37−.BI "\-A " FRAMERATE
37+.BI "\-A, \-\-framerate " FRAMERATE
3838 Play animations with a constant frame rate set to
3939 .IR FRAMERATE .
4040 .TP
41−.B \-a
41+.B "\-a, \-\-animate"
4242 Play animations of multi-frame images.
4343 .TP
44−.B \-b
44+.B "\-b, \-\-no\-bar"
4545 Do not show statusbar at the bottom of the window.
4646 .TP
47−.B \-c
47+.B "\-c, \-\-clean\-cache"
4848 Remove all orphaned cache files from the thumbnail cache directory and exit.
4949 .TP
50−.BI "\-e " WID
50+.BI "\-e, \-\-embed " WID
5151 Embed nsxiv's window into window whose ID is
5252 .IR WID .
5353 .TP
54−.B \-f
54+.B "\-f, \-\-fullscreen"
5555 Start in fullscreen mode.
5656 .TP
57−.BI "\-G " GAMMA
57+.BI "\-G, \-\-gamma " GAMMA
5858 Set image gamma to GAMMA (\-32..32).
5959 .TP
60−.BI "\-g " GEOMETRY
60+.BI "\-g, \-\-geometry " GEOMETRY
6161 Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for
6262 more information on GEOMETRY argument.
6363 .TP
64−.BI "\-N " NAME
65−Set the resource name of nsxiv's X window to NAME.
66−.TP
67−.BI "\-n " NUM
68−Start at picture number NUM.
69−.TP
70−.B \-h
64+.B "\-h, \-\-help"
7165 Print brief usage information to standard output and exit.
7266 .TP
73−.B \-i
67+.B "\-i, \-\-stdin"
7468 Read names of files to open from standard input. Also done if FILE is `-'.
7569 .TP
76−.B \-o
70+.BI "\-N, \-\-class " NAME
71+Set the resource name (WM_CLASS) of nsxiv's X window to NAME.
72+.TP
73+.BI "\-n, \-\-start\-at " NUM
74+Start at picture number NUM.
75+.TP
76+.B "\-o, \-\-stdout"
7777 Write list of all marked files to standard output when quitting. In combination
7878 with
79−.B \-i
79+.B "\-i, \-\-stdin"
8080 nsxiv can be used as a visual filter/pipe.
8181 .TP
82−.B \-p
82+.B "\-p, \-\-private"
8383 Enable private mode, in which nsxiv does not write any cache or temporary files.
8484 .TP
85−.B \-q
85+.B "\-q, \-\-quiet"
8686 Be quiet, and disable warnings to standard error stream.
8787 .TP
88−.B \-r
88+.B "\-r, \-\-recursive"
8989 Search the given directories recursively for images to view.
9090 .TP
91−.BI "\-S " DELAY
91+.BI "\-S, \-\-ss\-delay " DELAY
9292 Start in slideshow mode. Set the delay between images to
9393 .I DELAY
9494 seconds.
9595 .I DELAY
9696 may be a floating-point number.
9797 .TP
98−.BI "\-s " MODE
98+.BI "\-s, \-\-scale\-mode " MODE
9999 Set scale mode according to MODE character. Supported modes are: [d]own,
100100 [f]it, [F]ill, [w]idth, [h]eight.
101101 .TP
102−.B \-t
102+.B "\-t, \-\-thumbnail"
103103 Start in thumbnail mode.
104104 .TP
105−.B \-v
105+.B "\-v, \-\-version"
106106 Print version information to standard output and exit.
107107 .TP
108−.B \-Z
108+.B "\-Z, \-\-zoom\-100"
109109 The same as `\-z 100'.
110110 .TP
111−.BI "\-z " ZOOM
111+.BI "\-z, \-\-zoom " ZOOM
112112 Set zoom level to ZOOM percent.
113113 .TP
114−.B \-0
114+.B "\-0, \-\-null"
115115 Use NULL-separator. With this option, output of \-o and file-list sent to the
116116 key-handler and the input of \-i will be separated by a NULL character.
117117 .SH KEYBOARD COMMANDS
M options.c View
@@ -20,11 +20,20 @@
2020 #include "nsxiv.h"
2121 #include "version.h"
2222
23+#include <assert.h>
2324 #include <stdio.h>
2425 #include <stdlib.h>
2526 #include <string.h>
2627 #include <unistd.h>
2728
29+#define OPTPARSE_IMPLEMENTATION
30+#define OPTPARSE_API static
31+#pragma GCC diagnostic push /* also works on clang */
32+#pragma GCC diagnostic ignored "-Wshadow"
33+#pragma GCC diagnostic ignored "-Wunused-function"
34+#include "optparse.h"
35+#pragma GCC diagnostic pop
36+
2837 const opt_t *options;
2938
3039 void print_usage(void)
@@ -54,12 +63,41 @@ static void print_version(void)
5463
5564 void parse_options(int argc, char **argv)
5665 {
66+ static const struct optparse_long longopts[] = {
67+ { "framerate", 'A', OPTPARSE_REQUIRED },
68+ { "animate", 'a', OPTPARSE_NONE },
69+ { "no-bar", 'b', OPTPARSE_NONE },
70+ { "clean-cache", 'c', OPTPARSE_NONE },
71+ { "embed", 'e', OPTPARSE_REQUIRED },
72+ { "fullscreen", 'f', OPTPARSE_NONE },
73+ { "gamma", 'G', OPTPARSE_REQUIRED },
74+ { "geometry", 'g', OPTPARSE_REQUIRED },
75+ { "help", 'h', OPTPARSE_NONE },
76+ { "stdin", 'i', OPTPARSE_NONE },
77+ { "class", 'N', OPTPARSE_REQUIRED },
78+ { "start-at", 'n', OPTPARSE_REQUIRED },
79+ { "stdout", 'o', OPTPARSE_NONE },
80+ { "private", 'p', OPTPARSE_NONE },
81+ { "quiet", 'q', OPTPARSE_NONE },
82+ { "recursive", 'r', OPTPARSE_NONE },
83+ { "ss-delay", 'S', OPTPARSE_REQUIRED },
84+ { "scale-mode", 's', OPTPARSE_REQUIRED },
85+ { NULL, 'T', OPTPARSE_REQUIRED },
86+ { "thumbnail", 't', OPTPARSE_NONE },
87+ { "version", 'v', OPTPARSE_NONE },
88+ { "zoom-100", 'Z', OPTPARSE_NONE },
89+ { "zoom", 'z', OPTPARSE_REQUIRED },
90+ { "null", '0', OPTPARSE_NONE },
91+ { 0 }, /* end */
92+ };
93+
5794 int n, opt;
5895 char *end, *s;
59− const char *scalemodes = "dfFwh";
96+ struct optparse op;
97+ const char scalemodes[] = "dfFwh"; /* must be sorted according to scalemode_t */
6098 static opt_t _options;
61− options = &_options;
6299
100+ options = &_options;
63101 progname = strrchr(argv[0], '/');
64102 progname = progname ? progname + 1 : argv[0];
65103
@@ -87,15 +125,21 @@ void parse_options(int argc, char **argv)
87125 _options.clean_cache = false;
88126 _options.private_mode = false;
89127
90− while ((opt = getopt(argc, argv, "A:abce:fG:g:hin:N:opqrS:s:T:tvZz:0")) != -1) {
128+ optparse_init(&op, argv);
129+ while ((opt = optparse_long(&op, longopts, NULL)) != -1) {
130+ for (n = 0; n < (int)ARRLEN(longopts); ++n) { /* clang-tidy finds some non-sensical branch and thinks optarg == NULL is possible */
131+ if (opt == longopts[n].shortname && longopts[n].argtype == OPTPARSE_REQUIRED)
132+ assert(op.optarg != NULL);
133+ }
91134 switch (opt) {
92135 case '?':
136+ fprintf(stderr, "%s\n", op.errmsg);
93137 print_usage();
94138 exit(EXIT_FAILURE);
95139 case 'A':
96− n = strtol(optarg, &end, 0);
140+ n = strtol(op.optarg, &end, 0);
97141 if (*end != '\0' || n <= 0)
98− error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", optarg);
142+ error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", op.optarg);
99143 _options.framerate = n;
100144 /* fall through */
101145 case 'a':
@@ -108,22 +152,22 @@ void parse_options(int argc, char **argv)
108152 _options.clean_cache = true;
109153 break;
110154 case 'e':
111− n = strtol(optarg, &end, 0);
155+ n = strtol(op.optarg, &end, 0);
112156 if (*end != '\0')
113− error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", optarg);
157+ error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", op.optarg);
114158 _options.embed = n;
115159 break;
116160 case 'f':
117161 _options.fullscreen = true;
118162 break;
119163 case 'G':
120− n = strtol(optarg, &end, 0);
164+ n = strtol(op.optarg, &end, 0);
121165 if (*end != '\0')
122− error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", optarg);
166+ error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", op.optarg);
123167 _options.gamma = n;
124168 break;
125169 case 'g':
126− _options.geometry = optarg;
170+ _options.geometry = op.optarg;
127171 break;
128172 case 'h':
129173 print_usage();
@@ -132,13 +176,13 @@ void parse_options(int argc, char **argv)
132176 _options.from_stdin = true;
133177 break;
134178 case 'n':
135− n = strtol(optarg, &end, 0);
179+ n = strtol(op.optarg, &end, 0);
136180 if (*end != '\0' || n <= 0)
137− error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", optarg);
181+ error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", op.optarg);
138182 _options.startnum = n - 1;
139183 break;
140184 case 'N':
141− _options.res_name = optarg;
185+ _options.res_name = op.optarg;
142186 break;
143187 case 'o':
144188 _options.to_stdout = true;
@@ -153,15 +197,15 @@ void parse_options(int argc, char **argv)
153197 _options.recursive = true;
154198 break;
155199 case 'S':
156− n = strtof(optarg, &end) * 10;
200+ n = strtof(op.optarg, &end) * 10;
157201 if (*end != '\0' || n <= 0)
158− error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", optarg);
202+ error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", op.optarg);
159203 _options.slideshow = n;
160204 break;
161205 case 's':
162− s = strchr(scalemodes, optarg[0]);
163− if (s == NULL || *s == '\0' || strlen(optarg) != 1)
164− error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", optarg);
206+ s = strchr(scalemodes, op.optarg[0]);
207+ if (s == NULL || *s == '\0' || strlen(op.optarg) != 1)
208+ error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", op.optarg);
165209 _options.scalemode = s - scalemodes;
166210 break;
167211 case 'T':
@@ -175,14 +219,14 @@ void parse_options(int argc, char **argv)
175219 exit(EXIT_SUCCESS);
176220 case 'Z':
177221 _options.scalemode = SCALE_ZOOM;
178− _options.zoom = 1.0;
222+ _options.zoom = 1.0f;
179223 break;
180224 case 'z':
181− n = strtol(optarg, &end, 0);
225+ n = strtol(op.optarg, &end, 0);
182226 if (*end != '\0' || n <= 0)
183− error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", optarg);
227+ error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", op.optarg);
184228 _options.scalemode = SCALE_ZOOM;
185− _options.zoom = (float) n / 100.0;
229+ _options.zoom = (float) n / 100.0f;
186230 break;
187231 case '0':
188232 _options.using_null = true;
@@ -190,8 +234,8 @@ void parse_options(int argc, char **argv)
190234 }
191235 }
192236
193− _options.filenames = argv + optind;
194− _options.filecnt = argc - optind;
237+ _options.filenames = argv + op.optind;
238+ _options.filecnt = argc - op.optind;
195239
196240 if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) {
197241 _options.filenames++;
A optparse.h View
@@ -0,0 +1,403 @@
1+/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
2+ *
3+ * This is free and unencumbered software released into the public domain.
4+ *
5+ * To get the implementation, define OPTPARSE_IMPLEMENTATION.
6+ * Optionally define OPTPARSE_API to control the API's visibility
7+ * and/or linkage (static, __attribute__, __declspec).
8+ *
9+ * The POSIX getopt() option parser has three fatal flaws. These flaws
10+ * are solved by Optparse.
11+ *
12+ * 1) Parser state is stored entirely in global variables, some of
13+ * which are static and inaccessible. This means only one thread can
14+ * use getopt(). It also means it's not possible to recursively parse
15+ * nested sub-arguments while in the middle of argument parsing.
16+ * Optparse fixes this by storing all state on a local struct.
17+ *
18+ * 2) The POSIX standard provides no way to properly reset the parser.
19+ * This means for portable code that getopt() is only good for one
20+ * run, over one argv with one option string. It also means subcommand
21+ * options cannot be processed with getopt(). Most implementations
22+ * provide a method to reset the parser, but it's not portable.
23+ * Optparse provides an optparse_arg() function for stepping over
24+ * subcommands and continuing parsing of options with another option
25+ * string. The Optparse struct itself can be passed around to
26+ * subcommand handlers for additional subcommand option parsing. A
27+ * full reset can be achieved by with an additional optparse_init().
28+ *
29+ * 3) Error messages are printed to stderr. This can be disabled with
30+ * opterr, but the messages themselves are still inaccessible.
31+ * Optparse solves this by writing an error message in its errmsg
32+ * field. The downside to Optparse is that this error message will
33+ * always be in English rather than the current locale.
34+ *
35+ * Optparse should be familiar with anyone accustomed to getopt(), and
36+ * it could be a nearly drop-in replacement. The option string is the
37+ * same and the fields have the same names as the getopt() global
38+ * variables (optarg, optind, optopt).
39+ *
40+ * Optparse also supports GNU-style long options with optparse_long().
41+ * The interface is slightly different and simpler than getopt_long().
42+ *
43+ * By default, argv is permuted as it is parsed, moving non-option
44+ * arguments to the end. This can be disabled by setting the `permute`
45+ * field to 0 after initialization.
46+ */
47+#ifndef OPTPARSE_H
48+#define OPTPARSE_H
49+
50+#ifndef OPTPARSE_API
51+# define OPTPARSE_API
52+#endif
53+
54+struct optparse {
55+ char **argv;
56+ int permute;
57+ int optind;
58+ int optopt;
59+ char *optarg;
60+ char errmsg[64];
61+ int subopt;
62+};
63+
64+enum optparse_argtype {
65+ OPTPARSE_NONE,
66+ OPTPARSE_REQUIRED,
67+ OPTPARSE_OPTIONAL
68+};
69+
70+struct optparse_long {
71+ const char *longname;
72+ int shortname;
73+ enum optparse_argtype argtype;
74+};
75+
76+/**
77+ * Initializes the parser state.
78+ */
79+OPTPARSE_API
80+void optparse_init(struct optparse *options, char **argv);
81+
82+/**
83+ * Read the next option in the argv array.
84+ * @param optstring a getopt()-formatted option string.
85+ * @return the next option character, -1 for done, or '?' for error
86+ *
87+ * Just like getopt(), a character followed by no colons means no
88+ * argument. One colon means the option has a required argument. Two
89+ * colons means the option takes an optional argument.
90+ */
91+OPTPARSE_API
92+int optparse(struct optparse *options, const char *optstring);
93+
94+/**
95+ * Handles GNU-style long options in addition to getopt() options.
96+ * This works a lot like GNU's getopt_long(). The last option in
97+ * longopts must be all zeros, marking the end of the array. The
98+ * longindex argument may be NULL.
99+ */
100+OPTPARSE_API
101+int optparse_long(struct optparse *options,
102+ const struct optparse_long *longopts,
103+ int *longindex);
104+
105+/**
106+ * Used for stepping over non-option arguments.
107+ * @return the next non-option argument, or NULL for no more arguments
108+ *
109+ * Argument parsing can continue with optparse() after using this
110+ * function. That would be used to parse the options for the
111+ * subcommand returned by optparse_arg(). This function allows you to
112+ * ignore the value of optind.
113+ */
114+OPTPARSE_API
115+char *optparse_arg(struct optparse *options);
116+
117+/* Implementation */
118+#ifdef OPTPARSE_IMPLEMENTATION
119+
120+#define OPTPARSE_MSG_INVALID "invalid option"
121+#define OPTPARSE_MSG_MISSING "option requires an argument"
122+#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
123+
124+static int
125+optparse_error(struct optparse *options, const char *msg, const char *data)
126+{
127+ unsigned p = 0;
128+ const char *sep = " -- '";
129+ while (*msg)
130+ options->errmsg[p++] = *msg++;
131+ while (*sep)
132+ options->errmsg[p++] = *sep++;
133+ while (p < sizeof(options->errmsg) - 2 && *data)
134+ options->errmsg[p++] = *data++;
135+ options->errmsg[p++] = '\'';
136+ options->errmsg[p++] = '\0';
137+ return '?';
138+}
139+
140+OPTPARSE_API
141+void
142+optparse_init(struct optparse *options, char **argv)
143+{
144+ options->argv = argv;
145+ options->permute = 1;
146+ options->optind = argv[0] != 0;
147+ options->subopt = 0;
148+ options->optarg = 0;
149+ options->errmsg[0] = '\0';
150+}
151+
152+static int
153+optparse_is_dashdash(const char *arg)
154+{
155+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
156+}
157+
158+static int
159+optparse_is_shortopt(const char *arg)
160+{
161+ return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
162+}
163+
164+static int
165+optparse_is_longopt(const char *arg)
166+{
167+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
168+}
169+
170+static void
171+optparse_permute(struct optparse *options, int index)
172+{
173+ char *nonoption = options->argv[index];
174+ int i;
175+ for (i = index; i < options->optind - 1; i++)
176+ options->argv[i] = options->argv[i + 1];
177+ options->argv[options->optind - 1] = nonoption;
178+}
179+
180+static int
181+optparse_argtype(const char *optstring, char c)
182+{
183+ int count = OPTPARSE_NONE;
184+ if (c == ':')
185+ return -1;
186+ for (; *optstring && c != *optstring; optstring++);
187+ if (!*optstring)
188+ return -1;
189+ if (optstring[1] == ':')
190+ count += optstring[2] == ':' ? 2 : 1;
191+ return count;
192+}
193+
194+OPTPARSE_API
195+int
196+optparse(struct optparse *options, const char *optstring)
197+{
198+ int type;
199+ char *next;
200+ char *option = options->argv[options->optind];
201+ options->errmsg[0] = '\0';
202+ options->optopt = 0;
203+ options->optarg = 0;
204+ if (option == 0) {
205+ return -1;
206+ } else if (optparse_is_dashdash(option)) {
207+ options->optind++; /* consume "--" */
208+ return -1;
209+ } else if (!optparse_is_shortopt(option)) {
210+ if (options->permute) {
211+ int index = options->optind++;
212+ int r = optparse(options, optstring);
213+ optparse_permute(options, index);
214+ options->optind--;
215+ return r;
216+ } else {
217+ return -1;
218+ }
219+ }
220+ option += options->subopt + 1;
221+ options->optopt = option[0];
222+ type = optparse_argtype(optstring, option[0]);
223+ next = options->argv[options->optind + 1];
224+ switch (type) {
225+ case -1: {
226+ char str[2] = {0, 0};
227+ str[0] = option[0];
228+ options->optind++;
229+ return optparse_error(options, OPTPARSE_MSG_INVALID, str);
230+ }
231+ case OPTPARSE_NONE:
232+ if (option[1]) {
233+ options->subopt++;
234+ } else {
235+ options->subopt = 0;
236+ options->optind++;
237+ }
238+ return option[0];
239+ case OPTPARSE_REQUIRED:
240+ options->subopt = 0;
241+ options->optind++;
242+ if (option[1]) {
243+ options->optarg = option + 1;
244+ } else if (next != 0) {
245+ options->optarg = next;
246+ options->optind++;
247+ } else {
248+ char str[2] = {0, 0};
249+ str[0] = option[0];
250+ options->optarg = 0;
251+ return optparse_error(options, OPTPARSE_MSG_MISSING, str);
252+ }
253+ return option[0];
254+ case OPTPARSE_OPTIONAL:
255+ options->subopt = 0;
256+ options->optind++;
257+ if (option[1])
258+ options->optarg = option + 1;
259+ else
260+ options->optarg = 0;
261+ return option[0];
262+ }
263+ return 0;
264+}
265+
266+OPTPARSE_API
267+char *
268+optparse_arg(struct optparse *options)
269+{
270+ char *option = options->argv[options->optind];
271+ options->subopt = 0;
272+ if (option != 0)
273+ options->optind++;
274+ return option;
275+}
276+
277+static int
278+optparse_longopts_end(const struct optparse_long *longopts, int i)
279+{
280+ return !longopts[i].longname && !longopts[i].shortname;
281+}
282+
283+static void
284+optparse_from_long(const struct optparse_long *longopts, char *optstring)
285+{
286+ char *p = optstring;
287+ int i;
288+ for (i = 0; !optparse_longopts_end(longopts, i); i++) {
289+ if (longopts[i].shortname && longopts[i].shortname < 127) {
290+ int a;
291+ *p++ = longopts[i].shortname;
292+ for (a = 0; a < (int)longopts[i].argtype; a++)
293+ *p++ = ':';
294+ }
295+ }
296+ *p = '\0';
297+}
298+
299+/* Unlike strcmp(), handles options containing "=". */
300+static int
301+optparse_longopts_match(const char *longname, const char *option)
302+{
303+ const char *a = option, *n = longname;
304+ if (longname == 0)
305+ return 0;
306+ for (; *a && *n && *a != '='; a++, n++)
307+ if (*a != *n)
308+ return 0;
309+ return *n == '\0' && (*a == '\0' || *a == '=');
310+}
311+
312+/* Return the part after "=", or NULL. */
313+static char *
314+optparse_longopts_arg(char *option)
315+{
316+ for (; *option && *option != '='; option++);
317+ if (*option == '=')
318+ return option + 1;
319+ else
320+ return 0;
321+}
322+
323+static int
324+optparse_long_fallback(struct optparse *options,
325+ const struct optparse_long *longopts,
326+ int *longindex)
327+{
328+ int result;
329+ char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
330+ optparse_from_long(longopts, optstring);
331+ result = optparse(options, optstring);
332+ if (longindex != 0) {
333+ *longindex = -1;
334+ if (result != -1) {
335+ int i;
336+ for (i = 0; !optparse_longopts_end(longopts, i); i++)
337+ if (longopts[i].shortname == options->optopt)
338+ *longindex = i;
339+ }
340+ }
341+ return result;
342+}
343+
344+OPTPARSE_API
345+int
346+optparse_long(struct optparse *options,
347+ const struct optparse_long *longopts,
348+ int *longindex)
349+{
350+ int i;
351+ char *option = options->argv[options->optind];
352+ if (option == 0) {
353+ return -1;
354+ } else if (optparse_is_dashdash(option)) {
355+ options->optind++; /* consume "--" */
356+ return -1;
357+ } else if (optparse_is_shortopt(option)) {
358+ return optparse_long_fallback(options, longopts, longindex);
359+ } else if (!optparse_is_longopt(option)) {
360+ if (options->permute) {
361+ int index = options->optind++;
362+ int r = optparse_long(options, longopts, longindex);
363+ optparse_permute(options, index);
364+ options->optind--;
365+ return r;
366+ } else {
367+ return -1;
368+ }
369+ }
370+
371+ /* Parse as long option. */
372+ options->errmsg[0] = '\0';
373+ options->optopt = 0;
374+ options->optarg = 0;
375+ option += 2; /* skip "--" */
376+ options->optind++;
377+ for (i = 0; !optparse_longopts_end(longopts, i); i++) {
378+ const char *name = longopts[i].longname;
379+ if (optparse_longopts_match(name, option)) {
380+ char *arg;
381+ if (longindex)
382+ *longindex = i;
383+ options->optopt = longopts[i].shortname;
384+ arg = optparse_longopts_arg(option);
385+ if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
386+ return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
387+ } if (arg != 0) {
388+ options->optarg = arg;
389+ } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
390+ options->optarg = options->argv[options->optind];
391+ if (options->optarg == 0)
392+ return optparse_error(options, OPTPARSE_MSG_MISSING, name);
393+ else
394+ options->optind++;
395+ }
396+ return options->optopt;
397+ }
398+ }
399+ return optparse_error(options, OPTPARSE_MSG_INVALID, option);
400+}
401+
402+#endif /* OPTPARSE_IMPLEMENTATION */
403+#endif /* OPTPARSE_H */