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