libclap 1.0.0
Command Line Argument Parser for C
Loading...
Searching...
No Matches
libclap

Command Line Argument Parser for C

CI codecov [License](LICENSE) Standard [Platform]() Docs

libclap is a modern, secure, and extensible command-line argument parsing library for C, inspired by Python's argparse. It provides a familiar, feature-rich API while maintaining C99 compatibility, zero external dependencies, and a strong focus on memory safety.

✨ Features

  • Full argparse Compatibility – Positional arguments, optional flags, subcommands, mutually exclusive groups, nargs (*, +, ?, N, REMAINDER), and more.
  • Rich Action Systemstore, store_true/false, store_const, append, append_const, count, help, version, and custom actions.
  • Type Conversion – Built-in support for int, float, string, and bool, plus user‑defined converters.
  • Automatic Help Generation – Beautifully formatted usage and help messages with smart line‑wrapping and alignment.
  • Subcommands – Full support for git‑style sub‑parsers with flat namespace merging.
  • Memory Safe – Bounds‑checked string buffers, input validation, and comprehensive leak testing (Valgrind/ASan clean).
  • Cross‑Platform – Windows, Linux, macOS, and BSD. Works with GCC, Clang, and MSVC.
  • Zero Dependencies – Only requires a C99 standard library.
  • CMake Integration – Easy to build and consume with find_package(clap) or pkg-config.

📦 Installation

From Source

git clone https://github.com/wdl0214/libclap.git
cd libclap
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local
make
sudo make install

CMake Options

Option Default Description
CLAP_BUILD_SHARED ON Build shared library
CLAP_BUILD_STATIC ON Build static library
CLAP_BUILD_TESTS ON Build unit tests
CLAP_BUILD_EXAMPLES ON Build example programs
CLAP_BUILD_FUZZ OFF Build fuzz test (Clang only)
CLAP_ENABLE_ASAN OFF Enable AddressSanitizer
CLAP_ENABLE_UBSAN OFF Enable UndefinedBehaviorSanitizer
CLAP_ENABLE_COVERAGE OFF Enable code coverage

Using in Your Project

⚠️ Static linking: If you link libclap statically, you must define CLAP_STATIC at compile time (e.g. -DCLAP_STATIC or #define CLAP_STATIC). This is required on all platforms, but most important on Windows where the absence of CLAP_STATIC causes the compiler to emit __declspec(dllimport) decorations that are incompatible with a static library.

When linking against the shared library, no additional defines are needed.

CMake <tt>find_package</tt>

find_package(clap REQUIRED)
add_executable(myapp myapp.c)
target_link_libraries(myapp PRIVATE clap::clap_static)
target_compile_definitions(myapp PRIVATE CLAP_STATIC)

Use clap::clap_shared instead if you prefer linking against the shared library (no CLAP_STATIC needed).

pkg-config

# Dynamic linking (default)
gcc myapp.c -o myapp $(pkg-config --cflags --libs libclap)
# Static linking — requires -DCLAP_STATIC
gcc myapp.c -o myapp $(pkg-config --cflags --static --libs libclap) -DCLAP_STATIC

Manual Linking

# Dynamic linking
gcc myapp.c -o myapp -I/usr/local/include -L/usr/local/lib -lclap
# Static linking — requires -DCLAP_STATIC
gcc myapp.c -o myapp -I/usr/local/include /usr/local/lib/libclap.a -DCLAP_STATIC

🚀 Quick Start

#include <clap/clap.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
clap_error_t error = {0};
clap_namespace_t *ns = NULL;
clap_parser_t *parser = clap_parser_new("my_program", "A simple example", NULL);
arg = clap_add_argument(parser, "input");
clap_argument_help(arg, "Input file");
clap_argument_type(arg, "string");
arg = clap_add_argument(parser, "--output/-o");
clap_argument_help(arg, "Output file");
clap_argument_type(arg, "string");
arg = clap_add_argument(parser, "--verbose/-v");
clap_argument_help(arg, "Increase verbosity");
clap_parse_result_t result = clap_parse_args(parser, argc, argv, &ns, &error);
if (result == CLAP_PARSE_ERROR) {
clap_print_help_on_error(parser, &error, stderr);
return 1;
}
if (result == CLAP_PARSE_HELP || result == CLAP_PARSE_VERSION) {
return 0;
}
const char *input, *output;
int verbose = 0;
clap_namespace_get_string(ns, "input", &input);
clap_namespace_get_string(ns, "output", &output);
clap_namespace_get_int(ns, "verbose", &verbose);
printf("Input: %s, Output: %s, Verbose: %d\n", input, output, verbose);
return 0;
}
Main public API for libclap.
CLAP_EXPORT void clap_parser_free(clap_parser_t *parser)
Destroy a parser and all resources it owns.
CLAP_EXPORT clap_parse_result_t clap_parse_args(clap_parser_t *parser, int argc, char *argv[], clap_namespace_t **out_namespace, clap_error_t *error)
Parse command-line arguments.
CLAP_EXPORT clap_argument_t * clap_argument_default(clap_argument_t *arg, const char *default_value)
Set a default value for an argument.
CLAP_EXPORT void clap_print_help_on_error(clap_parser_t *parser, const clap_error_t *error, FILE *stream)
Print "<prog>: error: <message>" and contextual help.
CLAP_EXPORT clap_argument_t * clap_argument_type(clap_argument_t *arg, const char *type_name)
Set the type name for an argument.
CLAP_EXPORT clap_argument_t * clap_argument_help(clap_argument_t *arg, const char *help_text)
Set the help text for an argument.
CLAP_EXPORT void clap_namespace_free(clap_namespace_t *ns)
Free a namespace and all values it contains. NULL-safe.
CLAP_EXPORT bool clap_namespace_get_string(clap_namespace_t *ns, const char *name, const char **value)
Retrieve a string value from the namespace.
CLAP_EXPORT clap_argument_t * clap_argument_required(clap_argument_t *arg, bool required)
Mark an optional argument as required or not.
CLAP_EXPORT bool clap_namespace_get_int(clap_namespace_t *ns, const char *name, int *value)
Retrieve an int value from the namespace.
CLAP_EXPORT clap_argument_t * clap_argument_action(clap_argument_t *arg, clap_action_t action)
Set the action type for an argument.
CLAP_EXPORT clap_argument_t * clap_add_argument(clap_parser_t *parser, const char *name_or_flags)
Register a new argument (positional or optional).
CLAP_EXPORT clap_parser_t * clap_parser_new(const char *prog_name, const char *description, const char *epilog)
Create a new argument parser.
@ CLAP_ACTION_COUNT
Definition clap_action.h:23
clap_parse_result_t
Parse result codes returned by clap_parse_args()
Definition clap_types.h:60
@ CLAP_PARSE_HELP
Definition clap_types.h:63
@ CLAP_PARSE_ERROR
Definition clap_types.h:62
@ CLAP_PARSE_VERSION
Definition clap_types.h:64
Argument definition (positional or optional).
Error information structure.
Definition clap_error.h:36
Container for parsed argument values.
Parsing engine state and configuration.

Compile and run:

$ gcc -o myapp myapp.c -lclap
$ ./myapp --help
Usage: my_program [-h] [--output OUTPUT] [--verbose] input
A simple example
Positional arguments:
input Input file
Optional arguments:
--help, -h Show this help message and exit
--output, -o OUTPUT Output file (default: -)
--verbose, -v Increase verbosity

📚 API Overview

Parser Lifecycle

clap_parser_t *parser = clap_parser_new("prog", "Description", "Epilog");
clap_parser_set_version(parser, "1.0.0");
clap_namespace_t *ns = NULL;
clap_error_t error = {0};
clap_parse_result_t result = clap_parse_args(parser, argc, argv, &ns, &error);
if (result == CLAP_PARSE_ERROR) {
clap_print_help_on_error(parser, &error, stderr);
return 1;
}
if (result == CLAP_PARSE_HELP || result == CLAP_PARSE_VERSION) {
return 0;
}
CLAP_EXPORT void clap_parser_set_help_width(clap_parser_t *parser, int width)
Set help output width (default: 100).
CLAP_EXPORT void clap_parser_set_version(clap_parser_t *parser, const char *version)
Set the version string printed by clap_print_version().

Adding Arguments

clap_argument_t *arg = clap_add_argument(parser, "--output/-o");
clap_argument_help(arg, "Output file path");
clap_argument_type(arg, "string");
clap_argument_metavar(arg, "FILE");
CLAP_EXPORT clap_argument_t * clap_argument_metavar(clap_argument_t *arg, const char *metavar)
Override the metavar shown in help/usage for this argument.

Actions

Action Description
CLAP_ACTION_STORE Store the value (default)
CLAP_ACTION_STORE_TRUE Store true when flag is present
CLAP_ACTION_STORE_FALSE Store false when flag is present
CLAP_ACTION_STORE_CONST Store a constant value
CLAP_ACTION_APPEND Append value to a list
CLAP_ACTION_APPEND_CONST Append constant to a list
CLAP_ACTION_COUNT Count occurrences of the option
CLAP_ACTION_HELP Print help and exit
CLAP_ACTION_VERSION Print version and exit
CLAP_ACTION_CUSTOM User‑defined handler

nargs Specifiers

Specifier Description
1 (default) Exactly one argument
? (CLAP_NARGS_ZERO_OR_ONE) Zero or one argument
* (CLAP_NARGS_ZERO_OR_MORE) Zero or more arguments
+ (CLAP_NARGS_ONE_OR_MORE) One or more arguments
N Exactly N arguments
REMAINDER Consume all remaining arguments

Subcommands

clap_parser_t *subparsers = clap_add_subparsers(parser, "command", "Available commands");
clap_parser_t *commit = clap_subparser_add(subparsers, "commit", "Record changes");
clap_argument_t *msg_arg = clap_add_argument(commit, "-m");
clap_argument_dest(msg_arg, "message");
clap_argument_required(msg_arg, true);
// Parse as usual
const char *cmd;
clap_namespace_get_string(ns, "command", &cmd);
if (strcmp(cmd, "commit") == 0) {
const char *msg;
clap_namespace_get_string(ns, "message", &msg);
}
CLAP_EXPORT clap_parser_t * clap_subparser_add(clap_parser_t *subparsers, const char *name, const char *help_text)
Register a subcommand.
CLAP_EXPORT clap_argument_t * clap_argument_dest(clap_argument_t *arg, const char *dest)
Override the destination key (dest) in the namespace.
CLAP_EXPORT clap_parser_t * clap_add_subparsers(clap_parser_t *parser, const char *dest, const char *help_text)
Enable subcommand support.

Mutually Exclusive Groups

int group = clap_add_mutually_exclusive_group(parser, false);
clap_argument_t *verbose = clap_add_argument(parser, "--verbose");
clap_mutex_group_add_argument(parser, group, verbose);
clap_argument_t *quiet = clap_add_argument(parser, "--quiet");
clap_mutex_group_add_argument(parser, group, quiet);
CLAP_EXPORT int clap_add_mutually_exclusive_group(clap_parser_t *parser, bool required)
Create a mutually exclusive group.
CLAP_EXPORT bool clap_mutex_group_add_argument(clap_parser_t *parser, int mutex_group_id, clap_argument_t *arg)
Add an argument to a mutually exclusive group.
@ CLAP_ACTION_STORE_TRUE
Definition clap_action.h:19

Argument Groups

Organize related arguments into named sections in the help output:

int net_group = clap_add_argument_group(parser, "Network", "Connection options");
clap_argument_t *host = clap_add_argument(parser, "--host");
clap_argument_help(host, "Hostname to connect to");
clap_argument_group_add_argument(parser, net_group, host);
clap_argument_t *port = clap_add_argument(parser, "--port");
clap_argument_type(port, "int");
clap_argument_help(port, "Port number");
clap_argument_group_add_argument(parser, net_group, port);
CLAP_EXPORT int clap_add_argument_group(clap_parser_t *parser, const char *title, const char *description)
Create a display group to organize arguments in help output.
CLAP_EXPORT bool clap_argument_group_add_argument(clap_parser_t *parser, int display_group_id, clap_argument_t *arg)
Add an argument to a display group.

Help output:

Usage: prog [-h] [--host HOST] [--port PORT]
Optional arguments:
-h, --help Show this help message and exit
Network:
Connection options
--host HOST Hostname to connect to
--port PORT Port number

Arguments in a display group are excluded from the default "Optional arguments" and "Positional arguments" sections, appearing only in their group section.

Custom Memory Allocator

void* my_malloc(size_t size) { return pool_alloc(size); }
void my_free(void *ptr) { pool_free(ptr); }
int main() {
clap_set_allocator(my_malloc, my_free, NULL);
// ... use libclap ...
}
CLAP_EXPORT void clap_set_allocator(void *(*malloc_fn)(size_t), void(*free_fn)(void *), void *(*realloc_fn)(void *, size_t))
Set custom memory allocator functions.

Frequently Asked Questions

How do I handle CLAP_PARSE_HELP / CLAP_PARSE_VERSION?

These are not errors. The library has already printed the help/version text to stdout. The caller should clean up and exit successfully:

if (result == CLAP_PARSE_HELP || result == CLAP_PARSE_VERSION) {
return 0;
}

Why does my custom type handler return "Unknown type"?

You registered a type name via clap_argument_type(arg, "ip_addr") but forgot to register the converter with clap_register_type(parser, "ip_addr", my_handler, sizeof(my_ip_t)). The type handler must be registered on the parser before parsing begins.

What is CLAP_STATIC and when do I need it?

When linking libclap statically (e.g. libclap.a on Unix or clap.lib on Windows), you must define CLAP_STATIC at compile time (-DCLAP_STATIC or #define CLAP_STATIC). This is required because libclap's headers use __declspec(dllimport) on Windows by default, which is incompatible with a static library. Shared library users do not need this define.

My –verbose/-v flag doesn't consume a value, but the next argument is skipped

This is expected. Flags using CLAP_ACTION_STORE_TRUE, STORE_FALSE, STORE_CONST, APPEND_CONST, or COUNT do not consume a value. If you need a flag that takes a value, use CLAP_ACTION_STORE (the default) with clap_argument_type().

Why is my subcommand's help not showing the right name?

Subcommand names are extracted from the parser's prog_name. When creating a subparser with clap_subparser_add(subparsers, "commit", ...), the subparser's prog_name is set to "parent_prog commit". The display logic extracts the portion after the last space, so names with spaces (e.g. "myprog nested sub") will only show "sub".

How do I make a positional argument optional?

Use ‘clap_argument_nargs(arg, ’?')(zero or one),'*'(zero or more), or setCLAP_NARGS_REMAINDER`. By default, positional arguments are required.

Testing

libclap includes a comprehensive test suite built with Unity.

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make
ctest # Run all tests
ctest -R usage # Run only usage compliance tests
ctest --output-on-failure # Show output for failed tests
./tests/test_parser # Run a specific test

Memory safety is verified with deterministic runtime checks and sanitizers:

valgrind --leak-check=full ./tests/test_usage
cmake .. -DCLAP_ENABLE_ASAN=ON && make && ctest

The Valgrind command above runs the test_usage executable under a leak checker. It is most useful on Linux; for regular development and CI, ASan is usually faster and easier to integrate.

Code coverage requires gcovr to be installed:

pip install gcovr
cmake .. -DCLAP_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
make && ctest
make coverage

Fuzz testing is available with Clang builds:

mkdir build && cd build
cmake .. -DCLAP_BUILD_FUZZ=ON -DCMAKE_C_COMPILER=clang
make fuzz_clap
./tests/fuzz_clap -max_len=4096 -runs=100000 corpus/

Unlike the fixed test suite, fuzzing continuously generates and mutates inputs to find crashes, unexpected exits, leaks, and other parser edge cases.

📖 Examples

Example Description
examples/basic.c Minimal argument parsing
examples/git_style.c Full git‑style CLI with subcommands
examples/action_demo.c Demonstrates all 10 action types
examples/custom_type.c Demonstrates custom type validation and typed storage with CLAP_ACTION_CUSTOM

🏗️ Project Structure

libclap/
├── CMakeLists.txt
├── cmake/ # CMake modules
├── include/clap/ # Public headers
├── src/ # Library source
├── tests/ # Unit and integration tests
│ ├── unit/ # Per-module unit tests
│ ├── integration/ # End-to-end tests
│ └── fuzz/ # Fuzz testing
├── examples/ # Example programs
└── scripts/ # Test and utility scripts

🤝 Contributing

Contributions are welcome! Please open an issue or pull request on GitHub. Ensure that:

  • Code follows the existing C99 style
  • All tests pass (ctest)
  • No new compiler warnings are introduced
  • Valgrind/ASan reports no leaks
  • Parser and tokenizer changes are validated with fuzzing when possible

Changelog

See CHANGELOG.md for version history and release notes.

License

libclap is released under the MIT License. See [LICENSE](LICENSE) for details.


libclapargparse for C, without compromise.