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