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	DATETEST_CMD_NONE = 0U,
12
13	/* actual commands */
14
15	/* convenience identifiers */
16	YUCK_NOCMD = DATETEST_CMD_NONE,
17	YUCK_NCMDS = DATETEST_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 quiet_flag;
40	size_t input_format_nargs; char **input_format_args;
41	char *base_arg;
42	char *from_locale_arg;
43	char *from_zone_arg;
44	unsigned int backslash_escapes_flag;
45	unsigned int eq_flag;
46	unsigned int ne_flag;
47	unsigned int gt_flag;
48	unsigned int lt_flag;
49	unsigned int ge_flag;
50	unsigned int le_flag;
51	unsigned int nt_flag;
52	unsigned int ot_flag;
53	unsigned int cmp_flag;
54	unsigned int isvalid_flag;
55};
56
57
58static __attribute__((nonnull(1))) int
59yuck_parse(yuck_t*, int argc, char *argv[]);
60static __attribute__((nonnull(1))) void yuck_free(yuck_t*);
61
62static __attribute__((nonnull(1))) void yuck_auto_help(const yuck_t*);
63static __attribute__((nonnull(1))) void yuck_auto_usage(const yuck_t*);
64static __attribute__((nonnull(1))) void yuck_auto_version(const yuck_t*);
65
66/* some hooks */
67#if defined yuck_post_help
68static __attribute__((nonnull(1))) void yuck_post_help(const yuck_t*);
69#endif	/* yuck_post_help */
70
71#if defined yuck_post_usage
72static __attribute__((nonnull(1))) void yuck_post_usage(const yuck_t*);
73#endif	/* yuck_post_usage */
74
75#if defined yuck_post_version
76static __attribute__((nonnull(1))) void yuck_post_version(const yuck_t*);
77#endif	/* yuck_post_version */
78
79#endif	/* INCLUDED_yuck_h_ */
80/* -*- c -*- */
81#if defined HAVE_CONFIG_H
82# include "config.h"
83#endif	/* HAVE_CONFIG_H */
84#if defined HAVE_VERSION_H
85# include "version.h"
86#endif	/* HAVE_VERSION_H */
87#include <stdlib.h>
88#include <stdbool.h>
89#include <string.h>
90#include <stdio.h>
91#include <limits.h>
92
93#if defined __INTEL_COMPILER
94# pragma warning (push)
95# pragma warning (disable:177)
96# pragma warning (disable:111)
97# pragma warning (disable:3280)
98#elif defined __GNUC__
99# if __GNUC__ > 4 || __GNUC__ == 4 &&  __GNUC_MINOR__ >= 6
100#  pragma GCC diagnostic push
101# endif	 /* GCC version */
102# pragma GCC diagnostic ignored "-Wunused-label"
103# pragma GCC diagnostic ignored "-Wunused-variable"
104# pragma GCC diagnostic ignored "-Wunused-function"
105# pragma GCC diagnostic ignored "-Wshadow"
106#endif	/* __INTEL_COMPILER */
107
108
109static inline bool
110yuck_streqp(const char *s1, const char *s2)
111{
112	return !strcmp(s1, s2);
113}
114
115/* for multi-args */
116static inline char**
117yuck_append(char **array, size_t n, char *val)
118{
119	if (!(n % 16U)) {
120		/* resize */
121		void *tmp = realloc(array, (n + 16U) * sizeof(*array));
122		if (tmp == NULL) {
123			free(array);
124			return NULL;
125		}
126		/* otherwise make it persistent */
127		array = tmp;
128	}
129	array[n] = val;
130	return array;
131}
132
133static enum yuck_cmds_e yuck_parse_cmd(const char *cmd)
134{
135	if (0) {
136		;
137	} else {
138		/* error here? */
139		fprintf(stderr, "datetest: invalid command `%s'\n\
140Try `--help' for a list of commands.\n", cmd);
141	}
142	return (enum yuck_cmds_e)-1;
143}
144
145
146static int yuck_parse(yuck_t tgt[static 1U], int argc, char *argv[])
147{
148	char *op;
149	int i;
150
151	/* we'll have at most this many args */
152	memset(tgt, 0, sizeof(*tgt));
153	if ((tgt->args = calloc(argc, sizeof(*tgt->args))) == NULL) {
154		return -1;
155	}
156	for (i = 1; i < argc && tgt->nargs < (size_t)-1; i++) {
157		op = argv[i];
158
159		switch (*op) {
160		case '-':
161			/* could be an option */
162			switch (*++op) {
163			default:
164				/* could be glued into one */
165				for (; *op; op++) {
166					goto shortopt; back_from_shortopt:;
167				}
168				break;
169			case '-':
170				if (*++op == '\0') {
171					i++;
172					goto dashdash; back_from_dashdash:;
173					break;
174				}
175				goto longopt; back_from_longopt:;
176				break;
177			case '\0':
178				goto plain_dash;
179			}
180			break;
181		default:
182		plain_dash:
183			goto arg; back_from_arg:;
184			break;
185		}
186	}
187	if (i < argc) {
188		op = argv[i];
189
190		if (*op++ == '-' && *op++ == '-' && !*op) {
191			/* another dashdash, filter out */
192			i++;
193		}
194	}
195	/* has to be here as the max_pargs condition might drive us here */
196	dashdash:
197	{
198		/* dashdash loop, pile everything on tgt->args
199		 * don't check for subcommands either, this is in accordance to
200		 * the git tool which won't accept commands after -- */
201		for (; i < argc; i++) {
202			tgt->args[tgt->nargs++] = argv[i];
203		}
204	}
205	return 0;
206
207	longopt:
208	{
209		/* split into option and arg part */
210		char *arg;
211
212		if ((arg = strchr(op, '=')) != NULL) {
213			/* \nul this one out */
214			*arg++ = '\0';
215		}
216
217		switch (tgt->cmd) {
218		default:
219			goto DATETEST_CMD_NONE_longopt; back_from_DATETEST_CMD_NONE_longopt:;
220			break;
221		}
222		goto back_from_longopt;
223
224
225		DATETEST_CMD_NONE_longopt:
226		{
227			if (0) {
228				;
229			} else if (yuck_streqp(op, "help")) {
230				/* invoke auto action and exit */
231				yuck_auto_help(tgt);
232				goto success;
233			} else if (yuck_streqp(op, "version")) {
234				/* invoke auto action and exit */
235				yuck_auto_version(tgt);
236				goto success;
237			} else if (yuck_streqp(op, "quiet")) {
238				tgt->quiet_flag++; goto xtra_chk;
239			} else if (yuck_streqp(op, "input-format")) {
240				tgt->input_format_args =
241					yuck_append(
242						tgt->input_format_args, tgt->input_format_nargs++,
243						arg ?: argv[++i]);
244				if (tgt->input_format_args == NULL) {
245					return -1;
246				};
247			} else if (yuck_streqp(op, "base")) {
248				tgt->base_arg = arg ?: argv[++i];
249			} else if (yuck_streqp(op, "from-locale")) {
250				tgt->from_locale_arg = arg ?: argv[++i];
251			} else if (yuck_streqp(op, "from-zone")) {
252				tgt->from_zone_arg = arg ?: argv[++i];
253			} else if (yuck_streqp(op, "backslash-escapes")) {
254				tgt->backslash_escapes_flag++; goto xtra_chk;
255			} else if (yuck_streqp(op, "eq")) {
256				tgt->eq_flag++; goto xtra_chk;
257			} else if (yuck_streqp(op, "ne")) {
258				tgt->ne_flag++; goto xtra_chk;
259			} else if (yuck_streqp(op, "gt")) {
260				tgt->gt_flag++; goto xtra_chk;
261			} else if (yuck_streqp(op, "lt")) {
262				tgt->lt_flag++; goto xtra_chk;
263			} else if (yuck_streqp(op, "ge")) {
264				tgt->ge_flag++; goto xtra_chk;
265			} else if (yuck_streqp(op, "le")) {
266				tgt->le_flag++; goto xtra_chk;
267			} else if (yuck_streqp(op, "nt")) {
268				tgt->nt_flag++; goto xtra_chk;
269			} else if (yuck_streqp(op, "ot")) {
270				tgt->ot_flag++; goto xtra_chk;
271			} else if (yuck_streqp(op, "cmp")) {
272				tgt->cmp_flag++; goto xtra_chk;
273			} else if (yuck_streqp(op, "isvalid")) {
274				tgt->isvalid_flag++; goto xtra_chk;
275			} else {
276				/* grml */
277				fprintf(stderr, "datetest: unrecognized option `--%s'\n", op);
278				goto failure;
279			xtra_chk:
280				if (arg != NULL) {
281					fprintf(stderr, "datetest: option `--%s' doesn't allow an argument\n", op);
282					goto failure;
283				}
284			}
285			if (i >= argc) {
286				fprintf(stderr, "datetest: option `--%s' requires an argument\n", op);
287				goto failure;
288			}
289			goto back_from_DATETEST_CMD_NONE_longopt;
290		}
291
292	}
293
294	shortopt:
295	{
296		char *arg = op + 1U;
297
298		switch (tgt->cmd) {
299		default:
300			goto DATETEST_CMD_NONE_shortopt; back_from_DATETEST_CMD_NONE_shortopt:;
301			break;
302		}
303		goto back_from_shortopt;
304
305
306		DATETEST_CMD_NONE_shortopt:
307		{
308			switch (*op) {
309			case '0':
310			case '1':
311			case '2':
312			case '3':
313			case '4':
314			case '5':
315			case '6':
316			case '7':
317			case '8':
318			case '9':
319				if (op[-1] == '-') {
320					/* literal treatment of numeral */
321					goto arg;
322				}
323				/*@fallthrough@*/
324			default:
325				;
326				;
327				fprintf(stderr, "datetest: invalid option -%c\n", *op);
328				goto failure;
329
330
331
332
333			case 'h':
334				/* invoke auto action and exit */
335				yuck_auto_help(tgt);
336				goto success;
337				break;
338			case 'V':
339				/* invoke auto action and exit */
340				yuck_auto_version(tgt);
341				goto success;
342				break;
343			case 'q':
344				tgt->quiet_flag++;
345				break;
346			case 'i':
347				tgt->input_format_args =
348					yuck_append(
349						tgt->input_format_args,
350						tgt->input_format_nargs++,
351						*arg ? (op += strlen(arg), arg) : argv[++i]);
352				if (tgt->input_format_args == NULL) {
353					return -1;
354				};
355				break;
356			case 'b':
357				tgt->base_arg = *arg
358					? (op += strlen(arg), arg)
359					: argv[++i];
360				break;
361			case 'e':
362				tgt->backslash_escapes_flag++;
363				break;
364			}
365			if (i >= argc) {
366				fprintf(stderr, "datetest: option `--%s' requires an argument\n", op);
367				goto failure;
368			}
369			goto back_from_DATETEST_CMD_NONE_shortopt;
370		}
371
372	}
373
374	arg:
375	{
376		if (tgt->cmd || YUCK_NCMDS == 0U) {
377			tgt->args[tgt->nargs++] = argv[i];
378		} else {
379			/* ah, might be an arg then */
380			if ((tgt->cmd = yuck_parse_cmd(op)) > YUCK_NCMDS) {
381				return -1;
382			}
383		}
384		goto back_from_arg;
385	}
386
387	failure:
388	{
389		exit(EXIT_FAILURE);
390	}
391
392	success:
393	{
394		exit(EXIT_SUCCESS);
395	}
396}
397
398static void yuck_free(yuck_t tgt[static 1U])
399{
400	if (tgt->args != NULL) {
401		/* free despite const qualifier */
402		free(tgt->args);
403	}
404	/* free mulargs */
405	switch (tgt->cmd) {
406		void *ptr;
407	default:
408		break;
409	case DATETEST_CMD_NONE:
410;
411;
412;
413		ptr = tgt->input_format_args;
414		if (ptr != NULL) {
415			free(ptr);
416		}
417;
418;
419;
420;
421;
422;
423;
424;
425;
426;
427;
428;
429;
430;
431;
432		break;
433	}
434	return;
435}
436
437static void yuck_auto_usage(const yuck_t src[static 1U])
438{
439	switch (src->cmd) {
440	default:
441	YUCK_NOCMD:
442		puts("Usage: datetest [OPTION]... DATE/TIME1 OP DATE/TIME2\n\
443\n\
444Like test(1) but for dates.\n\
445");
446		break;
447
448	}
449
450#if defined yuck_post_usage
451	yuck_post_usage(src);
452#endif	/* yuck_post_usage */
453	return;
454}
455
456static void yuck_auto_help(const yuck_t src[static 1U])
457{
458	yuck_auto_usage(src);
459
460
461	/* leave a not about common options */
462	if (src->cmd == YUCK_NOCMD) {
463		;
464	}
465
466	switch (src->cmd) {
467	default:
468	case DATETEST_CMD_NONE:
469		puts("\
470  -h, --help            Print help and exit\n\
471  -V, --version         Print version and exit\n\
472  -q, --quiet           Suppress message about date/time and duration\n\
473                        parser errors.\n\
474  -i, --input-format=STRING...\n\
475                        Input format, can be used multiple times.\n\
476                        Each date/time will be passed to the input\n\
477                        format parsers in the order they are given, if a\n\
478                        date/time can be read successfully with a given\n\
479                        input format specifier string, that value will\n\
480                        be used.\n\
481  -b, --base=DT         For underspecified input use DT as a fallback to\n\
482                        fill in missing fields.  Also used for ambiguous\n\
483                        format specifiers to position their range on the\n\
484                        absolute time line.\n\
485                        Must be a date/time in ISO8601 format.\n\
486                        If omitted defaults to the current date/time.\n\
487      --from-locale=LOCALE\n\
488                        Interpret dates on stdin or the command line as\n\
489                        coming from the locale LOCALE, this would only\n\
490                        affect month and weekday names as input formats\n\
491                        have to be specified explicitly.\n\
492      --from-zone=ZONE  Interpret dates on stdin or the command line as\n\
493                        coming from the time zone ZONE.\n\
494  -e, --backslash-escapes\n\
495                        Enable interpretation of backslash escapes in the\n\
496                        output and input format specifier strings.\n\
497      --eq              DATE/TIME1 is the same as DATE/TIME2\n\
498      --ne              DATE/TIME1 is not the same as DATE/TIME2\n\
499      --gt              DATE/TIME1 is newer than DATE/TIME2\n\
500      --lt              DATE/TIME1 is older than DATE/TIME2\n\
501      --ge              DATE/TIME1 is newer than or equals DATE/TIME2\n\
502      --le              DATE/TIME1 is older than or equals DATE/TIME2\n\
503      --nt              DATE/TIME1 is newer than DATE/TIME2\n\
504      --ot              DATE/TIME1 is older than DATE/TIME2\n\
505      --cmp             compare DATE/TIME1 to DATE/TIME2, return with 0 if\n\
506                        equal, 1 if left argument was newer and 2 if\n\
507                        right argument was newer\n\
508      --isvalid         Return success if dates specified conform to\n\
509                        input format.\n\
510");
511		break;
512
513	}
514
515#if defined yuck_post_help
516	yuck_post_help(src);
517#endif	/* yuck_post_help */
518
519#if defined PACKAGE_BUGREPORT
520	puts("\n\
521Report bugs to " PACKAGE_BUGREPORT);
522#endif	/* PACKAGE_BUGREPORT */
523	return;
524}
525
526static void yuck_auto_version(const yuck_t src[static 1U])
527{
528	switch (src->cmd) {
529	default:
530#if 0
531
532#elif defined package_string
533		puts(package_string);
534#elif defined package_version
535		printf("datetest %s\n", package_version);
536#elif defined PACKAGE_STRING
537		puts(PACKAGE_STRING);
538#elif defined PACKAGE_VERSION
539		puts("datetest " PACKAGE_VERSION);
540#elif defined VERSION
541		puts("datetest " VERSION);
542#else  /* !PACKAGE_VERSION, !VERSION */
543		puts("datetest unknown version");
544#endif	/* PACKAGE_VERSION */
545		break;
546	}
547
548#if defined yuck_post_version
549	yuck_post_version(src);
550#endif	/* yuck_post_version */
551	return;
552}
553
554#if defined __INTEL_COMPILER
555# pragma warning (pop)
556#elif defined __GNUC__
557# if __GNUC__ > 4 || __GNUC__ == 4 &&  __GNUC_MINOR__ >= 6
558#  pragma GCC diagnostic pop
559# endif	 /* GCC version */
560#endif	/* __INTEL_COMPILER */
561