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