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