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