├── .gitignore ├── Makefile ├── package.json ├── .github └── workflows │ └── tests.yml ├── History.md ├── test.c ├── src ├── commander.h └── commander.c └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | test 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: test.c src/commander.c 3 | $(CC) $^ -o $@ -Wall -Wextra 4 | 5 | clean: 6 | rm -f test 7 | 8 | .PHONY: clean 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commander", 3 | "version": "1.4.0", 4 | "repo": "clibs/commander", 5 | "description": "Command-line argument parser", 6 | "keywords": ["cli", "command", "parser", "argv", "args", "options"], 7 | "license": "MIT", 8 | "src": ["src/commander.h", "src/commander.c"] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - run: sudo apt install -y valgrind 11 | - run: make test 12 | - run: valgrind --leak-check=full --error-exitcode=5 ./test 13 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.4.0 / 2025-03-25 3 | ================== 4 | 5 | * Setup CI (#28) 6 | * Add C++ support (#27) 7 | * Make short option optional 8 | 9 | 1.3.2 / 2013-12-18 10 | ================== 11 | 12 | * ANSI it up 13 | * fix comparison warning 14 | * fix warning when compiling with -Wshorten-64-to-32 flag 15 | 16 | 1.3.1 / 2013-10-15 17 | ================== 18 | 19 | * fix argv NULL 20 | 21 | 1.3.0 / 2013-10-10 22 | ================== 23 | 24 | * add ansi-friendly loops 25 | 26 | 1.2.1 / 2013-01-08 27 | ================== 28 | 29 | * add freeing of normalized argv 30 | * dont worry about zeroing in command_free() 31 | * fix docs 32 | 33 | 1.2.0 / 2013-01-08 34 | ================== 35 | 36 | * add command_free() 37 | 38 | 1.1.0 / 2012-12-18 39 | ================== 40 | 41 | * add short flag expansion 42 | 43 | 1.0.0 / 2012-12-13 44 | ================== 45 | 46 | * fix malloc(), add a byte for nul 47 | 48 | 0.0.1 / 2012-06-16 49 | ================== 50 | 51 | * Initial release 52 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "src/commander.h" 4 | 5 | static void 6 | verbose() { 7 | printf("verbose: enabled\n"); 8 | } 9 | 10 | static void 11 | required(command_t *self) { 12 | printf("required: %s\n", self->arg); 13 | } 14 | 15 | static void 16 | optional(command_t *self) { 17 | printf("optional: %s\n", self->arg); 18 | } 19 | 20 | static void 21 | omit_short(command_t *self) { 22 | printf("omitted short\n"); 23 | } 24 | 25 | int 26 | main(int argc, char **argv){ 27 | command_t cmd; 28 | command_init(&cmd, argv[0], "0.0.1"); 29 | command_option(&cmd, "-v", "--verbose", "enable verbose stuff", verbose); 30 | command_option(&cmd, "-r", "--required ", "required arg", required); 31 | command_option(&cmd, "-o", "--optional [arg]", "optional arg", optional); 32 | command_option(&cmd, NULL, "--omit-short", "omit short version by passing NULL", omit_short); 33 | command_parse(&cmd, argc, argv); 34 | printf("additional args:\n"); 35 | for (int i = 0; i < cmd.argc; ++i) { 36 | printf(" - '%s'\n", cmd.argv[i]); 37 | } 38 | command_free(&cmd); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/commander.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // commander.h 4 | // 5 | // Copyright (c) 2012 TJ Holowaychuk 6 | // 7 | 8 | #ifndef COMMANDER_H 9 | #define COMMANDER_H 10 | 11 | /* 12 | * Max options that can be defined. 13 | */ 14 | 15 | #ifndef COMMANDER_MAX_OPTIONS 16 | #define COMMANDER_MAX_OPTIONS 32 17 | #endif 18 | 19 | /* 20 | * Max arguments that can be passed. 21 | */ 22 | 23 | #ifndef COMMANDER_MAX_ARGS 24 | #define COMMANDER_MAX_ARGS 32 25 | #endif 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | namespace commander { 30 | #endif 31 | /* 32 | * Command struct. 33 | */ 34 | 35 | struct command; 36 | 37 | /* 38 | * Option callback. 39 | */ 40 | 41 | typedef void (* command_callback_t)(struct command *self); 42 | 43 | /* 44 | * Command option. 45 | */ 46 | 47 | typedef struct { 48 | int optional_arg; 49 | int required_arg; 50 | char *argname; 51 | char *large; 52 | const char *small; 53 | const char *large_with_arg; 54 | const char *description; 55 | command_callback_t cb; 56 | } command_option_t; 57 | 58 | /* 59 | * Command. 60 | */ 61 | 62 | typedef struct command { 63 | void *data; 64 | const char *usage; 65 | const char *arg; 66 | const char *name; 67 | const char *version; 68 | int option_count; 69 | command_option_t options[COMMANDER_MAX_OPTIONS]; 70 | int argc; 71 | char *argv[COMMANDER_MAX_ARGS]; 72 | char **nargv; 73 | } command_t; 74 | 75 | // prototypes 76 | 77 | void 78 | command_init(command_t *self, const char *name, const char *version); 79 | 80 | void 81 | command_free(command_t *self); 82 | 83 | void 84 | command_help(command_t *self); 85 | 86 | void 87 | command_option(command_t *self, const char *small, const char *large, const char *desc, command_callback_t cb); 88 | 89 | void 90 | command_parse(command_t *self, int argc, char **argv); 91 | 92 | #ifdef __cplusplus 93 | } 94 | } 95 | #endif /* __cplusplus */ 96 | #endif /* COMMANDER_H */ 97 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # commander 2 | 3 | Commander option parser ported to C. 4 | 5 | ## Installation 6 | 7 | Install with [clib](https://github.com/clibs/clib): 8 | 9 | ``` 10 | $ clib install clibs/commander 11 | ``` 12 | 13 | ## Automated --help 14 | 15 | The [example](#example) below would produce the following `--help`: 16 | 17 | ``` 18 | 19 | Usage: example [options] 20 | 21 | Options: 22 | 23 | -V, --version output program version 24 | -h, --help output help information 25 | -v, --verbose enable verbose stuff 26 | -r, --required required arg 27 | -o, --optional [arg] optional arg 28 | 29 | ``` 30 | 31 | ## Example 32 | 33 | ```c 34 | #include 35 | #include "commander.h" 36 | 37 | static void 38 | verbose(command_t *self) { 39 | printf("verbose: enabled\n"); 40 | } 41 | 42 | static void 43 | required(command_t *self) { 44 | printf("required: %s\n", self->arg); 45 | } 46 | 47 | static void 48 | optional(command_t *self) { 49 | printf("optional: %s\n", self->arg); 50 | } 51 | 52 | int 53 | main(int argc, const char **argv){ 54 | command_t cmd; 55 | command_init(&cmd, argv[0], "0.0.1"); 56 | command_option(&cmd, "-v", "--verbose", "enable verbose stuff", verbose); 57 | command_option(&cmd, "-r", "--required ", "required arg", required); 58 | command_option(&cmd, "-o", "--optional [arg]", "optional arg", optional); 59 | command_parse(&cmd, argc, argv); 60 | printf("additional args:\n"); 61 | for (int i = 0; i < cmd.argc; ++i) { 62 | printf(" - '%s'\n", cmd.argv[i]); 63 | } 64 | command_free(&cmd); 65 | return 0; 66 | } 67 | ``` 68 | 69 | ## Closure 70 | 71 | `cmd.data` is a `void *` so pass along a struct to the callbacks if you want. 72 | 73 | ## Usage 74 | 75 | `cmd.usage` defaults to "[options]". 76 | 77 | ## Links 78 | 79 | - Used by the [mon(1)](https://github.com/visionmedia/mon/blob/master/src/mon.c) process monitor 80 | 81 | ## Short flags 82 | 83 | Compound short flags are automatically expanded to their canonical form. For example `-vLO` would 84 | become `-v -L -O`. 85 | 86 | ## License 87 | 88 | (The MIT License) 89 | 90 | Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca> 91 | 92 | Permission is hereby granted, free of charge, to any person obtaining 93 | a copy of this software and associated documentation files (the 94 | 'Software'), to deal in the Software without restriction, including 95 | without limitation the rights to use, copy, modify, merge, publish, 96 | distribute, sublicense, and/or sell copies of the Software, and to 97 | permit persons to whom the Software is furnished to do so, subject to 98 | the following conditions: 99 | 100 | The above copyright notice and this permission notice shall be 101 | included in all copies or substantial portions of the Software. 102 | 103 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 104 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 105 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 106 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 107 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 108 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 109 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 110 | 111 | -------------------------------------------------------------------------------- /src/commander.c: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // commander.c 4 | // 5 | // Copyright (c) 2012 TJ Holowaychuk 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "commander.h" 13 | 14 | /* 15 | * Output error and exit. 16 | */ 17 | 18 | static void 19 | error(char *msg) { 20 | fprintf(stderr, "%s\n", msg); 21 | exit(1); 22 | } 23 | 24 | /* 25 | * Output command version. 26 | */ 27 | 28 | static void 29 | command_version(command_t *self) { 30 | printf("%s\n", self->version); 31 | command_free(self); 32 | exit(0); 33 | } 34 | 35 | /* 36 | * Output command help. 37 | */ 38 | 39 | void 40 | command_help(command_t *self) { 41 | printf("\n"); 42 | printf(" Usage: %s %s\n", self->name, self->usage); 43 | printf("\n"); 44 | printf(" Options:\n"); 45 | printf("\n"); 46 | 47 | int i; 48 | for (i = 0; i < self->option_count; ++i) { 49 | int omittedShort = 0; 50 | command_option_t *option = &self->options[i]; 51 | omittedShort = option->small == NULL; 52 | printf(" %s%c %-25s %s\n" 53 | , omittedShort ? " " : option->small 54 | , omittedShort ? ' ' : ',' 55 | , option->large_with_arg 56 | , option->description); 57 | } 58 | 59 | printf("\n"); 60 | command_free(self); 61 | exit(0); 62 | } 63 | 64 | /* 65 | * Initialize with program `name` and `version`. 66 | */ 67 | 68 | void 69 | command_init(command_t *self, const char *name, const char *version) { 70 | self->arg = NULL; 71 | self->name = name; 72 | self->version = version; 73 | self->option_count = self->argc = 0; 74 | self->usage = "[options]"; 75 | self->nargv = NULL; 76 | command_option(self, "-V", "--version", "output program version", command_version); 77 | command_option(self, "-h", "--help", "output help information", command_help); 78 | } 79 | 80 | /* 81 | * Free up commander after use. 82 | */ 83 | 84 | void 85 | command_free(command_t *self) { 86 | int i; 87 | 88 | for (i = 0; i < self->option_count; ++i) { 89 | command_option_t *option = &self->options[i]; 90 | free(option->argname); 91 | free(option->large); 92 | } 93 | 94 | if (self->nargv) { 95 | for (i = 0; self->nargv[i]; ++i) { 96 | free(self->nargv[i]); 97 | } 98 | free(self->nargv); 99 | } 100 | } 101 | 102 | /* 103 | * Parse argname from `str`. For example 104 | * Take "--required " and populate `flag` 105 | * with "--required" and `arg` with "". 106 | */ 107 | 108 | static void 109 | parse_argname(const char *str, char *flag, char *arg) { 110 | int buffer = 0; 111 | size_t flagpos = 0; 112 | size_t argpos = 0; 113 | size_t len = strlen(str); 114 | size_t i; 115 | 116 | for (i = 0; i < len; ++i) { 117 | if (buffer || '[' == str[i] || '<' == str[i]) { 118 | buffer = 1; 119 | arg[argpos++] = str[i]; 120 | } else { 121 | if (' ' == str[i]) continue; 122 | flag[flagpos++] = str[i]; 123 | } 124 | } 125 | 126 | arg[argpos] = '\0'; 127 | flag[flagpos] = '\0'; 128 | } 129 | 130 | /* 131 | * Normalize the argument vector by exploding 132 | * multiple options (if any). For example 133 | * "foo -abc --scm git" -> "foo -a -b -c --scm git" 134 | */ 135 | 136 | static char ** 137 | normalize_args(int *argc, char **argv) { 138 | int size = 0; 139 | int alloc = *argc + 1; 140 | char **nargv = malloc(alloc * sizeof(char *)); 141 | int i; 142 | 143 | for (i = 0; argv[i]; ++i) { 144 | const char *arg = argv[i]; 145 | size_t len = strlen(arg); 146 | 147 | // short flag 148 | if (len > 2 && '-' == arg[0] && !strchr(arg + 1, '-')) { 149 | alloc += len - 2; 150 | nargv = realloc(nargv, alloc * sizeof(char *)); 151 | for (size_t j = 1; j < len; ++j) { 152 | nargv[size] = malloc(3); 153 | sprintf(nargv[size], "-%c", arg[j]); 154 | size++; 155 | } 156 | continue; 157 | } 158 | 159 | // regular arg 160 | nargv[size] = malloc(len + 1); 161 | strcpy(nargv[size], arg); 162 | size++; 163 | } 164 | 165 | nargv[size] = NULL; 166 | *argc = size; 167 | return nargv; 168 | } 169 | 170 | /* 171 | * Define an option. 172 | */ 173 | 174 | void 175 | command_option(command_t *self, const char *small, const char *large, const char *desc, command_callback_t cb) { 176 | if (self->option_count == COMMANDER_MAX_OPTIONS) { 177 | command_free(self); 178 | error("Maximum option definitions exceeded"); 179 | } 180 | int n = self->option_count++; 181 | command_option_t *option = &self->options[n]; 182 | option->cb = cb; 183 | option->small = small; 184 | option->description = desc; 185 | option->required_arg = option->optional_arg = 0; 186 | option->large_with_arg = large; 187 | option->argname = malloc(strlen(large) + 1); 188 | assert(option->argname); 189 | option->large = malloc(strlen(large) + 1); 190 | assert(option->large); 191 | parse_argname(large, option->large, option->argname); 192 | if ('[' == option->argname[0]) option->optional_arg = 1; 193 | if ('<' == option->argname[0]) option->required_arg = 1; 194 | } 195 | 196 | /* 197 | * Parse `argv` (internal). 198 | * Input arguments should be normalized first 199 | * see `normalize_args`. 200 | */ 201 | 202 | static void 203 | command_parse_args(command_t *self, int argc, char **argv) { 204 | int literal = 0; 205 | int i, j; 206 | 207 | for (i = 1; i < argc; ++i) { 208 | const char *arg = argv[i]; 209 | for (j = 0; j < self->option_count; ++j) { 210 | command_option_t *option = &self->options[j]; 211 | 212 | // match flag 213 | if ((option->small != NULL && !strcmp(arg, option->small)) || !strcmp(arg, option->large)) { 214 | self->arg = NULL; 215 | 216 | // required 217 | if (option->required_arg) { 218 | arg = argv[++i]; 219 | if (!arg || '-' == arg[0]) { 220 | fprintf(stderr, "%s %s argument required\n", option->large, option->argname); 221 | command_free(self); 222 | exit(1); 223 | } 224 | self->arg = arg; 225 | } 226 | 227 | // optional 228 | if (option->optional_arg) { 229 | if (argv[i + 1] && '-' != argv[i + 1][0]) { 230 | self->arg = argv[++i]; 231 | } 232 | } 233 | 234 | // invoke callback 235 | option->cb(self); 236 | goto match; 237 | } 238 | } 239 | 240 | // -- 241 | if ('-' == arg[0] && '-' == arg[1] && 0 == arg[2]) { 242 | literal = 1; 243 | goto match; 244 | } 245 | 246 | // unrecognized 247 | if ('-' == arg[0] && !literal) { 248 | fprintf(stderr, "unrecognized flag %s\n", arg); 249 | command_free(self); 250 | exit(1); 251 | } 252 | 253 | int n = self->argc++; 254 | if (n == COMMANDER_MAX_ARGS) { 255 | command_free(self); 256 | error("Maximum number of arguments exceeded"); 257 | } 258 | self->argv[n] = (char *) arg; 259 | match:; 260 | } 261 | } 262 | 263 | /* 264 | * Parse `argv` (public). 265 | */ 266 | 267 | void 268 | command_parse(command_t *self, int argc, char **argv) { 269 | self->nargv = normalize_args(&argc, argv); 270 | command_parse_args(self, argc, self->nargv); 271 | self->argv[self->argc] = NULL; 272 | } 273 | --------------------------------------------------------------------------------