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