Use char* instead of char[] for format_specifier
[OptFetch.git] / optfetch.c
blobca807647b0f6f80269116ed77a29c11e707d331d
1 #include <stdbool.h>
2 #include <stdarg.h>
3 #include <stdio.h>
4 #include <string.h>
6 #include "optfetch.h"
8 #ifdef DEBUG
9 # define dbprintf(...) fprintf(stderr, __VA_ARGS__)
10 #else
11 # define dbprintf(...) // nothing
12 #endif
14 typedef unsigned int uint;
17 static int countopts(struct opttype *opts) {
18 uint i;
19 for (i = 0;
20 // longname and short name are both 0, OR
21 !(((opts[i].longname == NULL) && (opts[i].shortname == 0)) ||
22 // output type was unspecified OR
23 ((opts[i].type == 0) || (opts[i].type > OPTTYPE_STRING)) ||
24 // nowhere to output data!
25 (opts[i].outdata == NULL));
27 i++);
29 return i;
32 static int get_option_index_short(char opt, char *potentialopts, uint len) {
33 for (uint i = 0; i < len; i++) {
34 if (potentialopts[i] == 0) {
35 continue;
36 } else if (potentialopts[i] == opt) {
37 return i;
40 return -1;
43 static int get_option_index_long(char *opt, char **potentialopts, uint len) {
44 for (uint i = 0; i < len; i++) {
45 if (potentialopts[i] == NULL) {
46 continue;
47 } else if (!strcmp(potentialopts[i], opt)) {
48 return i;
51 return -1;
55 signed char fetchopts(int *argc, char ***argv, struct opttype *opts) {
56 uint numopts = countopts(opts);
57 if (!numopts) {
58 return 0;
61 char *longopts[numopts];
62 char shortopts[numopts];
63 char *curropt;
64 char *format_specifier;
66 struct opttype *wasinarg = NULL;
68 // char to take up less memory, unsigned so compiler doesn't complain
69 unsigned char oneoffset;
71 int argindex, option_index;;
72 uint newargc = 0;
74 // new argument variable with the arguments that aren't options
75 char *newargv[*argc];
77 // fill these up with opts. That way they're easier to look up
78 for (uint i = 0; i < numopts; i++) {
79 longopts[i] = opts[i].longname;
80 shortopts[i] = opts[i].shortname;
83 // start at 1 because 0 is the executable name
84 for (argindex = 1; argindex < *argc; argindex++) {
85 if ((curropt = (*argv)[argindex]) == NULL) continue;
87 // Last argument was an option, now we're setting the actual value of that option
88 if (wasinarg != NULL) {
89 switch (wasinarg->type) {
90 /* We set the format specifier here then make
91 * one sscanf call with it. We don't even need
92 * to cast it because it's already a pointer
93 * unless the user fucked something up which is
94 * their fault!
96 case OPTTYPE_CHAR: format_specifier = "%c"; break;
97 case OPTTYPE_SHORT: format_specifier = "%hi"; break;
98 case OPTTYPE_USHORT: format_specifier = "%hu"; break;
99 case OPTTYPE_INT: format_specifier = "%d"; break;
100 case OPTTYPE_UINT: format_specifier = "%u"; break;
101 case OPTTYPE_LONG: format_specifier = "%ld"; break;
102 case OPTTYPE_ULONG: format_specifier = "%lu"; break;
103 #ifdef _WIN32
104 case OPTTYPE_LONGLONG: format_specifier = "%l64d"; break;
105 case OPTTYPE_ULONGLONG: format_specifier = "%l64u"; break;
106 #else
107 case OPTTYPE_LONGLONG: format_specifier = "%lld"; break;
108 case OPTTYPE_ULONGLONG: format_specifier = "%llu"; break;
109 #endif
110 case OPTTYPE_FLOAT: format_specifier = "%f"; break;
111 case OPTTYPE_DOUBLE: format_specifier = "%lf"; break;
112 case OPTTYPE_LONGDOUBLE: format_specifier = "%Lf"; break;
114 /* Handled differently. This is because %s expects a char*, and copies one buffer to
115 * the other. This is an enormous waste because we would then have to allocate a buffer
116 * when we could just make the string point to the same place as the other string.
117 * Which is also better because it means we don't have to malloc data that is later freed
118 * by the user. Fun fact: there are no mallocs in this program! VLAs FTW!
120 case OPTTYPE_STRING:
121 *(char**)(wasinarg->outdata) = curropt;
122 wasinarg = NULL;
123 format_specifier = NULL;
124 continue;
126 sscanf(curropt, format_specifier, wasinarg->outdata);
127 wasinarg = NULL;
128 format_specifier = NULL;
129 } else {
130 // Has the user manually demanded that the option-parsing end now?
131 if (!strcmp(curropt, "--")) {
132 // copy over the remaining arguments to newargv
133 for (int i = argindex+1; i < *argc; i++) {
134 newargv[newargc] = (*argv)[i];
135 newargc++;
138 goto end;
141 // in an option, getting warmer!
142 if (curropt[0] == '-') {
143 // was it a --foo or just a -foo?
144 if (curropt[1] == '-') {
145 oneoffset = 2;
146 } else {
147 oneoffset = 1;
150 // is it a short opt (e.g. -f) or a long one (e.g. -foo)?
151 if (strlen(curropt+oneoffset) == 1) {
152 option_index = get_option_index_short(curropt[oneoffset], shortopts, numopts);
153 // nope
154 } else {
155 option_index = get_option_index_long(curropt+oneoffset, longopts, numopts);
158 // not an option
159 if (option_index == -1) {
160 newargv[newargc++] = curropt;
161 dbprintf("Faulty option %s.\n", curropt);
162 continue;
163 } else {
164 // it's a boolean option, so the next loop doesn't want to know about it
165 if ((opts[option_index]).type == OPTTYPE_BOOL) {
166 *(bool*)opts[option_index].outdata = 1;
167 // just to make sure
168 wasinarg = NULL;
169 // let the next loop get the value
170 } else {
171 wasinarg = &opts[option_index];
174 } else {
175 newargv[newargc++] = curropt;
176 dbprintf("Regular argument %s.\n", curropt);
181 end:
182 *argc = newargc;
184 for (uint i = 1; i <= newargc; i++) {
185 /* -1, because argv starts at 1 (with 0 as program name), but newargv starts at 0 */
186 (*argv)[i] = newargv[i-1];
189 return 1;