Skip to content

Instantly share code, notes, and snippets.

@lelanthran
Last active August 31, 2019 22:30
Show Gist options
  • Save lelanthran/aa5d2371af2b0e7b6070617a7ec501cd to your computer and use it in GitHub Desktop.
Save lelanthran/aa5d2371af2b0e7b6070617a7ec501cd to your computer and use it in GitHub Desktop.
More comprehensive c/line arguments processing
#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