1/* -*- c -*- */
2#if !defined INCLUDED_yuck_h_
3#define INCLUDED_yuck_h_
4
5#include <stddef.h>
6
7#define YUCK_OPTARG_NONE	((void*)0x1U)
8
9enum yuck_cmds_e {
10	/* value used when no command was specified */
11	STRPTIME_CMD_NONE = 0U,
12
13	/* actual commands */
14
15	/* convenience identifiers */
16	YUCK_NOCMD = STRPTIME_CMD_NONE,
17	YUCK_NCMDS = STRPTIME_CMD_NONE
18};
19
20
21
22typedef struct yuck_s yuck_t;
23
24
25
26/* generic struct */
27struct yuck_s {
28	enum yuck_cmds_e cmd;
29
30	/* left-over arguments,
31	 * the command string is never a part of this */
32	size_t nargs;
33	char **args;
34
35	/* slots common to all commands */
36
37	/* help is handled automatically */
38	/* version is handled automatically */
39	unsigned int time_flag;
40	unsigned int quiet_flag;
41	char *format_arg;
42	size_t input_format_nargs; char **input_format_args;
43	unsigned int backslash_escapes_flag;
44	unsigned int sed_mode_flag;
45	unsigned int locale_flag;
46};
47
48
49static __attribute__((nonnull(1))) int
50yuck_parse(yuck_t*, int argc, char *argv[]);
51static __attribute__((nonnull(1))) void yuck_free(yuck_t*);
52
53static __attribute__((nonnull(1))) void yuck_auto_help(const yuck_t*);
54static __attribute__((nonnull(1))) void yuck_auto_usage(const yuck_t*);
55static __attribute__((nonnull(1))) void yuck_auto_version(const yuck_t*);
56
57/* some hooks */
58#if defined yuck_post_help
59static __attribute__((nonnull(1))) void yuck_post_help(const yuck_t*);
60#endif	/* yuck_post_help */
61
62#if defined yuck_post_usage
63static __attribute__((nonnull(1))) void yuck_post_usage(const yuck_t*);
64#endif	/* yuck_post_usage */
65
66#if defined yuck_post_version
67static __attribute__((nonnull(1))) void yuck_post_version(const yuck_t*);
68#endif	/* yuck_post_version */
69
70#endif	/* INCLUDED_yuck_h_ */
71/* -*- c -*- */
72#if defined HAVE_CONFIG_H
73# include "config.h"
74#endif	/* HAVE_CONFIG_H */
75#if defined HAVE_VERSION_H
76# include "version.h"
77#endif	/* HAVE_VERSION_H */
78#include <stdlib.h>
79#include <stdbool.h>
80#include <string.h>
81#include <stdio.h>
82#include <limits.h>
83
84#if defined __INTEL_COMPILER
85# pragma warning (push)
86# pragma warning (disable:177)
87# pragma warning (disable:111)
88# pragma warning (disable:3280)
89#elif defined __GNUC__
90# if __GNUC__ > 4 || __GNUC__ == 4 &&  __GNUC_MINOR__ >= 6
91#  pragma GCC diagnostic push
92# endif	 /* GCC version */
93# pragma GCC diagnostic ignored "-Wunused-label"
94# pragma GCC diagnostic ignored "-Wunused-variable"
95# pragma GCC diagnostic ignored "-Wunused-function"
96# pragma GCC diagnostic ignored "-Wshadow"
97#endif	/* __INTEL_COMPILER */
98
99
100static inline bool
101yuck_streqp(const char *s1, const char *s2)
102{
103	return !strcmp(s1, s2);
104}
105
106/* for multi-args */
107static inline char**
108yuck_append(char **array, size_t n, char *val)
109{
110	if (!(n % 16U)) {
111		/* resize */
112		void *tmp = realloc(array, (n + 16U) * sizeof(*array));
113		if (tmp == NULL) {
114			free(array);
115			return NULL;
116		}
117		/* otherwise make it persistent */
118		array = tmp;
119	}
120	array[n] = val;
121	return array;
122}
123
124static enum yuck_cmds_e yuck_parse_cmd(const char *cmd)
125{
126	if (0) {
127		;
128	} else {
129		/* error here? */
130		fprintf(stderr, "strptime: invalid command `%s'\n\
131Try `--help' for a list of commands.\n", cmd);
132	}
133	return (enum yuck_cmds_e)-1;
134}
135
136
137static int yuck_parse(yuck_t tgt[static 1U], int argc, char *argv[])
138{
139	char *op;
140	int i;
141
142	/* we'll have at most this many args */
143	memset(tgt, 0, sizeof(*tgt));
144	if ((tgt->args = calloc(argc, sizeof(*tgt->args))) == NULL) {
145		return -1;
146	}
147	for (i = 1; i < argc && tgt->nargs < (size_t)-1; i++) {
148		op = argv[i];
149
150		switch (*op) {
151		case '-':
152			/* could be an option */
153			switch (*++op) {
154			default:
155				/* could be glued into one */
156				for (; *op; op++) {
157					goto shortopt; back_from_shortopt:;
158				}
159				break;
160			case '-':
161				if (*++op == '\0') {
162					i++;
163					goto dashdash; back_from_dashdash:;
164					break;
165				}
166				goto longopt; back_from_longopt:;
167				break;
168			case '\0':
169				goto plain_dash;
170			}
171			break;
172		default:
173		plain_dash:
174			goto arg; back_from_arg:;
175			break;
176		}
177	}
178	if (i < argc) {
179		op = argv[i];
180
181		if (*op++ == '-' && *op++ == '-' && !*op) {
182			/* another dashdash, filter out */
183			i++;
184		}
185	}
186	/* has to be here as the max_pargs condition might drive us here */
187	dashdash:
188	{
189		/* dashdash loop, pile everything on tgt->args
190		 * don't check for subcommands either, this is in accordance to
191		 * the git tool which won't accept commands after -- */
192		for (; i < argc; i++) {
193			tgt->args[tgt->nargs++] = argv[i];
194		}
195	}
196	return 0;
197
198	longopt:
199	{
200		/* split into option and arg part */
201		char *arg;
202
203		if ((arg = strchr(op, '=')) != NULL) {
204			/* \nul this one out */
205			*arg++ = '\0';
206		}
207
208		switch (tgt->cmd) {
209		default:
210			goto STRPTIME_CMD_NONE_longopt; back_from_STRPTIME_CMD_NONE_longopt:;
211			break;
212		}
213		goto back_from_longopt;
214
215
216		STRPTIME_CMD_NONE_longopt:
217		{
218			if (0) {
219				;
220			} else if (yuck_streqp(op, "help")) {
221				/* invoke auto action and exit */
222				yuck_auto_help(tgt);
223				goto success;
224			} else if (yuck_streqp(op, "version")) {
225				/* invoke auto action and exit */
226				yuck_auto_version(tgt);
227				goto success;
228			} else if (yuck_streqp(op, "time")) {
229				tgt->time_flag++; goto xtra_chk;
230			} else if (yuck_streqp(op, "quiet")) {
231				tgt->quiet_flag++; goto xtra_chk;
232			} else if (yuck_streqp(op, "format")) {
233				tgt->format_arg = arg ?: argv[++i];
234			} else if (yuck_streqp(op, "input-format")) {
235				tgt->input_format_args =
236					yuck_append(
237						tgt->input_format_args, tgt->input_format_nargs++,
238						arg ?: argv[++i]);
239				if (tgt->input_format_args == NULL) {
240					return -1;
241				};
242			} else if (yuck_streqp(op, "backslash-escapes")) {
243				tgt->backslash_escapes_flag++; goto xtra_chk;
244			} else if (yuck_streqp(op, "sed-mode")) {
245				tgt->sed_mode_flag++; goto xtra_chk;
246			} else if (yuck_streqp(op, "locale")) {
247				tgt->locale_flag++; goto xtra_chk;
248			} else {
249				/* grml */
250				fprintf(stderr, "strptime: unrecognized option `--%s'\n", op);
251				goto failure;
252			xtra_chk:
253				if (arg != NULL) {
254					fprintf(stderr, "strptime: option `--%s' doesn't allow an argument\n", op);
255					goto failure;
256				}
257			}
258			if (i >= argc) {
259				fprintf(stderr, "strptime: option `--%s' requires an argument\n", op);
260				goto failure;
261			}
262			goto back_from_STRPTIME_CMD_NONE_longopt;
263		}
264
265	}
266
267	shortopt:
268	{
269		char *arg = op + 1U;
270
271		switch (tgt->cmd) {
272		default:
273			goto STRPTIME_CMD_NONE_shortopt; back_from_STRPTIME_CMD_NONE_shortopt:;
274			break;
275		}
276		goto back_from_shortopt;
277
278
279		STRPTIME_CMD_NONE_shortopt:
280		{
281			switch (*op) {
282			case '0':
283			case '1':
284			case '2':
285			case '3':
286			case '4':
287			case '5':
288			case '6':
289			case '7':
290			case '8':
291			case '9':
292				if (op[-1] == '-') {
293					/* literal treatment of numeral */
294					goto arg;
295				}
296				/*@fallthrough@*/
297			default:
298				;
299				;
300				fprintf(stderr, "strptime: invalid option -%c\n", *op);
301				goto failure;
302
303
304
305
306			case 'h':
307				/* invoke auto action and exit */
308				yuck_auto_help(tgt);
309				goto success;
310				break;
311			case 'V':
312				/* invoke auto action and exit */
313				yuck_auto_version(tgt);
314				goto success;
315				break;
316			case 't':
317				tgt->time_flag++;
318				break;
319			case 'q':
320				tgt->quiet_flag++;
321				break;
322			case 'f':
323				tgt->format_arg = *arg
324					? (op += strlen(arg), arg)
325					: argv[++i];
326				break;
327			case 'i':
328				tgt->input_format_args =
329					yuck_append(
330						tgt->input_format_args,
331						tgt->input_format_nargs++,
332						*arg ? (op += strlen(arg), arg) : argv[++i]);
333				if (tgt->input_format_args == NULL) {
334					return -1;
335				};
336				break;
337			case 'e':
338				tgt->backslash_escapes_flag++;
339				break;
340			case 'S':
341				tgt->sed_mode_flag++;
342				break;
343			case 'l':
344				tgt->locale_flag++;
345				break;
346			}
347			if (i >= argc) {
348				fprintf(stderr, "strptime: option `--%s' requires an argument\n", op);
349				goto failure;
350			}
351			goto back_from_STRPTIME_CMD_NONE_shortopt;
352		}
353
354	}
355
356	arg:
357	{
358		if (tgt->cmd || YUCK_NCMDS == 0U) {
359			tgt->args[tgt->nargs++] = argv[i];
360		} else {
361			/* ah, might be an arg then */
362			if ((tgt->cmd = yuck_parse_cmd(op)) > YUCK_NCMDS) {
363				return -1;
364			}
365		}
366		goto back_from_arg;
367	}
368
369	failure:
370	{
371		exit(EXIT_FAILURE);
372	}
373
374	success:
375	{
376		exit(EXIT_SUCCESS);
377	}
378}
379
380static void yuck_free(yuck_t tgt[static 1U])
381{
382	if (tgt->args != NULL) {
383		/* free despite const qualifier */
384		free(tgt->args);
385	}
386	/* free mulargs */
387	switch (tgt->cmd) {
388		void *ptr;
389	default:
390		break;
391	case STRPTIME_CMD_NONE:
392;
393;
394;
395;
396;
397		ptr = tgt->input_format_args;
398		if (ptr != NULL) {
399			free(ptr);
400		}
401;
402;
403;
404;
405		break;
406	}
407	return;
408}
409
410static void yuck_auto_usage(const yuck_t src[static 1U])
411{
412	switch (src->cmd) {
413	default:
414	YUCK_NOCMD:
415		puts("Usage: strptime [OPTION]... [INPUT]...\n\
416\n\
417Parse input from stdin according to one of the given formats FORMATs.\n\
418The format string specifiers are the same as for strptime(3).\n\
419");
420		break;
421
422	}
423
424#if defined yuck_post_usage
425	yuck_post_usage(src);
426#endif	/* yuck_post_usage */
427	return;
428}
429
430static void yuck_auto_help(const yuck_t src[static 1U])
431{
432	yuck_auto_usage(src);
433
434
435	/* leave a not about common options */
436	if (src->cmd == YUCK_NOCMD) {
437		;
438	}
439
440	switch (src->cmd) {
441	default:
442	case STRPTIME_CMD_NONE:
443		puts("\
444  -h, --help            Print help and exit\n\
445  -V, --version         Print version and exit\n\
446  -t, --time            also display time in the output, default is to\n\
447                        display the date\n\
448  -q, --quiet           Suppress message about date/time and duration\n\
449                        parser errors.\n\
450  -f, --format=STRING   Output format.  This can either be a specifier\n\
451                        string (similar to strftime()'s FMT) or the name\n\
452                        of a calendar.\n\
453  -i, --input-format=STRING...\n\
454                        Input format, can be used multiple times.\n\
455                        Each date/time will be passed to the input\n\
456                        format parsers in the order they are given, if a\n\
457                        date/time can be read successfully with a given\n\
458                        input format specifier string, that value will\n\
459                        be used.\n\
460  -e, --backslash-escapes\n\
461                        Enable interpretation of backslash escapes in the\n\
462                        output and input format specifier strings.\n\
463  -S, --sed-mode        Copy parts from the input before and after a\n\
464                        matching date/time.\n\
465                        Note that all occurrences of date/times within a\n\
466                        line will be processed.\n\
467  -l, --locale          Make internal strptime(3) and strftime(3) behave\n\
468                        in a locale dependent way, default is to pretend\n\
469                        LC_ALL=C is in place.\n\
470");
471		break;
472
473	}
474
475#if defined yuck_post_help
476	yuck_post_help(src);
477#endif	/* yuck_post_help */
478
479#if defined PACKAGE_BUGREPORT
480	puts("\n\
481Report bugs to " PACKAGE_BUGREPORT);
482#endif	/* PACKAGE_BUGREPORT */
483	return;
484}
485
486static void yuck_auto_version(const yuck_t src[static 1U])
487{
488	switch (src->cmd) {
489	default:
490#if 0
491
492#elif defined package_string
493		puts(package_string);
494#elif defined package_version
495		printf("strptime %s\n", package_version);
496#elif defined PACKAGE_STRING
497		puts(PACKAGE_STRING);
498#elif defined PACKAGE_VERSION
499		puts("strptime " PACKAGE_VERSION);
500#elif defined VERSION
501		puts("strptime " VERSION);
502#else  /* !PACKAGE_VERSION, !VERSION */
503		puts("strptime unknown version");
504#endif	/* PACKAGE_VERSION */
505		break;
506	}
507
508#if defined yuck_post_version
509	yuck_post_version(src);
510#endif	/* yuck_post_version */
511	return;
512}
513
514#if defined __INTEL_COMPILER
515# pragma warning (pop)
516#elif defined __GNUC__
517# if __GNUC__ > 4 || __GNUC__ == 4 &&  __GNUC_MINOR__ >= 6
518#  pragma GCC diagnostic pop
519# endif	 /* GCC version */
520#endif	/* __INTEL_COMPILER */
521