├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── argz.c ├── argz.h ├── comp ├── argz.bash ├── argz.yash └── argz.zsh ├── readme.c ├── readme.sh └── tis.config /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | .dirstamp 3 | *.[ios] 4 | readme 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: c 4 | 5 | os: 6 | - linux 7 | - osx 8 | 9 | compiler: 10 | - clang 11 | - gcc 12 | 13 | script: 14 | - make CFLAGS=-std=c99 argz.o 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2022, Adrien Gallouët 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arg(z) 2 | 3 | A minimal command line library, with out-of-the-box auto-completion. 4 | 5 | Compile [readme.c](./readme.c): 6 | 7 | $ gcc -o readme readme.c 8 | 9 | Enable auto-competion for `bash`: 10 | 11 | $ source comp/argz.bash 12 | $ complete -F _argz ./readme 13 | 14 | A minimal help command is automatically generated: 15 | 16 | $ ./readme help 17 | size a size value 18 | time a time value 19 | percent a custom percent value 20 | file a file value 21 | 22 | Option `size` accepts some suffixes: 23 | 24 | $ ./readme size 10m 25 | size=10485760 time=0 percent=50 file= 26 | 27 | $ ./readme size 10M 28 | size=10485760 time=0 percent=50 file= 29 | 30 | $ ./readme size 10MiB 31 | size=10485760 time=0 percent=50 file= 32 | 33 | $ ./readme size 10Mibytes 34 | size=10485760 time=0 percent=50 file= 35 | 36 | $ ./readme size 10MB 37 | size=10000000 time=0 percent=50 file= 38 | 39 | $ ./readme size 10MBytes 40 | size=10000000 time=0 percent=50 file= 41 | 42 | $ ./readme size 10Mb 43 | size=1250000 time=0 percent=50 file= 44 | 45 | $ ./readme size 10Mibits 46 | size=1310720 time=0 percent=50 file= 47 | 48 | Option `time` too: 49 | 50 | $ ./readme time 10s 51 | size=0 time=10000 percent=50 file= 52 | 53 | $ ./readme time 10m 54 | size=0 time=600000 percent=50 file= 55 | 56 | $ ./readme time 10h 57 | size=0 time=36000000 percent=50 file= 58 | 59 | $ ./readme time 10d 60 | size=0 time=864000000 percent=50 file= 61 | 62 | Malformed options are reported: 63 | 64 | $ ./readme bad 65 | Option bad is unknown 66 | 67 | $ ./readme size 68 | Option size requires a number 69 | 70 | $ ./readme size 0 71 | Option size must be a number greater than or equal to 1 72 | 73 | $ ./readme size NaN 74 | Option size is not a valid number 75 | 76 | $ ./readme time 1y 77 | Option time is badly suffixed 78 | 79 | Classic input formats are supported: 80 | 81 | $ ./readme size -1 82 | size=18446744073709551615 time=0 percent=50 file= 83 | 84 | $ ./readme size 0xFF 85 | size=255 time=0 percent=50 file= 86 | 87 | $ ./readme size 010 88 | size=8 time=0 percent=50 file= 89 | 90 | Duplicates are detected: 91 | 92 | $ ./readme size 10 size 20 93 | Option size is already set 94 | -------------------------------------------------------------------------------- /argz.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "argz.h" 10 | 11 | #ifndef ARGZ_LEN 12 | #define ARGZ_LEN 14 13 | #endif 14 | 15 | int 16 | argz_help(int argc, char **argv) 17 | { 18 | for (int i = 1; i < argc; i++) { 19 | if (!strcmp(argv[i], "help")) 20 | return 1; 21 | } 22 | return 0; 23 | } 24 | 25 | int argz_help_me(int argc, char **argv) 26 | { 27 | return argz_help(argc > 2 ? 2 : argc, argv); 28 | } 29 | 30 | static int 31 | argz_cmp(struct argz *z, const char *name) 32 | { 33 | if (!strcmp(z->name, name)) 34 | return 0; 35 | if (z->alt) for (int k = 0; z->alt[k]; k++) { 36 | if (!strcmp(z->alt[k], name)) 37 | return 0; 38 | } 39 | return 1; 40 | } 41 | 42 | static int 43 | argz_is_available(struct argz *z, int i, int *ret) 44 | { 45 | if (z[i].set) 46 | return 0; 47 | if (z[i].grp > 0) for (int k = 0; z[k].name; k++) { 48 | if (z[k].set && z[k].grp == z[i].grp) { 49 | if (ret) *ret = k; 50 | return 0; 51 | } 52 | } 53 | return 1; 54 | } 55 | 56 | int 57 | argz_is_set(struct argz *z, const char *name) 58 | { 59 | if (z) for (int i = 0; z[i].name; i++) { 60 | if (argz_cmp(&z[i], name)) 61 | continue; 62 | return z[i].set; 63 | } 64 | return 0; 65 | } 66 | 67 | void 68 | argz_print_help(const char *name, const char *help) 69 | { 70 | printf(" %-*s %s\n", ARGZ_LEN, name, help ? help : ""); 71 | } 72 | 73 | void 74 | argz_print(struct argz *z) 75 | { 76 | if (z) for (int i = 0; z[i].name; i++) { 77 | if (!argz_is_available(z, i, NULL)) 78 | continue; 79 | argz_print_help(z[i].name, z[i].help); 80 | } 81 | } 82 | 83 | int 84 | argz_str(int argc, char **argv, void *data) 85 | { 86 | if (argz_help_me(argc, argv)) { 87 | const char *str = *(const char **)data; 88 | if (str && *str) 89 | printf("%s\n", str); 90 | } else if (argc > 1) { 91 | memcpy(data, &argv[1], sizeof(char *)); 92 | return argc - 2; 93 | } else { 94 | fprintf(stderr, "Option %s requires a value\n", argv[0]); 95 | } 96 | return -1; 97 | } 98 | 99 | int 100 | argz_path(int argc, char **argv, void *data) 101 | { 102 | struct argz_path *z = (struct argz_path *)data; 103 | 104 | if (argz_help_me(argc, argv)) { 105 | if (z->dir) { 106 | printf("DIR\n"); 107 | } else { 108 | printf("FILE\n"); 109 | } 110 | } else if (argc > 1) { 111 | z->path = argv[1]; 112 | return argc - 2; 113 | } else { 114 | fprintf(stderr, "Option %s requires a path\n", argv[0]); 115 | } 116 | return -1; 117 | } 118 | 119 | int 120 | argz_ull(int argc, char **argv, void *data) 121 | { 122 | struct argz_ull *z = (struct argz_ull *)data; 123 | 124 | if (argz_help_me(argc, argv)) { 125 | unsigned long long value = z->value; 126 | if (z->min && (z->min > z->value)) 127 | value = z->min; 128 | if (z->max && (z->max < z->value)) 129 | value = z->max; 130 | printf("%llu\n", value); 131 | } else if (argc > 1) { 132 | char *end = NULL; 133 | z->value = (errno = 0, strtoull(argv[1], &end, z->base)); 134 | if (errno == ERANGE) { 135 | fprintf(stderr, "Option %s is out of range\n", argv[0]); 136 | return -1; 137 | } 138 | if (errno || end == argv[1]) { 139 | fprintf(stderr, "Option %s is not a valid number\n", argv[0]); 140 | return -1; 141 | } 142 | if ((z->suffix && z->suffix(z, end)) || (!z->suffix && end && *end)) { 143 | fprintf(stderr, "Option %s is badly suffixed\n", argv[0]); 144 | return -1; 145 | } 146 | if (z->min && (z->value < z->min)) { 147 | fprintf(stderr, "Option %s must be a number greater than or equal to %llu\n", 148 | argv[0], z->min); 149 | return -1; 150 | } 151 | if (z->max && (z->value > z->max)) { 152 | fprintf(stderr, "Option %s must be a number less than or equal to %llu\n", 153 | argv[0], z->max); 154 | return -1; 155 | } 156 | return argc - 2; 157 | } else { 158 | fprintf(stderr, "Option %s requires a number\n", argv[0]); 159 | } 160 | return -1; 161 | } 162 | 163 | int 164 | argz_time_suffix(struct argz_ull *ull, const char *s) 165 | { 166 | if (!s) 167 | return 0; 168 | 169 | if (!strcmp(s, "ms")) 170 | return 0; 171 | 172 | if (s[0] && s[1]) 173 | return -1; 174 | 175 | unsigned long long i = 1; 176 | 177 | switch (s[0]) { 178 | default : return -1; 179 | case 'w': i *= 7; /* FALLTHRU */ 180 | case 'd': i *= 24; /* FALLTHRU */ 181 | case 'h': i *= 60; /* FALLTHRU */ 182 | case 'm': i *= 60; /* FALLTHRU */ 183 | case 0 : /* FALLTHRU */ 184 | case 's': i *= 1000; /* FALLTHRU */ 185 | } 186 | if (!i || ull->value > ULLONG_MAX / i) 187 | return -1; 188 | 189 | ull->value *= i; 190 | return 0; 191 | } 192 | 193 | int 194 | argz_size_suffix(struct argz_ull *ull, const char *s) 195 | { 196 | if (!s) 197 | return 0; 198 | 199 | unsigned n = 0; 200 | 201 | switch (s[0]) { 202 | case 'g': /* FALLTHRU */ 203 | case 'G': n++; /* FALLTHRU */ 204 | case 'm': /* FALLTHRU */ 205 | case 'M': n++; /* FALLTHRU */ 206 | case 'k': /* FALLTHRU */ 207 | case 'K': n++; s++; /* FALLTHRU */ 208 | } 209 | 210 | unsigned long long c = 1024; 211 | unsigned long long i = 1; 212 | 213 | if (s[0] == 'i') { 214 | s++; 215 | } else if (s[0]) { 216 | c = 1000; 217 | } 218 | while (n--) 219 | i *= c; 220 | 221 | if (!strcmp(s, "b") || !strcasecmp(s, "bit") 222 | || !strcasecmp(s, "bits")) { 223 | i >>= 3; 224 | } else if (s[0] && strcmp(s, "B") && strcasecmp(s, "byte") 225 | && strcasecmp(s, "bytes")) { 226 | return -1; 227 | } 228 | if (!i || ull->value > ULLONG_MAX / i) 229 | return -1; 230 | 231 | ull->value *= i; 232 | return 0; 233 | } 234 | 235 | int 236 | argz(int argc, char **argv, void *data) 237 | { 238 | struct argz *z = (struct argz *)data; 239 | int a = 1; 240 | 241 | while (a < argc) { 242 | if (!strcmp(argv[a], "help")) { 243 | argz_print(z); 244 | return -2; 245 | } 246 | int set = 0; 247 | if (z) for (int i = 0; z[i].name; i++) { 248 | if (argz_cmp(&z[i], argv[a])) 249 | continue; 250 | int k = -1; 251 | if (!argz_is_available(z, i, &k)) { 252 | if (k == -1) { 253 | fprintf(stderr, "Option %s is already set\n", 254 | z[i].name); 255 | } else { 256 | fprintf(stderr, "Option %s is not compatible with %s\n", 257 | z[i].name, z[k].name); 258 | } 259 | return -1; 260 | } 261 | z[i].set = set = 1; 262 | int ret = z[i].call ? z[i].call(argc - a, argv + a, z[i].data) 263 | : argc - a - 1; 264 | if (ret < 0) { 265 | if (ret == -2) 266 | argz_print(z); 267 | return ret; 268 | } 269 | a = argc - ret; 270 | break; 271 | } 272 | if (!set) 273 | break; 274 | } 275 | if (z) for (int i = 0; z[i].name; i++) { 276 | if (z[i].set || z[i].grp >= 0) 277 | continue; 278 | fprintf(stderr, "Option %s is mandatory\n", z[i].name); 279 | return -1; 280 | } 281 | return argc - a; 282 | } 283 | 284 | int 285 | argz_main(int argc, char **argv, struct argz *z) 286 | { 287 | int ret = argz(argc, argv, z); 288 | 289 | if (ret > 0 && argc > ret) { 290 | fprintf(stderr, "Option %s is unknown\n", argv[argc - ret]); 291 | return -1; 292 | } 293 | return ret; 294 | } 295 | -------------------------------------------------------------------------------- /argz.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct argz_path { 4 | char *path; 5 | int dir; 6 | }; 7 | 8 | struct argz_ull { 9 | int base; 10 | int (*suffix)(struct argz_ull *, const char *); 11 | unsigned long long min, max; 12 | unsigned long long value; 13 | }; 14 | 15 | struct argz { 16 | char *name; 17 | char *help; 18 | int (*call)(int, char **, void *); 19 | void *data; 20 | const char *const *alt; 21 | int grp; 22 | int set; 23 | }; 24 | 25 | int argz_help (int, char **); 26 | int argz_help_me (int, char **); 27 | int argz_is_set (struct argz *, const char *); 28 | 29 | void argz_print (struct argz *); 30 | void argz_print_help (const char *, const char *); 31 | 32 | int argz_time_suffix (struct argz_ull *, const char *); 33 | int argz_size_suffix (struct argz_ull *, const char *); 34 | 35 | int argz_path (int, char **, void *); 36 | int argz_ull (int, char **, void *); 37 | int argz_str (int, char **, void *); 38 | int argz (int, char **, void *); 39 | 40 | int argz_main (int, char **, struct argz *); 41 | -------------------------------------------------------------------------------- /comp/argz.bash: -------------------------------------------------------------------------------- 1 | _argz_reply() { 2 | while IFS='' read -r line; do COMPREPLY+=("$line"); done 3 | } 4 | 5 | _argz() { 6 | local last opts 7 | last="${COMP_WORDS[COMP_CWORD]}" 8 | COMP_WORDS[COMP_CWORD]="help" 9 | opts="$("${COMP_WORDS[@]}" 2>/dev/null | awk '!/^#/{print $1}')" 10 | case "$opts" in 11 | '') ;; 12 | CMD) _argz_reply < <(compgen -A command -- "$last") ;; 13 | DIR) _argz_reply < <(compgen -A dir -- "$last") ;; 14 | FILE) _argz_reply < <(compgen -A file -- "$last") ;; 15 | *) _argz_reply < <(compgen -W "$opts" -- "$last") ;; 16 | esac 17 | } 18 | -------------------------------------------------------------------------------- /comp/argz.yash: -------------------------------------------------------------------------------- 1 | function completion/argz { 2 | typeset opts 3 | opts="$($WORDS help 2>/dev/null | awk '!/^#/{print $1}')" 4 | case "$opts" in 5 | '') ;; 6 | CMD) complete -c;; 7 | DIR) complete -d;; 8 | FILE) complete -f;; 9 | *) complete -- $opts 10 | esac 11 | } 12 | -------------------------------------------------------------------------------- /comp/argz.zsh: -------------------------------------------------------------------------------- 1 | _argz() { 2 | local -a opts 3 | opts=("${(@f)$(${words[@]:0:-1} help 2>/dev/null | awk '!/^#/{print $1}')}") 4 | case "${opts[1]}" in 5 | '') ;; 6 | CMD) words=(${(@)words:1}) 7 | CURRENT=1 8 | _normal ;; 9 | DIR) _files -/ ;; 10 | FILE) _files ;; 11 | *) compadd -a opts ;; 12 | esac 13 | } 14 | -------------------------------------------------------------------------------- /readme.c: -------------------------------------------------------------------------------- 1 | #include "argz.c" 2 | 3 | int 4 | readme_percent_suffix(struct argz_ull *ull, const char *s) 5 | { 6 | return s && s[0] && strcmp(s, "%"); 7 | } 8 | 9 | int 10 | main(int argc, char **argv) 11 | { 12 | struct argz_ull size = { 13 | .min = 1, 14 | .suffix = argz_size_suffix, 15 | }; 16 | struct argz_ull time = { 17 | .suffix = argz_time_suffix, 18 | }; 19 | struct argz_ull percent = { 20 | .min = 0, .max = 100, .value = 50, 21 | .suffix = readme_percent_suffix, 22 | }; 23 | struct argz_path file = {0}; 24 | struct argz z[] = { 25 | {"size" , "a size value", argz_ull, &size}, 26 | {"time" , "a time value", argz_ull, &time}, 27 | {"percent", "a custom percent value", argz_ull, &percent}, 28 | {"file", "a file value", argz_path, &file}, 29 | {0}}; 30 | 31 | int err = argz_main(argc, argv, z); 32 | 33 | if (err) 34 | return err; 35 | 36 | printf("size=%llu time=%llu percent=%llu file=%s\n", 37 | size.value, time.value, percent.value, file.path ? file.path : ""); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /readme.sh: -------------------------------------------------------------------------------- 1 | cmd() { 2 | ( echo "$ $*"; "$@" 2>&1) | sed 's/^/ /' 3 | } 4 | 5 | cat > README.md <