Files
args.h/README.md
Krzosa Karol f54ed04e27 first version
2025-10-20 11:34:59 +02:00

5.5 KiB

args.h

Single header command line argument parser for C and C++ loosely based on python's argparse module.

Usage:

Define implementation macro before you include this file in one C or C++ file:

#define ARGS_IMPLEMENTATION

Should look like this:

#include ...
#include ...

#define ARGS_IMPLEMENTATION
#include "args.h"

This will expand the implementation into that file, in other files you can just include the header without the macro.

#include "args.h"

Example:

void example(int argc, char **argv) {
    args_t args = {
        .program = argv[0],
        .description = "this program does very cool things on many files and outputs it somewhere",
        .epilogue = "it also shows cool epilogue text in it's help",
    };
    args_add(&args, (args_desc_t){"filenames",         .nargs = "+"                            });
    args_add(&args, (args_desc_t){"--output",    "-o"                                          });
    args_add(&args, (args_desc_t){"--force",     "-f", .store_value = "1", .default_value = "0"});
    args_add(&args, (args_desc_t){"--recursive", "-r", .store_value = "1", .default_value = "0"});
    args_add(&args, (args_desc_t){"--help",      "-h", .store_value = "1", .default_value = "0"});
    args_parse(&args, argc, argv);

    const char **filenames = args_get(&args, "filenames");
    const char **output = args_get(&args, "--output");
    int force = atoi(args_get(&args, "-f")[0]);
    int recursive = atoi(args_get(&args, "--recursive")[0]);
    int help = atoi(args_get(&args, "--help")[0]);

    if (help) {
        args_print_help(&args);
        return;
    }

    // ..

    for (int i = 0; filenames[i]; i += 1) {
        // ..
    }
}

Execute like this:

app.exe file1 file2 file3 --force --output /folder/.

Help menu:

usage: ./a.out [options] filenames ... 

this program does very cool things on many files and outputs it somewhere

options:
  filenames        <arg> ... list of filenames to process
  --output     -o  <arg>     path to the folder where output will be gathered
  --force      -f            force the processing
  --recursive  -r            apply recursively to files in current directory
  --help       -h            show this help window

it also shows cool epilogue text in it's help

How To's

  1. Single positional argument

program.exe argument

Specifying .nargs = "<digit>" enforces a strict number of arguments, so supplying less or more will result in an error.

args_t args = {0};
args_add(&args, (args_desc_t){"argument", .nargs = "1"});
args_parse(&args, argc, argv);
const char *argument = args_get(&args, "argument")[0];
  1. One or more positional arguments

program.exe a b c

You need to set .nargs = "+" this will make it so that one or more arguments are expected. Alternatively .nargs = "*", which makes it so that having even one argumenti is optional.

args_t args = {0};
args_add(&args, (args_desc_t){"arguments", .nargs = "+"});
args_parse(&args, argc, argv);
const char **arguments = args_get(&args, "arguments");
  1. Help menu, help flag

program.exe --help

args_t args = {0};
args_add(&args, (args_desc_t){"--help", "-h", .store_value = "1", .default_value = "0"});
args_parse(&args, argc, argv);
int help = atoi(args_get(&args, "help")[0]);
if (help) {
    args_print_help(&args);
    exit(0);
}
  1. Typechecked optional list of integers

program.exe --list 1 2 3 program.exe

args_t args = {0};
args_add(&args, (args_desc_t){"--list", "-l", .type = ARGS_TYPE_INT, .nargs = "*"});
args_parse(&args, argc, argv);
const char **list = args_get(&args, "--list");
  1. Custom logging

You can override the ARGS_PRINTF macro, at compile time the library will use that. By default it uses fprintf(stderr)

#define ARGS_PRINTF(...) fprintf(stdout, __VA_ARGS__)
#define ARGS_IMPLEMENTATION
#include "args.h"
  1. User defined memory allocator
#define ARGS_MALLOC(x) my_malloc(x)
#define ARGS_FREE(x) my_free(x)
#define ARGS_IMPLEMENTATION
#include "args.h"
  1. C++

The library works in C++ but leverages special C syntax for aggregate types, in later C++ the syntax might be simiar so no issues there but for older versions you will either need to write some small wrapper functions or just bare with initializing structures inline. For example:

void args_add_bool(args_t *args, const char *name, const char *alt_name, const char *help) {
    args_desc_t desc = {name, alt_name};
    desc.help = help;
    desc.store_value = "1";
    desc.default_value = "0";
    args_add(args, desc);
}

void args_add_nargs(args_t *args, const char *name, const char *alt_name, const char *nargs, const char *help) {
    args_desc_t desc = {name, alt_name};
    desc.nargs = nargs;
    desc.help = help;
    args_add(args, desc);
}

int main(int argc, char **argv) {
    args_t args = {
        .program = argv[0],
        .description = "this program does very cool things on many files and outputs it somewhere",
        .epilogue = "it also shows cool epilogue text in it's help",
    };
    args_add_nargs(&args, "filenames", NULL, "+", "list of filenames to process");
    args_add_nargs(&args, "--output", "-o", "?", "path to the folder where output will be gathered");
    args_add_bool(&args, "--force",     "-f", "force the processing");
    args_add_bool(&args, "--recursive", "-r", "apply recursively to files in current directory");
    args_add_bool(&args, "--help",      "-h", "show this help window");
    args_parse(&args, argc, argv);
}

License

Library is in public domain. See end of code file for license.