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	DATEZONE_CMD_NONE = 0U,
12
13	/* actual commands */
14
15	/* convenience identifiers */
16	YUCK_NOCMD = DATEZONE_CMD_NONE,
17	YUCK_NCMDS = DATEZONE_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	char *base_arg;
41	size_t input_format_nargs; char **input_format_args;
42	char *from_locale_arg;
43	char *from_zone_arg;
44	unsigned int next_flag;
45	unsigned int prev_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, "datezone: 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 DATEZONE_CMD_NONE_longopt; back_from_DATEZONE_CMD_NONE_longopt:;
211			break;
212		}
213		goto back_from_longopt;
214
215
216		DATEZONE_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, "quiet")) {
229				tgt->quiet_flag++; goto xtra_chk;
230			} else if (yuck_streqp(op, "base")) {
231				tgt->base_arg = arg ?: argv[++i];
232			} else if (yuck_streqp(op, "input-format")) {
233				tgt->input_format_args =
234					yuck_append(
235						tgt->input_format_args, tgt->input_format_nargs++,
236						arg ?: argv[++i]);
237				if (tgt->input_format_args == NULL) {
238					return -1;
239				};
240			} else if (yuck_streqp(op, "from-locale")) {
241				tgt->from_locale_arg = arg ?: argv[++i];
242			} else if (yuck_streqp(op, "from-zone")) {
243				tgt->from_zone_arg = arg ?: argv[++i];
244			} else if (yuck_streqp(op, "next")) {
245				tgt->next_flag++; goto xtra_chk;
246			} else if (yuck_streqp(op, "prev")) {
247				tgt->prev_flag++; goto xtra_chk;
248			} else {
249				/* grml */
250				fprintf(stderr, "datezone: unrecognized option `--%s'\n", op);
251				goto failure;
252			xtra_chk:
253				if (arg != NULL) {
254					fprintf(stderr, "datezone: option `--%s' doesn't allow an argument\n", op);
255					goto failure;
256				}
257			}
258			if (i >= argc) {
259				fprintf(stderr, "datezone: option `--%s' requires an argument\n", op);
260				goto failure;
261			}
262			goto back_from_DATEZONE_CMD_NONE_longopt;
263		}
264
265	}
266
267	shortopt:
268	{
269		char *arg = op + 1U;
270
271		switch (tgt->cmd) {
272		default:
273			goto DATEZONE_CMD_NONE_shortopt; back_from_DATEZONE_CMD_NONE_shortopt:;
274			break;
275		}
276		goto back_from_shortopt;
277
278
279		DATEZONE_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, "datezone: 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 'q':
317				tgt->quiet_flag++;
318				break;
319			case 'b':
320				tgt->base_arg = *arg
321					? (op += strlen(arg), arg)
322					: argv[++i];
323				break;
324			case 'i':
325				tgt->input_format_args =
326					yuck_append(
327						tgt->input_format_args,
328						tgt->input_format_nargs++,
329						*arg ? (op += strlen(arg), arg) : argv[++i]);
330				if (tgt->input_format_args == NULL) {
331					return -1;
332				};
333				break;
334			}
335			if (i >= argc) {
336				fprintf(stderr, "datezone: option `--%s' requires an argument\n", op);
337				goto failure;
338			}
339			goto back_from_DATEZONE_CMD_NONE_shortopt;
340		}
341
342	}
343
344	arg:
345	{
346		if (tgt->cmd || YUCK_NCMDS == 0U) {
347			tgt->args[tgt->nargs++] = argv[i];
348		} else {
349			/* ah, might be an arg then */
350			if ((tgt->cmd = yuck_parse_cmd(op)) > YUCK_NCMDS) {
351				return -1;
352			}
353		}
354		goto back_from_arg;
355	}
356
357	failure:
358	{
359		exit(EXIT_FAILURE);
360	}
361
362	success:
363	{
364		exit(EXIT_SUCCESS);
365	}
366}
367
368static void yuck_free(yuck_t tgt[static 1U])
369{
370	if (tgt->args != NULL) {
371		/* free despite const qualifier */
372		free(tgt->args);
373	}
374	/* free mulargs */
375	switch (tgt->cmd) {
376		void *ptr;
377	default:
378		break;
379	case DATEZONE_CMD_NONE:
380;
381;
382;
383;
384		ptr = tgt->input_format_args;
385		if (ptr != NULL) {
386			free(ptr);
387		}
388;
389;
390;
391;
392;
393		break;
394	}
395	return;
396}
397
398static void yuck_auto_usage(const yuck_t src[static 1U])
399{
400	switch (src->cmd) {
401	default:
402	YUCK_NOCMD:
403		puts("Usage: datezone [OPTION]... [ZONENAME]... [DATE/TIME]...\n\
404\n\
405Convert DATE/TIMEs between timezones.\n\
406If DATE/TIME is omitted, it defaults to `now'.\n\
407\n\
408DATE/TIME can also be one of the following specials\n\
409  - `now'           interpreted as the current (UTC) time stamp\n\
410  - `time'          the time part of the current (UTC) time stamp\n\
411  - `today'         the current date (according to UTC)\n\
412  - `tomo[rrow]'    tomorrow's date (according to UTC)\n\
413  - `y[ester]day'   yesterday's date (according to UTC)\n\
414");
415		break;
416
417	}
418
419#if defined yuck_post_usage
420	yuck_post_usage(src);
421#endif	/* yuck_post_usage */
422	return;
423}
424
425static void yuck_auto_help(const yuck_t src[static 1U])
426{
427	yuck_auto_usage(src);
428
429
430	/* leave a not about common options */
431	if (src->cmd == YUCK_NOCMD) {
432		;
433	}
434
435	switch (src->cmd) {
436	default:
437	case DATEZONE_CMD_NONE:
438		puts("\
439  -h, --help            display this help and exit\n\
440  -V, --version         output version information and exit\n\
441  -q, --quiet           Suppress message about date/time or zonename\n\
442                        parser errors and fix-ups.\n\
443                        The default is to print a warning or the\n\
444                        fixed up value and return error code 2.\n\
445  -b, --base=DT         For underspecified input use DT as a fallback to\n\
446                        fill in missing fields.  Also used for ambiguous\n\
447                        format specifiers to position their range on the\n\
448                        absolute time line.\n\
449                        Must be a date/time in ISO8601 format.\n\
450                        If omitted defaults to the current date/time.\n\
451  -i, --input-format=STRING...\n\
452                        Input format, can be used multiple times.\n\
453                        Each date/time will be passed to the input\n\
454                        format parsers in the order they are given, if a\n\
455                        date/time can be read successfully with a given\n\
456                        input format specifier string, that value will\n\
457                        be used.\n\
458      --from-locale=LOCALE\n\
459                        Interpret dates on stdin or the command line as\n\
460                        coming from the locale LOCALE, this would only\n\
461                        affect month and weekday names as input formats\n\
462                        have to be specified explicitly.\n\
463      --from-zone=ZONE  Interpret dates on stdin or the command line as\n\
464                        coming from the time zone ZONE.\n\
465      --next            Show next transition from/to DST.\n\
466      --prev            Show previous transition from/to DST.\n\
467");
468		break;
469
470	}
471
472#if defined yuck_post_help
473	yuck_post_help(src);
474#endif	/* yuck_post_help */
475
476#if defined PACKAGE_BUGREPORT
477	puts("\n\
478Report bugs to " PACKAGE_BUGREPORT);
479#endif	/* PACKAGE_BUGREPORT */
480	return;
481}
482
483static void yuck_auto_version(const yuck_t src[static 1U])
484{
485	switch (src->cmd) {
486	default:
487#if 0
488
489#elif defined package_string
490		puts(package_string);
491#elif defined package_version
492		printf("datezone %s\n", package_version);
493#elif defined PACKAGE_STRING
494		puts(PACKAGE_STRING);
495#elif defined PACKAGE_VERSION
496		puts("datezone " PACKAGE_VERSION);
497#elif defined VERSION
498		puts("datezone " VERSION);
499#else  /* !PACKAGE_VERSION, !VERSION */
500		puts("datezone unknown version");
501#endif	/* PACKAGE_VERSION */
502		break;
503	}
504
505#if defined yuck_post_version
506	yuck_post_version(src);
507#endif	/* yuck_post_version */
508	return;
509}
510
511#if defined __INTEL_COMPILER
512# pragma warning (pop)
513#elif defined __GNUC__
514# if __GNUC__ > 4 || __GNUC__ == 4 &&  __GNUC_MINOR__ >= 6
515#  pragma GCC diagnostic pop
516# endif	 /* GCC version */
517#endif	/* __INTEL_COMPILER */
518