Modify the output argv so the user knows what "regular" args they were passed. Also...
[OptFetch.git] / optfetch.c
blob0b8a21d372292da6cb8a709462eec18c51600b15
1 #include "optfetch.h"
2 #include <stdbool.h>
3 #include <string.h>
4 #include <stdio.h>
7 #ifdef DEBUG
8 #define dprintf(...) fprintf(stderr, __VA_ARGS__);
9 #else
10 #define dprintf(...)
11 #endif
14 uint32_t countopts(struct opttype *opts) {
15 uint32_t i = 0;
17 while (true) {
18 // longname and short name are both 0, OR
19 if (((opts[i].longname == NULL) && (opts[i].shortname == 0)) ||
20 // output type was unspecified OR
21 ((opts[i].type == 0) || (opts[i].type > OPTTYPE_STRING)) ||
22 // nowhere to output data!
23 (opts[i].outdata == NULL)) {
24 return i;
27 i++;
30 return 0;
33 int32_t get_option_index_short(char opt, char *potentialopts, uint32_t len) {
34 for (uint32_t i = 0; i < len; i++) {
35 if (potentialopts[i] == 0) {
36 continue;
39 if (potentialopts[i] == opt) {
40 return i;
44 return -1;
47 int32_t get_option_index_long(char *opt, char **potentialopts, uint32_t len) {
48 for (uint32_t i = 0; i < len; i++) {
49 if (potentialopts[i] == NULL) {
50 continue;
53 if (!strcmp(potentialopts[i], opt)) {
54 return i;
58 return -1;
63 signed char fetchopts(uint32_t *argc, char ***argv, struct opttype *opts) {
64 uint32_t numopts = countopts(opts);
65 if (!numopts) {
66 return 0;
68 char *longopts[numopts];
69 char shortopts[numopts];
71 char *curropt;
73 // max 5 digits (%l64u) on windows,
74 // but only 4 (%llu) on unix (plus an EOF)
75 #ifdef _WIN32
76 char format_specifier[6];
77 #else
78 char format_specifier[5];
79 #endif
80 // gotta save that extra byte of memory
82 struct opttype *wasinarg = NULL;
84 // char to take up less memory, unsigned so compiler doesn't complain
85 unsigned char oneoffset;
87 uint32_t argindex, newargc = 0;
89 // new argument variable with the arguments that aren't options
90 char *newargv[*argc];
92 int32_t option_index;
94 // fill these up with opts. That way they're easier to look up
95 for (uint32_t i = 0; i < numopts; i++) {
96 longopts[i] = opts[i].longname;
97 shortopts[i] = opts[i].shortname;
100 // start at 1 because 0 is the executable name
101 for (argindex = 1; argindex < *argc; argindex++) {
102 if ((curropt = (*argv)[argindex]) == NULL) continue;
104 // Last argument was an option, now we're setting the actual value of that option
105 if (wasinarg != NULL) {
106 switch (wasinarg->type) {
107 // We set the format specifier here then make
108 // one sscanf call with it. We don't even need
109 // to cast it because it's already a pointer
110 // unless the user fucked something up which is
111 // their fault!
112 case OPTTYPE_CHAR: strcpy(format_specifier, "%c"); break;
113 case OPTTYPE_SHORT: strcpy(format_specifier, "%hi"); break;
114 case OPTTYPE_USHORT: strcpy(format_specifier, "%hu"); break;
115 case OPTTYPE_INT: strcpy(format_specifier, "%d"); break;
116 case OPTTYPE_UINT: strcpy(format_specifier, "%u"); break;
117 case OPTTYPE_LONG: strcpy(format_specifier, "%ld"); break;
118 case OPTTYPE_ULONG: strcpy(format_specifier, "%lu"); break;
119 #ifdef _WIN32
120 case OPTTYPE_LONGLONG: strcpy(format_specifier, "%l64d"); break;
121 case OPTTYPE_ULONGLONG: strcpy(format_specifier, "%l64u"); break;
122 #else
123 case OPTTYPE_LONGLONG: strcpy(format_specifier, "%lld"); break;
124 case OPTTYPE_ULONGLONG: strcpy(format_specifier, "%llu"); break;
125 #endif
126 case OPTTYPE_FLOAT: strcpy(format_specifier, "%f"); break;
127 case OPTTYPE_DOUBLE: strcpy(format_specifier, "%lf"); break;
128 case OPTTYPE_LONGDOUBLE: strcpy(format_specifier, "%Lf"); break;
130 // Handled differently. This is because %s expects a char*, and copies one buffer to
131 // the other. This is an enormous waste because we would then have to allocate a buffer
132 // when we could just make the string point to the same place as the other string.
133 case OPTTYPE_STRING:
134 *(char**)(wasinarg->outdata) = curropt;
135 wasinarg = NULL;
136 format_specifier[0] = 0;
137 continue;
139 sscanf(curropt, format_specifier, wasinarg->outdata);
140 wasinarg = NULL;
141 format_specifier[0] = 0;
142 } else {
143 // Has the user manually demanded that the option-parsing end now?
144 if (!strcmp(curropt, "--")) {
145 // copy over the remaining arguments to newargv
146 for (uint32_t i = argindex+1; i < *argc; i++) {
147 newargv[newargc] = (*argv)[i];
148 newargc++;
151 goto end;
154 // in an option, getting warmer!
155 if (curropt[0] == '-') {
156 // was it a --foo or just a -foo?
157 if (curropt[1] == '-') {
158 oneoffset = 2;
159 } else {
160 oneoffset = 1;
163 // is it a short opt (e.g. -f) or a long one (e.g. -foo)?
164 if (strlen(curropt+oneoffset) == 1) {
165 option_index = get_option_index_short(curropt[oneoffset], shortopts, numopts);
166 // nope
167 } else {
168 option_index = get_option_index_long(curropt+oneoffset, longopts, numopts);
171 // not an option
172 if (option_index == -1) {
173 newargv[newargc++] = curropt;
174 dprintf("Faulty option %s.\n", curropt);
175 continue;
176 } else {
177 // it's a boolean option, so the next loop doesn't want to know about it
178 if ((opts[option_index]).type == OPTTYPE_BOOL) {
179 *(bool*)opts[option_index].outdata = 1;
180 // just to make sure
181 wasinarg = NULL;
182 // let the next loop get the value
183 } else {
184 wasinarg = &opts[option_index];
187 } else {
188 newargv[newargc++] = curropt;
189 dprintf("Regular argument %s.\n", curropt);
194 end:
195 *argc = newargc;
197 for (uint32_t i = 1; i <= newargc; i++) {
198 // -1, because argv starts at 1 (with 0 as program name), but newargv starts at 0
199 (*argv)[i] = newargv[i-1];
202 return 1;