From fd56c5124ad7ea8c40b64eaf007e34a08fe77b55 Mon Sep 17 00:00:00 2001 From: Connor Date: Thu, 18 Aug 2016 19:46:44 -0700 Subject: [PATCH] --- kyaa.h | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ kyaa.md | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 kyaa.h create mode 100644 kyaa.md diff --git a/kyaa.h b/kyaa.h new file mode 100644 index 0000000..a90b4a9 --- /dev/null +++ b/kyaa.h @@ -0,0 +1,124 @@ +/* kyaa.h - macro hacks for handling main() arguments + license: public domain or whatever. + documentation is kept separate in kyaa.md +*/ + +#pragma once + +#ifndef KYAA_OKAY +#define KYAA_OKAY 0 +#endif +#ifndef KYAA_ERROR +#define KYAA_ERROR 1 +#endif +#ifndef KYAA_ITER +#define KYAA_ITER i +#endif + +#define KYAA_SETUP \ + /* dumb sanity checks */ \ + if (argc <= 0 || argv == NULL || argv[0] == NULL || argv[0][0] == '\0') { \ + fprintf(stderr, "You've met with a terrible fate.\n"); \ + return KYAA_ERROR; \ + } \ + /* read-only */ \ + char *kyaa_name = argv[0]; \ + bool kyaa_read_stdin = false; \ + char kyaa_flag = '\0'; \ + /* internal */ \ + bool kyaa_keep_parsing = true; \ + bool kyaa_parse_next = false; \ + +#define KYAA_LOOP \ + KYAA_SETUP \ + for (int KYAA_ITER = 1; KYAA_ITER < argc; KYAA_ITER++) \ + +#define KYAA_BEGIN \ + char *kyaa_arg = argv[KYAA_ITER]; \ + if (kyaa_keep_parsing && (kyaa_parse_next || kyaa_arg[0] == '-')) { \ + if (!kyaa_parse_next && kyaa_arg[1] == '-' && kyaa_arg[2] == '\0') { \ + kyaa_keep_parsing = false; \ + continue; \ + } \ + if (!kyaa_parse_next && kyaa_arg[1] == '\0') { \ + kyaa_read_stdin = true; \ + } else { \ + /* case: kyaa_parse_next: arg is at least 1 char initialized. */ \ + /* case: !kyaa_parse_next: arg is at least 3 chars initialized. */ \ + char *kyaa_etc = kyaa_parse_next ? kyaa_arg : kyaa_arg + 2; \ + bool kyaa_no_more = false; \ + bool kyaa_helping = false; \ + bool kyaa_any = false; \ + if (!kyaa_parse_next && kyaa_arg[1] != '-') { \ + kyaa_flag = kyaa_arg[1]; \ + kyaa_no_more = kyaa_arg[2] == '\0'; \ + } \ + if (kyaa_flag == 'h' || !strcmp(kyaa_arg, "--help")) { \ + printf("usage:\n"); \ + kyaa_helping = true; \ + } \ + if (0) { \ + +#define KYAA_END \ + } \ + if (!kyaa_any && !kyaa_helping) { \ + if (kyaa_flag) { \ + fprintf(stderr, "unknown flag: -%c\n", kyaa_flag); \ + } else { \ + fprintf(stderr, "unknown flag: %s\n", kyaa_arg); \ + } \ + return KYAA_ERROR; \ + } \ + if (kyaa_helping) { \ + return KYAA_OKAY; \ + } \ + kyaa_parse_next = false; \ + kyaa_flag = '\0'; \ + continue; \ + } \ + } \ + +#define KYAA_DESCRIBE(c, name, description) \ + printf(" -%c --%s\n%s\n", c, name, description) \ + +#define KYAA_FLAG(c, name, description) \ + } \ + if (kyaa_helping) { \ + KYAA_DESCRIBE(c, name, description); \ + } else if (kyaa_flag == c || !strcmp(kyaa_arg, "--"name)) { \ + kyaa_flag = c; \ + kyaa_any = true; \ + +#define KYAA_FLAG_ARG(c, name, description) \ + } \ + if (kyaa_helping) { \ + KYAA_DESCRIBE(c, name, description); \ + } else if (kyaa_flag == c || !strcmp(kyaa_arg, "--"name)) { \ + if (kyaa_no_more || kyaa_flag != c) { \ + kyaa_parse_next = true; \ + if (KYAA_ITER + 1 == argc || argv[KYAA_ITER + 1][0] == '\0') { \ + fprintf(stderr, "expected an argument for --%s (-%c)\n", name, c); \ + return KYAA_ERROR; \ + } \ + kyaa_flag = c; \ + continue; \ + } \ + kyaa_flag = c; \ + kyaa_any = true; \ + +#define KYAA_FLAG_LONG(c, name, description) \ + KYAA_FLAG_ARG(c, name, description) \ + errno = 0; \ + long kyaa_flag_arg = strtol(kyaa_etc, NULL, 0); \ + if (errno) { \ + perror(NULL); \ + return KYAA_ERROR; \ + } \ + +#define KYAA_HELP(description) \ + } \ + if (kyaa_helping) { \ + if (description != NULL) { \ + printf("%s\n", description); \ + } \ + diff --git a/kyaa.md b/kyaa.md new file mode 100644 index 0000000..b198cd0 --- /dev/null +++ b/kyaa.md @@ -0,0 +1,117 @@ +# kyaa + +super hacky macro hacks for parsing arguments in C. + +## prerequisites + +C99 or greater. + +standard library headers: +* errno.h +* stdbool.h +* stdio.h +* string.h + +## tutorial/API + +ensure argc and argv are defined. +kyaa doesn't actually care if it's in main() or not. + +iterate over the arguments with KYAA\_LOOP: + +```c +int main(int argc, char *argv[]) { + KYAA_LOOP { + // i, kyaa_name, kyaa_read_stdin, and kyaa_flag are exposed here. + // [other code goes here] + } + return 0; +} +``` + +use KYAA\_BEGIN and KYAA\_END to begin parsing arguments. +put unrelated code above KYAA\_BEGIN or below KYAA\_END, but not within: + +```c + KYAA_LOOP { + // [other code goes here] + KYAA_BEGIN + // kyaa_arg and kyaa_etc are exposed here. + // [kyaa code goes here] + KYAA_END + // [other code goes here] + } +``` + +use KYAA\_FLAG for defining flags that don't take an argument, +and KYAA\_FLAG\_ARG or KYAA\_FLAG\_LONG for flags that do: + +```c + bool use_feature = false; + char *log_fn = "logs.txt"; + long my_var = 0; + KYAA_LOOP { + // [other code goes here] + KYAA_BEGIN + + // arguments: short flag, long flag, help description + KYAA_FLAG("-x", "--enable-feature", +" enable some feature") + use_feature = true; + + // same arguments, but kyaa_etc contains the relevant string. + KYAA_FLAG_ARG("-l", "--log-file", +" use a given filename for the log file") + log_fn = kyaa_etc; + + // same arguments, kyaa_etc is set, but kyaa_flag_arg is also set. + KYAA_FLAG_LONG("-v", "--var", +" set an integer variable\n" +" default: 0") + my_var = kyaa_flag_arg; + + KYAA_END + // [other code goes here] + } +``` + +kyaa secretly wraps flag handling in if/else statements with {} blocks. +do not confuse it for a switch/case method. + +kyaa handles -h and --help for printing help text. +additional help text may be defined using KYAA\_HELP: + +```c + KYAA_LOOP { + KYAA_BEGIN + KYAA_HELP( +" {files...}\n" +" do things with files.") + KYAA_END + + do_stuff(kyaa_arg); + } +``` + +kyaa interprets an argument of "-" as a request to enable reading from stdin: +kyaa\_read\_stdin is set to true. + +arguments may be passed in three ways, consider: +* `-v42` +* `-v 42` +* `--var 42` + +kyaa returns KYAA\_OKAY when -h or --help is given, +and KYAA\_ERROR in the event of invalid flags, missing arguments, +or invalid numbers (KYAA\_FLAG\_LONG). +kyaa uses continue for handling arguments. + +kyaa prints error messages to stderr, and help text to stdout. + +KYAA\_ITER may be redefined to avoid name collisions. + +## TODO + +* support `--var=42` argument style +* rename overlapping things, e.g. KYAA\_FLAG vs kyaa\_flag, KYAA\_FLAG\_ARG vs kyaa\_flag\_arg, etc. +* move KYAA\_FLAG\_ARG to `kyaa_extend.h` or something; write similar macros. \ No newline at end of file