From d92b42f49c3796d59690449f854c33e3463d95a3 Mon Sep 17 00:00:00 2001 From: nathants Date: Sun, 12 Jul 2020 12:23:05 -0700 Subject: [PATCH] initial commit --- .gitignore | 1 + Makefile | 10 +++++++ argh.h | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++ example.c | 25 ++++++++++++++++ license.txt | 21 ++++++++++++++ readme.md | 52 +++++++++++++++++++++++++++++++++ 6 files changed, 191 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 argh.h create mode 100644 example.c create mode 100644 license.txt create mode 100644 readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33a9488 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +example diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0d53b75 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: all clean +ALL=clean example + +all: $(ALL) + +clean: + rm -f example + +example: + gcc example.c -o example diff --git a/argh.h b/argh.h new file mode 100644 index 0000000..5e00d11 --- /dev/null +++ b/argh.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include + +#define ARGH_PARSE \ + bool argh_used = true; \ + bool argh_repeat = false; \ + bool argh_val = false; \ + char *ARGH_ARGV[argc]; \ + char *argh_name; \ + int ARGH_ARGC = 0; \ + int argh_diff = 0; \ + int argh_offset = 0; \ + while(argh_offset < argc) + +// check for a flag which is either present or not +#define ARGH_BOOL(short_name, long_name) \ + /* ARGH_VAL cannot be used with ARGH_BOOL */ \ + (argh_name = NULL, \ + /* short name exact match */ \ + (( 0 == strcmp(argv[argh_offset], short_name) \ + /* short name prefix match, for specify multiple short names with a single param, -xyz instead -x -y -z. this mutates argv for the next pass, -xyz to -yz */ \ + || ((0 == strncmp(argv[argh_offset], short_name, 2) && strlen(argv[argh_offset]) > 2 && (argv[argh_offset]++, argv[argh_offset][0] = '-', argh_repeat = true))) \ + /* long name exact match */ \ + || 0 == strcmp(argv[argh_offset], long_name)) \ + /* set argh_used to avoid adding to ARGH_ARGV, return true */ \ + ? argh_used = true \ + /* return false */ \ + : false)) + +// check for a flag which if present is followed by a value +#define ARGH_FLAG(short_name, long_name) \ + /* set argh_name so we can print it later as an error if no value is next in argv */ \ + (argh_name = long_name, \ + /* maybe set arg_diff=strlen(short_name) in case flag is "--nameVALUE" not "--name VALUE" */ \ + ( (argh_diff = strlen(argv[argh_offset]) != strlen(short_name) ? strlen(short_name) : 0, \ + /* short name prefix match, if argh_diff == 0 then this is an exact match */ \ + 0 == strncmp(argv[argh_offset], short_name, strlen(short_name))) \ + /* maybe set arg_diff=strlen(long_name) in case flag is "--nameVALUE" not "--name VALUE" */ \ + || (argh_diff = strlen(argv[argh_offset]) != strlen(long_name) ? strlen(long_name) : 0, \ + /* long name prefix match, if argh_diff == 0 then this is an exact match */ \ + 0 == strncmp(argv[argh_offset], long_name, strlen(long_name)))) \ + /* set argh_used to avoid adding to ARGH_ARGV, return true */ \ + ? argh_used = true \ + /* return false */ \ + : false) + +// get value for the current flag +#define ARGH_VAL() \ + /* assert can only be used with ARGH_FLAG */ \ + ((argh_name == NULL) \ + ? (fprintf(stderr, "fatal: ARGH_VAL can only be used with ARGH_FLAG\n", argh_name), exit(1), NULL) \ + /* if argh_diff, use suffix of current argv instead of next argv */ \ + : (argh_diff \ + ? (argv[argh_offset] + argh_diff) \ + /* else use next argv as long as it exists and does not start with "-" */ \ + : ((argh_offset < argc - 1 && strlen(argv[argh_offset + 1]) > 0 && argv[argh_offset + 1][0] != '-') \ + /* set argh_val to offset argv one additional space, return val */ \ + ? (argh_val = true, argv[argh_offset + 1]) \ + : (fprintf(stderr, "fatal: needs value %s\n", argh_name), exit(1), NULL)))) + +#define ARGH_NEXT() \ + /* reset argh name, which is set by ARGH_FLAG */ \ + argh_name = NULL; \ + /* if not argh_used, save as a positional argument for later, then reset argh_used */ \ + if (!argh_used) \ + ARGH_ARGV[ARGH_ARGC++] = argv[argh_offset]; \ + argh_used = false; \ + /* if argh_val, offset argv an additional space because of ARGH_VAL, then reset argh_val */ \ + if (argh_val) \ + argh_offset++; \ + argh_val = false; \ + /* if not argh_repeat, offset argv, then reset */ \ + if (!argh_repeat) \ + argh_offset++; \ + argh_repeat = false; \ + /* check if we are done with all of argv and break is so */ \ + if (argh_offset == argc) \ + break; diff --git a/example.c b/example.c new file mode 100644 index 0000000..82158d5 --- /dev/null +++ b/example.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +#include "argh.h" + +int main(int argc, char **argv) { + if (strcmp(argv[argc - 1], "-h") == 0 || strcmp(argv[argc - 1], "--help") == 0) { + fprintf(stderr, "usage: example [-l|--lz4] [-h N|--head N] [-p|--prefix] POS1 ... POSN\n"); + exit(1); + } + bool prefix = false; + bool lz4 = false; + int head = 0; + ARGH_PARSE { + ARGH_NEXT(); + if ARGH_BOOL("-p", "--prefix") { prefix = true;} + else if ARGH_BOOL("-l", "--lz4") { lz4 = true; } + else if ARGH_FLAG("-h", "--head") { head = atol(ARGH_VAL()); } + } + printf("head: %d, prefix: %d, lz4: %d\n", head, prefix, lz4); + for (int i = 0; i < ARGH_ARGC; i++) + printf("positional arg %d: %s\n", i, ARGH_ARGV[i]); +} diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..8a2de1a --- /dev/null +++ b/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020-present Nathan Todd-Stone + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..96757e1 --- /dev/null +++ b/readme.md @@ -0,0 +1,52 @@ +## why + +minimal [argument parsing](./example.c) shouldn't require a hundred lines of code. + +## what + +header only argument parsing for c inspired by the simplicity of [argh](https://pythonhosted.org/argh/). + +## example + +```c +#include "argh.h" + +int main(int argc, char **argv) { + if (strcmp(argv[argc - 1], "-h") == 0 || strcmp(argv[argc - 1], "--help") == 0) { + fprintf(stderr, "usage: example [-l|--lz4] [-h N|--head N] [-p|--prefix] POS1 ... POSN\n"); + exit(1); + } + bool prefix = false; + bool lz4 = false; + int head = 0; + ARGH_PARSE { + ARGH_NEXT(); + if ARGH_BOOL("-p", "--prefix") { prefix = true;} + else if ARGH_BOOL("-l", "--lz4") { lz4 = true; } + else if ARGH_FLAG("-h", "--head") { head = atol(ARGH_VAL()); } + } + printf("head: %d, prefix: %d, lz4: %d\n", head, prefix, lz4); + for (int i = 0; i < ARGH_ARGC; i++) + printf("pos arg %d: %s\n", i, ARGH_ARGV[i]); +} +``` + +```bash +>> make + +>> ./example -lph5 asdf 123 +head: 5, prefix: 1, lz4: 1 +pos arg 0: asdf +pos arg 1: 123 + +>> ./example --lz4 asdf -p 123 --head 5 +head: 5, prefix: 1, lz4: 1 +pos arg 0: asdf +pos arg 1: 123 + + +>> ./example asdf 123 --head 5 --lz4 +head: 5, prefix: 0, lz4: 1 +pos arg 0: asdf +pos arg 1: 123 +```