Skip to content

Command Line Flag Parsing in C/C++, inspired by Go's `flag` package

License

Notifications You must be signed in to change notification settings

WhoBrokeTheBuild/cflags

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cflags

Command line flag parsing library in C

Heavily inspired by Go's flag package https://golang.org/pkg/flag/

Building

cflags is a header only library. To use it, simply copy cflags.h or cflags.hpp to your project, or add it to your include path.

You may also install it using cmake, like so:

cmake path/to/source
sudo make install

This will install both CMake and pkg-config configuration files.

Argument Parsing Logic

  • The first argument is stored in program
  • The following arguments are parsed left to right
  • If an argument does not start with -, it is placed in the additional arguments list stored in args/argv
  • If the special -- argument appears, all following arguments are treated as positional
    e.g. -c 4 -- --name hello would parse the -c, but place --name and hello into args/argv
  • Arguments starting with -- are long name flags, e.g. --example
    • The list of flags is searched for one with long_name equal to the argument name (after the --), e.g. long_name == example
    • If a flag is not found with that name, an error is printed and parse() returns false
  • Arguments starting with just - are short name flags, e.g. -xvf
    • These can be grouped together, so they are searched one at a time from left to right, e.g. x, v, then f
    • If any of these fail to match a flag, an error is printed and parse() returns false
  • Once a flag is found, it attempts to find a value
    • Arguments with long names can also come in the forms --name, --name=value, or --name value
    • Arguemnts with short names can come in the forms -n, or -n value
      • Note: Only the last short flag of a group can have a value, e.g. -xvf file will work, but -xfv file will fail
    • If the flag is of type [c]string, int, or float then a value is required, and if one is not found an error is printed and parse() returns false
      • Arguments of type bool can have a value, e.g. --debug=false, but one is not required
    • Each time a flag is encountered, the count member is incremented
    • The value for a flag is overwritten each time the flag is processed, the last argument parsed wins, e.g. -c 4 -c 10 will result in -c being 10
      • If you want to capture each argument separately, use add_*_callback instead

Usage (C)

#include <cflags.h>

void process_string(const char * str)
{
    printf("processing %s\n", str);
}

void process_bool(bool b)
{
    printf("processing %d\n", b);
}

void process_int(int i)
{
    printf("processing %d\n", i);
}

void process_float(float f)
{
    printf("processing %f\n", f);
}

int main(int argc, char** argv)
{
    // Create a cflags object
    cflags_t * flags = cflags_init();

    // Add a bool flag, which will be callable with -d or --debug
    // The value will be true if it exists, and can bet set to false
    // by saying -d false or --debug=false
    bool debug = false;
    cflags_add_bool(flags, 'd', "debug", &debug, "enable debug mode");

    // Add a similar help flag, which will be callable with just --help
    bool help = false;
    cflags_add_bool(flags, '\0', "help", &help, "print this text and exit");

    // Add a string flag
    const char * string = NULL;
    cflags_add_string(flags, 's', "string", &string, "enter a string");

    // Add an int flag
    int count = 0;
    cflags_add_int(flags, 'c', "count", &count, "enter a number");

    // Add a float flag
    float amount = 0.f;
    cflags_add_float(flags, 'a', "amount", &amount, "enter a float");

    // Add a string callback flag. This will call the supplied function with the value
    // when it is parsed
    cflags_add_string_callback(flags, 'f', "file", &process_string, "process a file");

    cflags_add_bool_callback(flags, 'q', "bool-flag", &process_bool, "process a bool");

    cflags_add_int_callback(flags, 'w', "int-flag", &process_int, "process a int");

    cflags_add_float_callback(flags, 'e', "float-flag", &process_float, "process a float");

    // Add a flag that can be called multiple times
    cflags_flag_t * verbose = cflags_add_bool(flags, 'v', "verbose", NULL, "enables verbose output, repeat up to 4 times for more verbosity");

    // Parse the command arguments
    if (!cflags_parse(flags, argc, argv) || help || flags->argc == 0) {
        cflags_print_usage(flags,
            "[OPTION]... [ARG]...", 
            "Tests the cflags library.", 
            "Additional information about this library can be found by at:\n"
            "  https://github.com/WhoBrokeTheBuild/cflags");
    }

    printf("debug: %d\n", debug);
    printf("string: %s\n", string);
    printf("count: %d\n", count);
    printf("amount: %f\n", amount);

    // Print the number of times verbose was added
    printf("verbosity: %d\n", verbose->count);

    // Print any additional arguments, in the order they were parsed
    for (int i = 1; i < flags->argc; ++i) {
        printf("positional arg %d: %s\n", i, flags->argv[i]);
    }

    // Cleanup
    cflags_free(flags);

    return 0;
}

Usage (C++)

#include <cflags.hpp>

void process_string(std::string str)
{
    printf("processing %s\n", str.c_str());
}

void process_cstring(const char * str)
{
    printf("processing %s\n", str);
}

void process_bool(bool b)
{
    printf("processing %d\n", b);
}

void process_int(int i)
{
    printf("processing %d\n", i);
}

void process_float(float f)
{
    printf("processing %f\n", f);
}

int main(int argc, char * argv[])
{
    // Create a cflags object
    cflags::cflags flags;

    // Add a bool flag, which will be callable with -d or --debug
    // The value will be true if it exists, and can bet set to false
    // by saying -d false or --debug=false
    bool debug = false;
    flags.add_bool('d', "debug", &debug, "enable debug mode");

    // Add a similar help flag, which will be callable with just --help
    bool help = false;
    flags.add_bool('\0', "help", &help, "print this text and exit");

    // Add a string flag
    std::string string;
    flags.add_string('s', "string", &string, "enter a string");

    // Add a cstring flag
    const char * cstring = NULL;
    flags.add_cstring('\0', "cstring", &cstring, "enter a string (cstring)");

    // Add an int flag
    int count = 0;
    flags.add_int('c', "count", &count, "enter a number");

    // Add a float flag
    float amount = 0.f;
    flags.add_float('a', "amount", &amount, "enter a float");

    // Add a string callback flag. This will call the supplied function with the value
    // when it is parsed
    flags.add_string_callback('f', "file", &process_string, "process a file");

    flags.add_cstring_callback('\0', "cfile", &process_cstring, "process a file (cstring)");

    flags.add_bool_callback('q', "bool-flag", &process_bool, "process a bool");

    flags.add_int_callback('w', "int-flag", &process_int, "process a int");

    flags.add_float_callback('e', "float-flag", &process_float, "process a float");

    // You can also use lambdas
    flags.add_string_callback('l', "lambda",
        [](std::string value) {
            printf("Hello %s\n", value.c_str());
        },
        "use a lambda function"
    );

    // Add a flag that can be called multiple times
    auto verbose = flags.add_bool('v', "verbose", NULL, "enables verbose output, repeat up to 4 times for more verbosity");

    // Parse the command arguments
    if (!flags.parse(argc, argv) || help || flags.argc == 0) {
        flags.print_usage(
            "[OPTION]... [ARG]...", 
            "Tests the cflags library.", 
            "Additional information about this library can be found by at:\n"
            "  https://github.com/WhoBrokeTheBuild/cflags");
    }

    printf("debug: %d\n", debug);
    printf("string: %s\n", string.c_str());
    printf("cstring: %s\n", cstring);
    printf("count: %d\n", count);
    printf("amount: %f\n", amount);

    // Print the number of times verbose was added
    printf("verbosity: %d\n", verbose->count);

    // Print any additional arguments, in the order they were parsed
    for (auto& arg : flags.args) {
        printf("positional arg %s\n", arg.data());
    }

    // For backwards compatability, the additional arguments are also exposed in argc/argv
    for (int i = 0; i < flags.argc; ++i) {
        printf("positional arg %d: %s\n", i, flags.argv[i]);
    }

    return 0;
}

Quirks

1. Only the last short-name argument in a group may have a value.

For example:

flags.add_string('f', "file", parse_filename, "parse a filename");
flags.add_bool('d', "debug", &debug, "enable debug mode");

// These will work
test -df test.txt
test -df test.txt -f test2.txt

// And these will fail
test -fd test.txt
test -ff test.txt test2.txt

2. Any char * strings point to the original memory from argv

Call the following code as program -s hello

int main(int argc, char ** argv)
{
    for (int i = 1; i < argc; ++i) {
        printf("argv[%d] @ %p: %s\n", i, argv[i], argv[i]);
    }

    cflags_t * flags = cflags_init();

    const char * string = NULL;
    cflags_add_string(flags, 's', "string", &string, "enter a string");

    cflags_parse(flags, argc, argv);

    // The address pointed to by `string` is the same memory pointed to by argv
    printf("string @ %p: %s\n", string, string);
    
    cflags_free(flags);

    return 0;
}

Example output

argv[1] @ 0000024F6BDD665F: -s
argv[2] @ 0000024F6BDD6662: hello
string @ 0000024F6BDD6662: hello

When parsing arguments in the form --name=value, the memory pointed to by argv is altered and the = is replaced by a \0.

When using the C++ version, arguments as std::string do not point at argv as their memory gets copied.

About

Command Line Flag Parsing in C/C++, inspired by Go's `flag` package

Resources

License

Stars

Watchers

Forks

Packages

No packages published