Last active
August 31, 2019 22:30
-
-
Save lelanthran/aa5d2371af2b0e7b6070617a7ec501cd to your computer and use it in GitHub Desktop.
More comprehensive c/line arguments processing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdbool.h> | |
#include <string.h> | |
/* ******************************************************************** | |
* An even more comprehensive command-line parsing mechanism. This method | |
* allows the program to have long options (--name=value) short options | |
* (-n value or -nvalue or -abcnvalue) mixed with non-options (arguments | |
* without a '-' or '--'. | |
* | |
* The caller will be able to support intuitive arguments when options and | |
* arguments can be mixed freely. For example the following are all | |
* equivalent: | |
* progname --infile=/path/to/infile command-name --outfile=/path/to/outfile | |
* progname command-name --outfile=/path/to/outfile --infile=/path/to/infile | |
* progname --outfile=/path/to/outfile --infile=/path/to/infile command-name | |
* | |
* To support all of the above the parser must be run on the main() argc and | |
* argv parameters, after which all of the arguments, long options and | |
* short options will be stored in arrays provided by the caller. | |
* | |
* The arrays containing the arguments and options are all positional, in that | |
* they will contain the arguments in the order they were processed. | |
* | |
* The arguments array can be searched linearly, stopping when the array | |
* element is NULL. | |
* | |
* The long options array can be searched similarly for the entire | |
* name=value pair, or can be searched with the provided function | |
* find_lopt() which will return only the value part of the string, which | |
* may be an empty string if the user did not provide a value. | |
* | |
* The short options can be searched linearly using the first element of | |
* each string in the array to determine if the option is stored (and | |
* using the rest of the string as the value for that option) or can be | |
* searched using the function find_sopt() which will return the value | |
* part of the option, which may be an empty string if the user did not | |
* provide a value for the option. | |
* | |
* The caller can search for both long options and short options | |
* simultaneously using the find_opt() function which will return the long | |
* option for the specified long option name if it exists, or the short | |
* option for the specified short option name if it exists. | |
* | |
* | |
*/ | |
static void string_array_free (char **sarr) | |
{ | |
for (size_t i=0; sarr && sarr[i]; i++) { | |
free (sarr[i]); | |
} | |
free (sarr); | |
} | |
static char **string_array_append (char ***dst, const char *s1, const char *s2) | |
{ | |
bool error = true; | |
size_t nstr = 0; | |
for (size_t i=0; (*dst) && (*dst)[i]; i++) | |
nstr++; | |
char **tmp = realloc (*dst, (sizeof **dst) * (nstr + 2)); | |
if (!tmp) | |
goto errorexit; | |
*dst = tmp; | |
(*dst)[nstr] = NULL; | |
(*dst)[nstr+1] = NULL; | |
char *scopy = malloc (strlen (s1) + strlen (s2) + 1); | |
if (!scopy) | |
goto errorexit; | |
strcpy (scopy, s1); | |
strcat (scopy, s2); | |
(*dst)[nstr] = scopy; | |
error = false; | |
errorexit: | |
if (error) { | |
return NULL; | |
} | |
return *dst; | |
} | |
static const char *find_lopt (char **lopts, const char *name) | |
{ | |
if (!lopts || !name) | |
return NULL; | |
size_t namelen = strlen (name); | |
for (size_t i=0; lopts[i]; i++) { | |
size_t maxlen = strlen (lopts[i]); | |
if (maxlen < namelen) | |
continue; | |
if ((strncmp (name, lopts[i], namelen))==0) { | |
char *ret = strchr (lopts[i], '='); | |
return ret ? &ret[1] : ""; | |
} | |
} | |
return NULL; | |
} | |
static const char *find_sopt (const char **sopts, char opt) | |
{ | |
if (!sopts || !opt) | |
return NULL; | |
size_t index = 0; | |
for (size_t i=0; sopts[i]; i++) | |
index++; | |
for (size_t i=index; i>0; i--) { | |
for (size_t j=0; sopts[i-1][j]; j++) { | |
if (sopts[i-1][j]==opt) | |
return &sopts[i-1][j+1]; | |
} | |
} | |
return NULL; | |
} | |
static const char *find_opt (const char **lopts, const char *lname, | |
const char **sopts, char sname) | |
{ | |
const char *ret = NULL; | |
if (lopts && lname) | |
ret = find_lopt (lopts, lname); | |
if (!ret && sopts && sname) | |
ret = find_sopt (sopts, sname); | |
return ret; | |
} | |
static size_t process_args (int argc, char **argv, | |
char ***args, char ***lopts, char ***sopts) | |
{ | |
int error = true; | |
char **largs = NULL, | |
**llopts = NULL, | |
**lsopts = NULL; | |
size_t ret = 0; | |
free (*args); | |
free (*lopts); | |
free (*sopts); | |
*args = NULL; | |
*lopts = NULL; | |
*sopts = NULL; | |
for (int i=1; i<argc && argv[i]; i++) { | |
if ((strncmp (argv[i], "--", 2))==0) { | |
// Store in lopt | |
if (!(string_array_append (&llopts, &argv[i][2], ""))) | |
goto errorexit; | |
continue; | |
} | |
if (argv[i][0]=='-') { | |
for (size_t j=1; argv[i][j]; j++) { | |
if (argv[i][j+1]) { | |
if (!(string_array_append (&lsopts, &argv[i][j], ""))) | |
goto errorexit; | |
} else { | |
char *value = &argv[i+1][0]; | |
if (!value || value[0]==0 || value[0]=='-') | |
value = ""; | |
if (!(string_array_append (&lsopts, &argv[i][j], value))) | |
goto errorexit; | |
if (argv[i+1] && argv[i+1][0]!='-') | |
i++; | |
break; | |
} | |
} | |
continue; | |
} | |
// Default - store in largs | |
if (!(string_array_append (&largs, argv[i], ""))) | |
goto errorexit; | |
} | |
*args = largs; | |
*lopts = llopts; | |
*sopts = lsopts; | |
error = false; | |
errorexit: | |
if (error) { | |
ret = (size_t)-1; | |
string_array_free (largs); | |
string_array_free (llopts); | |
string_array_free (lsopts); | |
} | |
return ret; | |
} | |
/* *********************************************************** | |
* Test program. Execute with: | |
* cline_test --long-one=long-one \ | |
* -abcd foo \ | |
* -fghbar \ | |
* --long-two= \ | |
* --long-three \ | |
* --long-five \ | |
* -- all the normal args go here -e | |
*/ | |
int main (int argc, char **argv) | |
{ | |
int ret = EXIT_FAILURE; | |
static const struct { | |
const char *l; | |
char s; | |
const char *result; | |
} testcases[] = { | |
{ "long-one", 0, "long-one" }, // --long-one=long-one | |
{ "long-two", 0, "" }, // --long-two= | |
{ "long-three", 0, "" }, // --long-three | |
{ "long-four", 0, NULL }, // --long-five | |
{ NULL, 'a', "bcd" }, // -abcd foo | |
{ NULL, 'b', "cd" }, // -abcd foo | |
{ NULL, 'c', "d" }, // -abcd foo | |
{ NULL, 'd', "foo" }, // -abcd foo | |
{ NULL, 'e', NULL }, // -abcd foo | |
{ NULL, 'h', "bar" }, // -fghbar | |
}; | |
char **args = NULL; | |
char **lopts = NULL; | |
char **sopts = NULL; | |
size_t nopts = process_args (argc, argv, &args, &lopts, &sopts); | |
if (nopts == (size_t)-1) { | |
fprintf (stderr, "Failed to process all the options [%zu processed]\n", | |
nopts); | |
goto errorexit; | |
} | |
for (size_t i=0; args && args[i]; i++) { | |
printf ("arg [%s]\n", args[i]); | |
} | |
for (size_t i=0; lopts && lopts[i]; i++) { | |
printf ("lopt [%s]\n", lopts[i]); | |
} | |
for (size_t i=0; sopts && sopts[i]; i++) { | |
printf ("sopt [%s]\n", sopts[i]); | |
} | |
printf ("lopt long-one [%s]\n", find_opt (lopts, "long-one", sopts, 'l')); | |
printf ("sopt d [%s]\n", find_opt (lopts, "lng-ne", sopts, 'd')); | |
ret = EXIT_SUCCESS; | |
errorexit: | |
string_array_free (args); | |
string_array_free (lopts); | |
string_array_free (sopts); | |
return ret; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment