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