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