Optly is a single-header command line argument parser for C (C99).
It is designed to be small, dependency‑free, and allocation‑free, making it ideal for small CLI tools and embedded-style programs.
- Single-header library
- No dynamic memory allocation
- Portable C99
- Commands and nested subcommands
- Command-specific flags
- Long flags (
--verbose) - Short flags (
-v) - Batched short flags (
-abc) - Inline flag values (
--threads=4) - Separate flag values (
--threads 4) - Typed flag values
- Positional arguments
- Optional and required flags
- Optional automatic generation and handling of
--help/-hand--version/-vflags - Optional automatic generation and handling of
help/help cmdandversioncommands
Just drop optly.h into your project.
In one C file:
#define OPTLY_IMPLEMENTATION
#include "optly.h"In other files:
#include "optly.h"#define OPTLY_IMPLEMENTATION
#include "optly.h"
#include <stdio.h>
int main(int argc, char **argv)
{
OptlyCommand cmd = {
.name = "app",
.flags = optly_flags(
optly_flag_bool("verbose", 'v', "Enable verbose output", .value.as_bool = false),
optly_flag_uint32("threads", 't', "Worker threads", .value.as_uint32 = 4)
),
.commands = optly_commands(
optly_command("run", "Run server",
.flags = optly_flags(
optly_flag_uint16("port", 'p', "Server port", .value.as_uint16 = 8080)
)
)
)
};
optly_parse_args(argc, argv, &cmd);
printf("threads: %u\n", optly_flag_value_uint32(&cmd, "threads"));
if (cmd.next_command) {
printf("command: %s\n", cmd.next_command->name);
printf("port: %u\n", optly_flag_value_uint16(cmd.next_command, "port"));
}
}Run:
./app --threads 8 run --port 9000optly_parse_args() collects every problem it finds instead of stopping at the
first one, and returns them:
OptlyErrors errs = optly_parse_args(argc, argv, &cmd);
for (size_t i = 0; i < optly_errors_count(&errs); i++) {
OptlyError e = optly_errors_at(&errs, i);
printf("%s%s%s\n", optly_error_message(e.kind), e.arg ? ": " : "", e.arg ? e.arg : "");
}optly_error_print(&errs) does the same thing if the default wording is fine.
By default optly calls exit() when parsing fails. A CLI parser runs once,
at startup, and bad arguments mean the program should not continue. To take
that decision yourself -- in tests, or when you want to print your own message
-- define:
#define OPTLY_NO_EXITOptly also logs each error as it happens. Out of the box that goes to stderr
via fprintf. If logcie is included
before optly, its module logging is used instead, so your application decides
where optly's output goes. To route it somewhere else entirely, define
OPTLY_LOG before including optly:
#define OPTLY_LOG(level, ...) my_logger(#level, __VA_ARGS__)Supported forms:
--verbose
-v
--threads 4
--threads=4
-t 4
Short flags can be batched:
-abc
Equivalent to:
-a -b -c
The last flag in a batch may take a value, the way tar does it:
tar -xzvf archive.tar
Everything before the last one must be boolean -- a flag in the middle has no way to say where its value ends.
Commands are positional tokens:
app run
app build
app run check
Each command may define its own:
- flags
- subcommands
- positional arguments
Declare them on the command that accepts them:
.positionals = optly_positionals(
optly_positional("files", "Files to build", .min = 1, .max = 0)
)min is how many are required, max how many are allowed; max = 0 means any
number. Everything after a bare -- is treated as positional, even if it looks
like a flag:
app build file1 file2
app build -- --not-a-flag
Access them via:
OptlyPositional *p = optly_get_positional(&cmd, "files");
for (size_t i = 0; i < p->count; i++) {
printf("%s\n", p->values[i]);
}optly_usage(&cmd); // usage for the whole program
optly_usage(cmd.next_command); // usage for the selected commandYou can define
#define OPTLY_GEN_HELP_FLAG
#define OPTLY_GEN_HELP_COMMANDto generate help flag --help | -h and/or help command help cmd, or
#define OPTLY_GEN_VERSION_FLAG
#define OPTLY_GEN_VERSION_COMMANDto generate version flag --version | -v and/or version command version.
If help/version command/flag would be found during parsing usage would be
automatically called and exit(0) is called.
Note that user defined flags with -h/-v would interfere with generated flags.
Their short forms can be moved with OPTLY_HELP_SHORT_FLAG and
OPTLY_VERSION_SHORT_FLAG.
OPTLY_GEN_VERSION_FLAG and OPTLY_GEN_VERSION_COMMAND add a fourth parameter
to optly_parse_args(), the version string:
optly_parse_args(argc, argv, &cmd, "1.0.0");Define these before including optly:
| Macro | Default | Effect |
|---|---|---|
OPTLY_NO_EXIT |
off | Never call exit(); return the errors instead |
OPTLY_MAX_POSITIONALS |
64 | Values one positional can hold |
OPTLY_MAX_ERRORS |
32 | Errors collected before further ones are dropped |
OPTLY_FLAG_BUFFER_LENGTH |
256 | Buffer used while formatting help output |
OPTLY_LOG |
fprintf to stderr |
Where optly's own messages go |
OPTLY_HELP_SHORT_FLAG |
"-h" |
Short flag for generated help |
OPTLY_VERSION_SHORT_FLAG |
"-v" |
Short flag for generated version |
OPTLYDEF |
empty | Linkage of the public functions |
Optly is a C library. It is not tested as C++ and does not try to compile as C++ -- use argparse, CLI11 or cxxopts there.
Optly itself is a single header -- there is nothing to build to use it. The
repository ships build.c, which compiles the examples and runs the tests.
Compile it once, then use the binary:
cc build.c -o build
./build # compile every example into ./out
./build tests # run the .tspec suite
./build clean # empty the output directoryRun ./build help for more info.
Tests live in ./tests/, one directory per concept. Each holds a small C
fixture and a test.tspec that compiles it, runs it, and compares the exact
bytes it writes.
They are written in the .tspec format and run with strum:
./build testsstrum has to be on your PATH. Use --tspec-runner to point at a different
implementation of the format.
Read more about tspec and strum at https://github.com/strongleong/strum
Optly focuses on:
- minimal runtime overhead
- zero allocations
- simple static configuration
This makes it suitable for:
- CLI utilities
- embedded tools
- static binaries
- low-level C projects
Optly is dual licensed:
- MIT License
- Public Domain (Unlicense)
Choose whichever works best for your project.