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