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