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