Explanation
The reason I post this is that I am worried this code is clever, which is usually a bad sign.
The command-line parser considers the following synopsis.
[--help | -h | -? | /?] | [--version] [<command> [[--help | (...)] | <args>]]
The idea of the parser is to match the following behaviour, which is based on git.
If the help option is set, but no command is given, then the help message is printed. If the help option is set and a command is given, then the command's help message is printed. If the version option is set, and the help option unset, then the version message is printed. If neither option is given, the <command>
is executed.
But there is a catch. If a bad option is found, the rest cannot be parsed, since the option may or may not take an argument (e.g. git log --tree green
). But if until then the help option or the version option were given, the program is executed only considering those options. So --help --cheese --version
would print the help option.
Code
There are two files: the header file (args.h
), and the implementation (args.c
). An example (example.c
) is also given.
args.h
#ifndef ARGS_H
#define ARGS_H
enum args_result {
ARGS_RESULT_SUCCESS,
ARGS_RESULT_BAD_OPTION,
ARGS_RESULT_NO_COMMAND,
ARGS_RESULT_NULL_ARG,
ARGS_RESULT_HELP,
ARGS_RESULT_VERSION,
ARGS_RESULT_HELP_COMMAND
};
enum args_result args_parse(int argc, char **argv, int *r);
#endif /* ARGS_H */
args.c
#include "args.h"
#include <string.h>
#include <stddef.h>
enum arg_type {
ARG_TYPE_NON,
ARG_TYPE_COMMAND,
ARG_TYPE_NULL,
ARG_TYPE_UNKNOWN_OPT
};
static int is_option(const char *str)
{
return str[0] == '-';
}
static int is_help_option(const char *str)
{
return strcmp(str, "--help") == 0 ||
strcmp(str, "-h") == 0 ||
strcmp(str, "-?") == 0 ||
strcmp(str, "/?") == 0;
}
enum args_result args_parse(int argc, char **argv, int *r)
{
int i;
int help = 0, ver = 0;
int argI = 0;
enum arg_type argT = ARG_TYPE_NON;
for (i = 1; i < argc; i++) {
if (argv[i] == NULL) {
argI = i;
argT = ARG_TYPE_NULL;
break;
}
if (is_help_option(argv[i])) {
help = 1;
continue;
}
if (strcmp(argv[i], "--version") == 0) {
ver = 1;
continue;
}
argI = i;
if (is_option(argv[i]))
argT = ARG_TYPE_UNKNOWN_OPT;
else
argT = ARG_TYPE_COMMAND;
break;
}
if (argT == ARG_TYPE_COMMAND && !help) {
/* --help after the <command> */
if (argI + 1 == argc);
else if (argv[argI + 1] == NULL);
else if (is_help_option(argv[argI + 1]))
help = 1;
}
/* --help and --version ignoring the correctness of the syntax. */
if (help) {
if (argT != ARG_TYPE_COMMAND)
return ARGS_RESULT_HELP;
*r = argI;
return ARGS_RESULT_HELP_COMMAND;
}
if (ver && argT != ARG_TYPE_COMMAND)
return ARGS_RESULT_VERSION;
if (argT == ARG_TYPE_NON)
return ARGS_RESULT_NO_COMMAND;
if (argT == ARG_TYPE_NULL) {
*r = argI;
return ARGS_RESULT_NULL_ARG;
}
if (argT == ARG_TYPE_UNKNOWN_OPT) {
*r = argI;
return ARGS_RESULT_BAD_OPTION;
}
/* argT = ARG_TYPE_COMMAND */
*r = argI;
return ARGS_RESULT_SUCCESS;
}
example.c
#include <stdio.h>
#include "args.h"
int main(int argc, char **argv)
{
enum args_result r;
int argI;
r = args_parse(argc, argv, &argI);
if (r == ARGS_RESULT_SUCCESS) {
printf("Command = '%s'\n", argv[argI]);
return 0;
}
if (r == ARGS_RESULT_BAD_OPTION) {
fprintf(stderr, "Fatal: Bad option: '%s'\n", argv[argI]);
return 1;
}
if (r == ARGS_RESULT_NO_COMMAND) {
fprintf(stderr, "Fatal: No command given\n");
return 1;
}
if (r == ARGS_RESULT_NULL_ARG) {
fprintf(stderr, "Fatal: Bad arg: NULL\n");
return 1;
}
if (r == ARGS_RESULT_HELP) {
printf(
"Usage:\n"
" %s --help",
argv[0]);
return 0;
}
if (r == ARGS_RESULT_VERSION) {
printf("0.1\n");
return 0;
}
if (r == ARGS_RESULT_HELP_COMMAND) {
printf("Help command = '%s'\n", argv[argI]);
return 0;
}
return 0;
}
getopt_long()
to parse their arguments. This is a GNU extension of the POSIXgetopt()
function, but it's supported on many operating systems out of the box. Unless you really want to reinvent the wheel, I recommend you use that instead, and then use a drop-in library for Windows if you also need to support that operating system. \$\endgroup\$getopt_long()
myself or takes someone else's implementation. \$\endgroup\$