gwion-util
utilities for the Gwion project
Loading...
Searching...
No Matches
cmdapp.c
Go to the documentation of this file.
1// cmdapp: cmdapp.c
2// Copyright (C) 2021 Jérémie Astor
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17#include "termcolor.h"
18#include "cmdapp.h"
19#include <stdio.h>
20#include <string.h>
21#include <stdarg.h>
22
23#define eprintf(fmt, ...) \
24 tcol_fprintf(stderr, "{R+}error:{0}" fmt, ##__VA_ARGS__);
25
26typedef struct _cmdopt_internal_t {
27 // The option that this internal representation refers to
29
30 // Used for help and version generation
31 const char* description;
32
33 // NULL-terminated array
36
37static inline size_t _argvlen(void** argv) {
38 size_t length = 0;
39 while (*argv) argv++, length++;
40 return length;
41}
42
43void cmdapp_init(cmdapp_t* app, int argc, char** argv, cmdapp_mode_t mode,
44 const cmdapp_info_t* info) {
45 app->_argc = argc;
46 app->_argv = argv;
47 app->_mode = mode;
48 app->_custom_help = 0;
49 app->_custom_ver = 0;
50 app->_length = 0;
51 app->_capacity = 4;
52 app->_start = malloc(sizeof(cmdarg_internal_t*) * app->_capacity);
53 app->_args.length = 0;
54 app->_args.contents = NULL;
55 app->_info = *info;
56 app->_proc = NULL;
57}
58
60 for (size_t i = 0; i < app->_length; i++) {
61 free(app->_start[i]);
62 }
63 free(app->_start);
64 free(app->_args.contents);
65 app->_args.contents = NULL;
66}
67
68static void cmdapp_append(cmdapp_t* app, cmdarg_internal_t* arg_int) {
69 if (app->_length + 1 > app->_capacity) {
70 app->_capacity += (app->_capacity / 2);
71 app->_start = realloc(app->_start,
72 sizeof(cmdarg_internal_t*) * app->_capacity);
73 }
74 app->_start[app->_length++] = arg_int;
75}
76
77void cmdapp_set(cmdapp_t* app, char shorto, const char* longo, uint8_t flags,
78 cmdopt_t** conflicts, const char* description,
79 const char *argtype,
80 cmdopt_t* option)
81{
82
83 if (strncmp(longo, "help", 5) == 0) {
84 app->_custom_help = 1;
85 } else if (strncmp(longo, "version", 8) == 0) {
86 app->_custom_ver = 1;
87 }
88
89 option->shorto = shorto;
90 option->longo = longo;
91 option->argtype = argtype;
92 option->flags = flags;
93 option->value = NULL;
94 const size_t conflict_count = (conflicts == NULL)
95 ? 1
96 : _argvlen((void**)conflicts) + 1;
97 const size_t size = sizeof(cmdarg_internal_t)
98 + conflict_count * sizeof(cmdopt_t*);
99 cmdarg_internal_t* arg_int = malloc(size);
100 arg_int->result = option;
101 arg_int->description = description;
102 if (conflicts == NULL) {
103 arg_int->conflicts[0] = NULL;
104 } else {
105 memcpy(arg_int->conflicts, conflicts, conflict_count
106 * sizeof(cmdopt_t*));
107 }
108 cmdapp_append(app, arg_int);
109}
110
111void cmdapp_enable_procedure(cmdapp_t* app, cmdapp_procedure_t proc, void *user_data) {
112 app->_proc = proc;
113 app->_user_data = user_data;
114}
115
117 if (app->_info.synopses && *app->_info.synopses) {
118 tcol_printf("{Y}Usage:{0} {+}%s{0} %s\n", app->_argv[0], *app->_info.synopses);
119 for (size_t i = 1; app->_info.synopses[i]; i++) {
120 printf(" or: {+}%s{0} %s\n", app->_argv[0], app->_info.synopses[i]);
121 }
122 } else {
123 tcol_printf("{Y}Usage:{0} {+}%s{0} {G}[OPTION]{-}...{0} {M}ARG {-}...{0}\n", app->_argv[0]);
124 }
125 printf("\n");
126 tcol_printf("{-}%s{0}\n", app->_info.description);
127 if (!app->_length)
128 return;
129 printf("\n");
130 tcol_printf("{Y}Options:{0}\n");
131 for (size_t i = 0; i < app->_length; i++) {
132 cmdarg_internal_t* arg_int = app->_start[i];
133 tcol_printf("%*s", app->_info.help_des_offset, "");
134 tcol_printf(arg_int->description);
135 tcol_printf("\r");
136 if(arg_int->result->shorto) {
137 tcol_printf(" {G}-%c{0}", arg_int->result->shorto);
138 } else {
139 tcol_printf(" {G}-{0}");
140 }
141 if (arg_int->result->longo) {
142 tcol_printf(", {G}--%s{0}", arg_int->result->longo);
143 }
144 if (arg_int->result->flags & CMDOPT_TAKESARG) {
145 tcol_printf("{M}=");
146 if (!arg_int->result->argtype)
147 tcol_printf("ARG");
148 else
149 tcol_printf(arg_int->result->argtype);
150 tcol_printf("{0}");
151 }
152 else if (arg_int->result->flags & CMDOPT_MAYTAKEARG) {
153 tcol_printf("{M}(=");
154 if (!arg_int->result->argtype)
155 tcol_printf("ARG");
156 else
157 tcol_printf(arg_int->result->argtype);
158 tcol_printf("){0}");
159 }
160 fputc('\n', stdout);
161 }
162 if (!app->_custom_help) {
163 tcol_printf("%*s%s\r {+G}--help{0}\n", app->_info.help_des_offset, "",
164 "Display this information");
165 }
166 if (!app->_custom_ver) {
167 tcol_printf("%*s%s\r {+G}--version{0}\n", app->_info.help_des_offset, "",
168 "Display program version information");
169 }
170}
171
173 printf("%s %s\n", app->_info.program, app->_info.version);
174 printf("Copyright (C) %d %s\n", app->_info.year, app->_info.author);
175 printf("%s", app->_info.ver_extra);
176}
177
178static cmdarg_internal_t* cmdapp_search(cmdapp_t* app, char shorto, const char* longo) {
179 if (longo == NULL) {
180 for (size_t i = 0; i < app->_length; i++) {
181 if (app->_start[i]->result->shorto == shorto) {
182 return app->_start[i];
183 }
184 }
185 } else {
186 for (size_t i = 0; i < app->_length; i++) {
187 if (strcmp(app->_start[i]->result->longo, longo) == 0) {
188 return app->_start[i];
189 }
190 }
191 }
192 return NULL;
193}
194
195#define IS_END_OF_FLAGS(str) \
196 ((str)[0] == '-' && (str)[1] == '-' && (str)[2] == 0)
197#define IS_LONG_FLAG(str) ((str)[0] == '-' && (str)[1] == '-')
198#define IS_SHORT_FLAG(str) ((str)[0] == '-')
199
201 for (size_t i = 0; i < app->_length; i++) {
202 cmdarg_internal_t* arg_int = app->_start[i];
203 if (!cmdopt_exists(*arg_int->result)) continue;
204 if (!cmdopt_is_optional(*arg_int->result)) {
205 eprintf("Required option -%c not passed\n",
206 arg_int->result->shorto);
207 return EXIT_FAILURE;
208 }
209 cmdopt_t** conflicts = arg_int->conflicts;
210 if (!conflicts) continue;
211 for (size_t i = 0; conflicts[i]; i++) {
212 if (cmdopt_exists(*conflicts[i])) {
213 eprintf("Cannot pass both -%c and -%c\n",
214 arg_int->result->shorto, conflicts[i]->shorto);
215 return EXIT_FAILURE;
216 }
217 }
218 }
219 return EXIT_SUCCESS;
220}
221
223 if (app->_args.contents != NULL) {
224 app->_args.contents = realloc(app->_args.contents, sizeof(char*) * 4);
225 app->_args.length = 0;
226 } else {
227 app->_args.contents = malloc(sizeof(char*) * 4);
228 }
229 size_t args_cap = 4;
230 #define APPEND_ARG(arg) if (app->_args.length + 1 > args_cap) { \
231 args_cap += (args_cap / 2); \
232 app->_args.contents = realloc(app->_args.contents, \
233 sizeof(char*) * args_cap); \
234 } \
235 if (app->_proc) { \
236 app->_proc(app->_user_data, NULL, arg); \
237 } \
238 app->_args.contents[app->_args.length++] = arg;
239
240 bool only_args = false;
241 for (int i = 0; i < app->_argc; i++) {
242 char* current = app->_argv[i];
243 if (only_args) {
244 APPEND_ARG(current);
245 continue;
246 }
247 const char* next = app->_argv[i + 1];
248 if (IS_END_OF_FLAGS(current)) {
249 only_args = true;
250 continue;
251 }
252 cmdarg_internal_t* arg_int;
253 if (IS_LONG_FLAG(current)) {
254 // Find the `=` for longopt args.
255 char* arg = strchr(current, '=');
256 // Move the pointer to the start of the longopt's args
257 if (arg) {
258 *arg = 0;
259 arg++;
260 }
261 if ((arg_int = cmdapp_search(app, 0, current + 2))) {
262 if (arg_int->result->flags & CMDOPT_TAKESARG) {
263 if (arg == NULL || *arg == 0) {
264 eprintf("%s expects an argument\n", current);
265 return EXIT_FAILURE;
266 }
267 arg_int->result->value = arg;
268 } else if (arg_int->result->flags & CMDOPT_MAYTAKEARG) {
269 arg_int->result->value = arg;
270 } else {
271 if (arg != NULL) {
272 eprintf("%s does not take arguments\n", current);
273 return EXIT_FAILURE;
274 }
275 }
276 } else {
277 if (strncmp(current, "--help", 7) == 0) {
279 app->_mode |= _CMDAPP_MODE_EXIT;
280 return EXIT_SUCCESS;
281 } else if (strncmp(current, "--version", 10) == 0) {
283 app->_mode |= _CMDAPP_MODE_EXIT;
284 return EXIT_SUCCESS;
285 }
286 eprintf("Unrecognized command line option %s, try --help\n", current);
287 return EXIT_FAILURE;
288 }
289 arg_int->result->flags |= CMDOPT_EXISTS;
290 if (app->_proc) {
291 app->_proc(app->_user_data, arg_int->result, NULL);
292 }
293 } else if (IS_SHORT_FLAG(current)) {
294 if (app->_mode & CMDAPP_MODE_SHORTARG) {
295 if ((arg_int = cmdapp_search(app, current[1], NULL))) {
296 arg_int->result->value = NULL;
297 if (arg_int->result->flags | CMDOPT_TAKESARG) {
298 arg_int->result->value = current + 2;
299 if (!current[2] && next && next[0] != '-') {
300 arg_int->result->value = next;
301 i++;
302 } else if (current[2] == 0) {
303 eprintf("-%c expects an argument\n", current[1]);
304 return EXIT_FAILURE;
305 }
306 } else if ((current[2] != 0) || (next && next[0] != '-')) {
307 eprintf("-%c does not take arguments\n", current[1]);
308 return EXIT_FAILURE;
309 }
310 } else {
311 eprintf("Unrecognized command line option -%c, try --help\n",
312 current[1]);
313 return EXIT_FAILURE;
314 }
315 arg_int->result->flags |= CMDOPT_EXISTS;
316 if (app->_proc) {
317 app->_proc(app->_user_data, arg_int->result, NULL);
318 }
319 } else /* app->_mode | CMDAPP_MODE_MULTIFLAG */ {
320 for (size_t j = 1; current[j]; j++) {
321 if ((arg_int = cmdapp_search(app, current[1], NULL))) {
322 arg_int->result->value = current + 2;
323 if (arg_int->result->value[2] == 0) {
324 if (next && next[0] != '-') {
325 arg_int->result->value = next;
326 i++;
327 } else {
328 eprintf("-%c expects an argument\n", current[1]);
329 return EXIT_FAILURE;
330 }
331 }
332 } else {
333 eprintf("Unrecognized command line option -%c\n",
334 current[1]);
335 return EXIT_FAILURE;
336 }
337 }
338 }
339 } else {
340 APPEND_ARG(current);
341 }
342 }
343 #undef APPEND_ARG
344
346 return EXIT_FAILURE;
347 }
348
349 return EXIT_SUCCESS;
350}
351
353 if (app->_args.contents == NULL) {
354 return NULL;
355 }
356 return &app->_args;
357}
358
359// https://stackoverflow.com/questions/11350878/how-can-i-determine-if-the-operating-system-is-posix-in-c
360#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
361#include <unistd.h>
362#endif
363
364void cmdapp_error(cmdapp_t* app, const char* fmt, ...) {
365 #ifdef _POSIX_VERSION
366 if (isatty(STDERR_FILENO) || (getenv("CMDAPP_COLOR_ALWAYS") != NULL)) {
367 fprintf(stderr, "%s: {R+}error:{0} ", app->_info.program);
368 } else {
369 fprintf(stderr, "%s: error: ", app->_info.program);
370 }
371 #else
372 fprintf(stderr, "%s: error: ", app->_info.program);
373 #endif /* _POSIX_VERSION */
374 va_list valist;
375 va_start(valist, fmt);
376 vfprintf(stderr, fmt, valist);
377 va_end(valist);
378}
void cmdapp_set(cmdapp_t *app, char shorto, const char *longo, uint8_t flags, cmdopt_t **conflicts, const char *description, const char *argtype, cmdopt_t *option)
Definition cmdapp.c:77
void cmdapp_enable_procedure(cmdapp_t *app, cmdapp_procedure_t proc, void *user_data)
Definition cmdapp.c:111
static size_t _argvlen(void **argv)
Definition cmdapp.c:37
#define APPEND_ARG(arg)
static int cmdapp_resolve_options(cmdapp_t *app)
Definition cmdapp.c:200
void cmdapp_print_version(cmdapp_t *app)
Definition cmdapp.c:172
#define IS_LONG_FLAG(str)
Definition cmdapp.c:197
#define IS_END_OF_FLAGS(str)
Definition cmdapp.c:195
#define IS_SHORT_FLAG(str)
Definition cmdapp.c:198
void cmdapp_print_help(cmdapp_t *app)
Definition cmdapp.c:116
cmdargs_t * cmdapp_getargs(cmdapp_t *app)
Definition cmdapp.c:352
int cmdapp_run(cmdapp_t *app)
Definition cmdapp.c:222
#define eprintf(fmt,...)
Definition cmdapp.c:23
static cmdarg_internal_t * cmdapp_search(cmdapp_t *app, char shorto, const char *longo)
Definition cmdapp.c:178
struct _cmdopt_internal_t cmdarg_internal_t
void cmdapp_error(cmdapp_t *app, const char *fmt,...)
Definition cmdapp.c:364
void cmdapp_destroy(cmdapp_t *app)
Definition cmdapp.c:59
static void cmdapp_append(cmdapp_t *app, cmdarg_internal_t *arg_int)
Definition cmdapp.c:68
void cmdapp_init(cmdapp_t *app, int argc, char **argv, cmdapp_mode_t mode, const cmdapp_info_t *info)
Definition cmdapp.c:43
#define CMDOPT_TAKESARG
Definition cmdapp.h:46
#define CMDOPT_MAYTAKEARG
Definition cmdapp.h:47
#define CMDOPT_EXISTS
Definition cmdapp.h:44
#define _CMDAPP_MODE_EXIT
Definition cmdapp.h:53
#define EXIT_SUCCESS
Definition cmdapp.h:30
#define EXIT_FAILURE
Definition cmdapp.h:27
uint8_t cmdapp_mode_t
Definition cmdapp.h:34
void(* cmdapp_procedure_t)(void *data, cmdopt_t *option, const char *arg)
Definition cmdapp.h:78
#define cmdopt_exists(opt)
Definition cmdapp.h:56
#define CMDAPP_MODE_SHORTARG
Definition cmdapp.h:50
#define cmdopt_is_optional(opt)
Definition cmdapp.h:58
char const char * fmt
Definition gwion_print.h:2
cmdopt_t * result
Definition cmdapp.c:28
cmdopt_t * conflicts[]
Definition cmdapp.c:34
const char * description
Definition cmdapp.c:31
const char * program
Definition cmdapp.h:63
int help_des_offset
Definition cmdapp.h:69
const char * description
Definition cmdapp.h:68
const char * ver_extra
Definition cmdapp.h:70
const char * version
Definition cmdapp.h:65
const char * author
Definition cmdapp.h:66
const char ** synopses
Definition cmdapp.h:64
int _argc
Definition cmdapp.h:81
void * _user_data
Definition cmdapp.h:92
size_t _capacity
Definition cmdapp.h:88
cmdargs_t _args
Definition cmdapp.h:90
int _custom_help
Definition cmdapp.h:85
char ** _argv
Definition cmdapp.h:82
int _custom_ver
Definition cmdapp.h:86
cmdapp_info_t _info
Definition cmdapp.h:84
cmdapp_mode_t _mode
Definition cmdapp.h:83
size_t _length
Definition cmdapp.h:87
cmdopt_internal_t ** _start
Definition cmdapp.h:89
cmdapp_procedure_t _proc
Definition cmdapp.h:91
size_t length
Definition cmdapp.h:74
const char ** contents
Definition cmdapp.h:75
const char * value
Definition cmdapp.h:39
cmdopt_flags_t flags
Definition cmdapp.h:41
const char * longo
Definition cmdapp.h:38
char shorto
Definition cmdapp.h:37
const char * argtype
Definition cmdapp.h:40
int tcol_printf(const char *fmt,...)
Definition termcolor.c:373
void void * arg
Definition threadpool.h:27