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