clix
Single-header file CLI libraries for C
Loading...
Searching...
No Matches
args.h
1// SPDX-License-Identifier: MIT
2/**
3 * @page args.h
4 * @author Jakov Dragičević (github:jakovdev)
5 * @copyright MIT License.
6 * @brief Decentralized CLI argument framework.
7 * @details
8 * This library lets each translation unit declare and register arguments near
9 * the code that owns them, instead of maintaining a single global argument
10 * table. Registration is constructor-driven and therefore complete before
11 * calling @ref args_parse.
12 *
13 * Correctness depends on constructor attribute support (GCC/Clang) or
14 * .CRT$XCU sections (MSVC) executing registration hooks before @c main.
15 *
16 * @attention WIP, only supports options.
17 * @note Partial C++ support due to struct field declaration order.
18 * Most macros will break because they don't take this into account.
19 * You can manually initialize fields which should make it work like in C.
20 *
21 * @see @subpage args_intro "Decentralized CLI Argument Framework"
22 *
23 * Source code:
24 * @include args.h
25 */
26
27/**
28 * @page args_intro Decentralized CLI Argument Framework
29 *
30 * # Overview
31 * @ref args.h "args.h" provides a header-only argument manager intended for
32 * modular C codebases. Arguments are declared with @ref ARGUMENT in any source
33 * file, registered automatically, then processed in three explicit stages:
34 * parsing, validation, and actions.
35 *
36 * # Feature Highlights
37 * - Distributed argument declarations with constructor-based registration.
38 * - Automatic help generation.
39 * - Dependency/conflict/subset relationships between arguments.
40 * - Automatic validation of required arguments and relation rules.
41 * - Typed parsing helpers via @ref ARG_PARSER family.
42 * - Validation and general callbacks with flexible execution policies.
43 * - Deterministic cross-file ordering for help, validate, and action stages.
44 *
45 * # Quick Start
46 * @code{.c}
47 * #define ARGS_IMPLEMENTATION
48 * #include "args.h"
49 *
50 * // Simple flag argument example
51 *
52 * static bool my_flag;
53 *
54 * static void print_my_flag(void)
55 * {
56 * printf("Hello from my_flag\n");
57 * }
58 *
59 * ARGUMENT(my_flag) = {
60 * .set = &my_flag,
61 * .action_callback = print_my_flag,
62 * .action_phase = ARG_CALLBACK_IF_SET,
63 * .help = "Enable my flag",
64 * .lopt = "my-flag",
65 * .opt = 'f',
66 * };
67 *
68 * int main(int argc, char **argv) {
69 * if (!args_parse(argc, argv) || !args_validate())
70 * return 1;
71 * args_actions();
72 * printf("My flag is %s\n", my_flag ? "enabled!" : "disabled!");
73 * return 0;
74 * }
75 *
76 * // Possible outputs:
77 * //
78 * // $ ./my_program
79 * // My flag is disabled!
80 * //
81 * // $ ./my_program -f
82 * // Hello from my_flag!
83 * // My flag is enabled!
84 * //
85 * // $ ./my_program -h
86 * // Usage: ./my_program [ARGUMENTS]
87 * //
88 * // Optional arguments:
89 * // -f, --my-flag Enable my flag
90 * // -h, --help Display this help message
91 * @endcode
92 *
93 * # Docs Navigation
94 * - @ref args_core "Core API"
95 * - @ref args_parsers "Parser Helpers"
96 * - @ref args_callbacks "Callback Phases"
97 * - @ref args_relations "Relationships"
98 * - @ref args_customizable "Customization Points"
99 * @if ARGS_INTERNALS
100 * - @ref args_internals "Internals"
101 * @endif
102 */
103
104#ifndef ARGS_H_
105#define ARGS_H_
106
107#include <errno.h>
108#include <stdbool.h>
109#include <stddef.h>
110
111/** @defgroup args_core args.h: Core API */
112/** @defgroup args_parsers args.h: Parser Helpers */
113/** @defgroup args_callbacks args.h: Callback Phases */
114/** @defgroup args_relations args.h: Relationships */
115/** @defgroup args_customizable args.h: Customization Points */
116/** @cond ARGS_INTERNALS */
117/** @defgroup args_internals args.h: Internals */
118/** @endcond */
119
120/** @addtogroup args_core
121 * @brief Argument creation and registration macros.
122 * @details
123 * Use @ref ARGUMENT to declare and register new arguments.
124 * Each argument is an instance of @ref argument, which has fields for storage,
125 * callbacks, schema rules, and ordering controls that you can initialize.
126 */
127
128/** @name Creating New Arguments */
129/** @{ */
130
131#ifndef __cplusplus
132/** @ingroup args_core
133 * @brief Creates and registers a new argument object.
134 * @hideinitializer
135 * @details
136 * Use this macro at file scope. It declares @ref argument @c _arg_<name>,
137 * wires constructor-based registration, then leaves a second
138 * declaration target for designated initialization.
139 * @param name Unique argument identifier used in generated symbol names.
140 * @pre The identifier is unique within the link unit.
141 * @post The argument is linked into internal processing lists before @c main.
142 * @see ARG
143 * @see ARG_DECLARE
144 * @see ARG_EXTERN
145 */
146#define ARGUMENT(name) \
147 ARG_DECLARE(name); \
148 _ARGS_CONSTRUCTOR(_arg_register_##name) \
149 { \
150 _args_register(ARG(name)); \
151 } \
152 ARG_DECLARE(name)
153#else
154#define ARGUMENT(name) \
155 ARG_EXTERN(name); \
156 _ARGS_CONSTRUCTOR(_arg_register_##name) \
157 { \
158 _args_register(ARG(name)); \
159 } \
160 ARG_DECLARE(name)
161#endif
162
163/** @} */
164/** @name Requirement And Visibility */
165/** @{ */
166
167/** @ingroup args_core
168 * @brief Requirement policy for @ref argument::arg_req.
169 */
171 ARG_OPTIONAL, /**< User does not need to provide the argument. */
172 ARG_REQUIRED, /**< Argument must be present unless conflicted out. */
173 ARG_HIDDEN, /**< Optional and omitted from generated help output. */
174 ARG_SOMETIME, /**< Conditionally required, enforce via relations/callbacks. */
175};
176
177/** @} */
178/** @name Parameter Modes */
179/** @{ */
180
181/** @ingroup args_core
182 * @brief Parameter requirement for @ref argument::param_req.
183 * @remark Semantics intentionally match common @c getopt usage.
184 */
186 ARG_PARAM_NONE, /**< Argument is a flag, does not take a parameter. */
187 ARG_PARAM_REQUIRED, /**< @ref argument::parse_callback receives non-NULL @p str. */
188 ARG_PARAM_OPTIONAL, /**< @ref argument::parse_callback may receive NULL @p str. */
189};
190
191/** @} */
192
193/** @addtogroup args_parsers
194 * @brief Parser callback generation and built-in parser helpers.
195 * @par Built-in parser wrappers
196 * @ref ARG_PARSE_L, @ref ARG_PARSE_LL, @ref ARG_PARSE_UL,
197 * @ref ARG_PARSE_ULL, @ref ARG_PARSE_F, and @ref ARG_PARSE_D are convenience
198 * wrappers around @ref ARG_PARSER.
199 */
200
201/** @name Parser Generation */
202/** @{ */
203
204/** @ingroup args_parsers
205 * @brief Generates a typed parser callback with common conversion checks.
206 * @hideinitializer
207 * @details
208 * The generated function is named @c parse_<name> and matches @ref argument::parse_callback.
209 * It parses @p str, rejects malformed input, range errors, and custom condition failures,
210 * then writes to @p dest.
211 * @param name Suffix used for generated function name.
212 * @param strto Conversion routine, e.g. @c strtol or @c strtod.
213 * @param ARG_BASE Macro, leave out macro for no base, or e.g. ARG_BASE(10) to pass to strto.
214 * @param strto_t Return type of @p strto.
215 * @param dest_t Destination pointee type.
216 * @param CAST Optional cast expression when narrowing/widening.
217 * @param cond Rejection predicate evaluated against local variable @c val.
218 * @param err Error text, falls back to @ref ARG_ERR when empty.
219 * @retval ARG_VALID Input accepted and stored in @p dest.
220 * @retval ARG_INVALID Input rejected with diagnostic message.
221 */
222#define ARG_PARSER(name, strto, ARG_BASE, strto_t, dest_t, CAST, cond, err) \
223 static struct arg_callback parse_##name(const char *str, void *dest) \
224 { \
225 errno = 0; \
226 char *end = NULL; \
227 strto_t val = strto(str, &end ARG_BASE); \
228 if (end == str || *end != '\0' || errno == ERANGE || (cond)) \
229 return ARG_INVALID((err) && *(err) ? (err) : ARG_ERR); \
230 *(dest_t *)dest = CAST val; \
231 return ARG_VALID(); \
232 }
233
234/** @ingroup args_parsers
235 * @brief Helper for the @p ARG_BASE parameter in @ref ARG_PARSER.
236 * @note If the conversion function doesn't take a base parameter,
237 * omit the macro like in the @ref ARG_PARSE_F macro definition.
238 * @param N Numeric base to pass to conversion functions, e.g. 10 for decimal or 16 for hex.
239 * @hideinitializer
240 */
241#define ARG_BASE(N) , N
242
243#ifndef ARG_ERR
244/** @ingroup args_parsers
245 * @brief Default parser error message if @p err is empty in @ref ARG_PARSER.
246 * @remark Overridable before including @ref args.h.
247 */
248#define ARG_ERR "Invalid value"
249#endif
250
251/** @} */
252/** @name Built-in Parser Wrappers */
253/** @{ */
254
255/** @ingroup args_parsers
256 * @brief Convenience wrapper of @ref ARG_PARSER using @c strtol.
257 * @hideinitializer
258 */
259#define ARG_PARSE_L(name, base, dest_t, CAST, cond, err) \
260 ARG_PARSER(name, strtol, ARG_BASE(base), long, dest_t, CAST, cond, err)
261
262/** @ingroup args_parsers
263 * @brief Convenience wrapper of @ref ARG_PARSER using @c strtoll.
264 * @hideinitializer
265 */
266#define ARG_PARSE_LL(name, base, dest_t, CAST, cond, err) \
267 ARG_PARSER(name, strtoll, ARG_BASE(base), long long, dest_t, CAST, \
268 cond, err)
269
270/** @ingroup args_parsers
271 * @brief Convenience wrapper of @ref ARG_PARSER using @c strtoul.
272 * @hideinitializer
273 */
274#define ARG_PARSE_UL(name, base, dest_t, CAST, cond, err) \
275 ARG_PARSER(name, strtoul, ARG_BASE(base), unsigned long, dest_t, CAST, \
276 cond, err)
277
278/** @ingroup args_parsers
279 * @brief Convenience wrapper of @ref ARG_PARSER using @c strtoull.
280 * @hideinitializer
281 */
282#define ARG_PARSE_ULL(name, base, dest_t, CAST, cond, err) \
283 ARG_PARSER(name, strtoull, ARG_BASE(base), unsigned long long, dest_t, \
284 CAST, cond, err)
285
286/** @ingroup args_parsers
287 * @brief Convenience wrapper of @ref ARG_PARSER using @c strtof.
288 * @hideinitializer
289 */
290#define ARG_PARSE_F(name, dest_t, CAST, cond, err) \
291 ARG_PARSER(name, strtof, , float, dest_t, CAST, cond, err)
292
293/** @ingroup args_parsers
294 * @brief Convenience wrapper of @ref ARG_PARSER using @c strtod.
295 * @hideinitializer
296 */
297#define ARG_PARSE_D(name, dest_t, CAST, cond, err) \
298 ARG_PARSER(name, strtod, , double, dest_t, CAST, cond, err)
299
300/** @} */
301
302/** @addtogroup args_parsers
303 * @par Custom parser contract
304 * Custom parser callbacks return @ref ARG_INVALID or @ref ARG_VALID and
305 * use signature @c (const char *str, void *dest).
306 */
307
308/** @name Parser Generation */
309/** @{ */
310
311/** @ingroup args_callbacks
312 * @brief Result object returned by parser/validator callbacks.
313 * @invariant @ref arg_callback::error is @c NULL when the callback succeeds.
314 */
316 /**
317 * @brief Diagnostic user-facing message for failures, NULL on success.
318 */
319 const char *error;
320};
321
322/** @ingroup args_callbacks
323 * @brief Creates a failing @ref arg_callback value.
324 * @param msg Diagnostic message to show the user, e.g. "Must be a positive integer".
325 * @hideinitializer
326 */
327#define ARG_INVALID(msg) ((struct arg_callback){ .error = msg })
328
329/** @ingroup args_callbacks
330 * @brief Creates a successful @ref arg_callback value.
331 * @hideinitializer
332 */
333#define ARG_VALID() ((struct arg_callback){ .error = NULL })
334
335/** @} */
336
337/** @ingroup args_parsers
338 * @brief Copy of the raw process argument vector.
339 * @details Exposed for use cases such as rendering usage with executable name.
340 */
341struct args_raw {
342 int c; /**< Argument count. */
343 char **v; /**< Argument vector. */
344};
345
346#ifdef ARGS_EXTERN_ARGR
347/** @ingroup args_customizable
348 * @brief Global storage of raw @c argc/@c argv values.
349 * @note Available when @c ARGS_EXTERN_ARGR is defined before including @ref args.h.
350 */
351extern struct args_raw argr;
352#endif
353
354/** @ingroup args_customizable
355 * @brief Prints generated CLI help output.
356 * @details
357 * Create your own help parse callback and call this function from it so you
358 * can provide custom @p usage, @p hint, @p req, and @p opt strings.
359 *
360 * Example:
361 * @code{.c}
362 * #define ARGS_EXTERN_ARGR
363 * #include "args.h"
364 *
365 * static struct arg_callback parse_help(const char *str, void *dest)
366 * {
367 * // help is a flag argument, safe to ignore both
368 * (void)str;
369 * (void)dest;
370 * args_help_print("Usage: ",
371 * argr.v[0],
372 * " [OPTIONS]\n",
373 * "\nRequired options:\n",
374 * "\nOptional options:\n");
375 * exit(EXIT_SUCCESS);
376 * }
377 *
378 * ARGUMENT(help) = {
379 * .parse_callback = parse_help,
380 * .help = "Display this help message",
381 * .lopt = "help",
382 * .opt = 'h',
383 * };
384 * @endcode
385 *
386 * Outputs: @c "{usage}{bin}{hint}{req}<required args>\n{opt}<optional args>\n"
387 */
388void args_help_print(const char *usage, const char *bin, const char *hint,
389 const char *req, const char *opt);
390
391/** @name Callback Stages */
392/** @{ */
393
394/** @ingroup args_core
395 * @brief Parses command line arguments and runs parse-phase relations.
396 * @details
397 * For each user-provided option, the parser resolves the matching @ref argument
398 * and calls @ref argument::parse_callback when available.
399 * Parse-time dependency/conflict relations are enforced immediately.
400 * Duplicate-argument detection applies only when @ref argument::set is non-NULL,
401 * though keep in mind it is implicitly allocated for most features.
402 *
403 * @par Execution order
404 * For each user-provided argument:
405 * -# If argument takes a parameter and is already set, error for repeated argument
406 * -# Run @ref argument::parse_callback if it exists
407 * -# Check dependency/conflict relations if @ref arg_relation_phase is @ref arg_relation_phase::ARG_RELATION_PARSE
408 * -# Set @ref argument::set to true (implicit allocation if needed)
409 *
410 * @pre Every argument was declared via @ref ARGUMENT and linked in.
411 * @post All successfully processed arguments with non-NULL @ref argument::set
412 * have @c *set == true.
413 * @retval true Parse succeeded for all encountered options.
414 * @retval false At least one user-facing parse error occurred.
415 * @see args_validate
416 */
417bool args_parse(int argc, char *argv[]);
418
419/** @} */
420
421/** @addtogroup args_callbacks
422 * @brief Callback execution policies.
423 * @details
424 * @par Lifecycle
425 * Execution proceeds through parse, validate, and action stages. Internal
426 * schema checks happen inside library logic. Project-specific checks belong in
427 * your callbacks.
428 */
429
430/** @name Callback Phases */
431/** @{ */
432
433/** @ingroup args_callbacks
434 * @brief Execution policy for @ref argument::validate_phase and @ref argument::action_phase.
435 */
437 ARG_CALLBACK_ALWAYS, /**< Run regardless of user input state. */
438 ARG_CALLBACK_IF_SET, /**< Run only when the argument was provided. */
439 ARG_CALLBACK_IF_UNSET, /**< Run only when the argument was not provided. */
440};
441
442/** @} */
443/** @name Callback Stages */
444/** @{ */
445
446/** @ingroup args_core
447 * @brief Validates argument schema rules and user-provided combinations.
448 * @details
449 * This stage enforces required-argument rules, non-parse relation phases, and
450 * executes @ref argument::validate_callback according to @ref argument::validate_phase.
451 *
452 * @par Validation checks
453 * For each registered argument:
454 * -# If @ref arg_requirement::ARG_REQUIRED, check it was provided (unless conflicted out)
455 * -# Check dependency/conflict relations if @ref arg_relation_phase is not @ref arg_relation_phase::ARG_RELATION_PARSE
456 * -# Run @ref argument::validate_callback if it exists using @ref argument::validate_phase
457 *
458 * @pre @ref args_parse was called.
459 * @post All validation callbacks due in this pass have executed.
460 * @retval true All checks passed.
461 * @retval false One or more constraints failed.
462 */
463bool args_validate(void);
464
465/** @ingroup args_core
466 * @brief Executes action callbacks for arguments matching action phase rules.
467 * @pre @ref args_validate returned @c true.
468 * @post All eligible @ref argument::action_callback functions have run.
469 */
470void args_actions(void);
471
472/** @} */
473
474/** @addtogroup args_relations
475 * @brief Dependencies, conflicts, and subset propagation.
476 * @details
477 * Relations model command schemas declaratively:
478 * - dependency: argument requires others to be set
479 * - conflict: argument requires others to be unset, overriding ARG_REQUIRED
480 * - subset: setting an argument that lists subset arguments triggers them
481 */
482
483/** @name Referencing Arguments */
484/** @{ */
485
486/** @ingroup args_relations
487 * @brief Returns the address of @ref argument object @p name.
488 * @param name Argument identifier used in @ref ARGUMENT.
489 * @hideinitializer
490 */
491#define ARG(name) &_arg_##name
492
493/** @ingroup args_relations
494 * @brief Forward declares an argument for same-file forward references.
495 * @hideinitializer
496 * @details
497 * Example:
498 * @code{.c}
499 * ARG_DECLARE(foo);
500 * ARGUMENT(bar) = {
501 * ...
502 * ARG_DEPENDS(ARG_RELATION_PARSE, ARG(foo)),
503 * ...
504 * };
505 * ARGUMENT(foo) = { ... };
506 * @endcode
507 * @param name Argument identifier used in @ref ARGUMENT.
508 */
509#define ARG_DECLARE(name) struct argument _arg_##name
510
511/** @ingroup args_relations
512 * @brief Extern declaration for an argument defined in another file.
513 * @hideinitializer
514 * @details
515 * Example:
516 *
517 * foo.c
518 * @code{.c}
519 * ARGUMENT(foo) = { ... };
520 * @endcode
521 *
522 * bar.c
523 * @code{.c}
524 * ARG_EXTERN(foo);
525 * ARGUMENT(bar) = {
526 * ...
527 * ARG_DEPENDS(ARG_RELATION_PARSE, ARG(foo)),
528 * ...
529 * };
530 * @endcode
531 * @param name Argument identifier used in @ref ARGUMENT.
532 */
533#define ARG_EXTERN(name) extern struct argument _arg_##name
534
535/** @} */
536/** @name Relation Declarations */
537/** @{ */
538
539/** @ingroup args_relations
540 * @brief Declares dependency relations for an argument.
541 * @hideinitializer
542 * @details
543 * When setting this argument, all dependencies must already be set.
544 * Use inside an @ref ARGUMENT definition.
545 *
546 * Example:
547 * @code{.c}
548 * ARGUMENT(foo) = { ... };
549 * ARGUMENT(bar) = { ... };
550 * ARGUMENT(baz) = {
551 * ...
552 * ARG_DEPENDS(ARG_RELATION_PARSE, ARG(foo), ARG(bar)),
553 * ...
554 * };
555 * @endcode
556 * @param relation_phase When to check dependencies, see @ref arg_relation_phase.
557 * @param ... A list of @ref ARG macros with @ref ARGUMENT names.
558 */
559#define ARG_DEPENDS(relation_phase, ...) \
560 ._.deps_phase = relation_phase, \
561 ._.deps = (struct argument *[]){ __VA_ARGS__, NULL }, \
562 ._.deps_n = sizeof((struct argument *[]){ __VA_ARGS__ }) / \
563 sizeof(struct argument *)
564
565/** @ingroup args_relations
566 * @brief Declares conflict relations for an argument.
567 * @hideinitializer
568 * @details
569 * If a conflict argument is set, this one must not be set, overriding
570 * ARG_REQUIRED. Use inside an @ref ARGUMENT definition.
571 *
572 * Example:
573 * @code{.c}
574 * ARGUMENT(foo) = { ... };
575 * ARGUMENT(bar) = { ... };
576 * ARGUMENT(baz) = {
577 * ...
578 * ARG_CONFLICTS(ARG_RELATION_PARSE, ARG(foo), ARG(bar)),
579 * ...
580 * };
581 * @endcode
582 * @param relation_phase When to check conflicts, see @ref arg_relation_phase.
583 * @param ... A list of @ref ARG macros with @ref ARGUMENT names.
584 */
585#define ARG_CONFLICTS(relation_phase, ...) \
586 ._.cons_phase = relation_phase, \
587 ._.cons = (struct argument *[]){ __VA_ARGS__, NULL }, \
588 ._.cons_n = sizeof((struct argument *[]){ __VA_ARGS__ }) / \
589 sizeof(struct argument *)
590
591/** @ingroup args_relations
592 * @brief Declares subset arguments triggered by a parent argument.
593 * @hideinitializer
594 * @details
595 * When this argument is set, all subsets are also processed. Parent string
596 * is passed to subset parsers unless customized with @ref ARG_SUBSTRINGS.
597 * Use inside an @ref ARGUMENT definition.
598 *
599 * Example:
600 * @code{.c}
601 * ARGUMENT(foo) = { ... };
602 * ARGUMENT(bar) = { ... };
603 * ARGUMENT(baz) = {
604 * ...
605 * ARG_SUBSETS(ARG(foo), ARG(bar)),
606 * ARG_SUBSTRINGS("out.txt", ARG_SUBPASS),
607 * ...
608 * };
609 * @endcode
610 * @param ... A list of @ref ARG macros with @ref ARGUMENT names.
611 */
612#define ARG_SUBSETS(...) \
613 ._.subs = (struct argument *[]){ __VA_ARGS__, NULL }, \
614 ._.subs_n = sizeof((struct argument *[]){ __VA_ARGS__ }) / \
615 sizeof(struct argument *)
616
617/** @ingroup args_relations
618 * @brief Supplies index-aligned custom strings for subset processing.
619 * @hideinitializer
620 * @details
621 * Use @ref ARG_SUBPASS to pass parent string, omit entirely if not needed.
622 * @param ... A list of strings or @ref ARG_SUBPASS.
623 */
624#define ARG_SUBSTRINGS(...) \
625 ._.subs_strs = ((const char *[]){ __VA_ARGS__, NULL })
626
627/** @ingroup args_relations
628 * @brief Special marker meaning "forward parent string to subset parser".
629 * @hideinitializer
630 * @details
631 * Use in @ref ARG_SUBSTRINGS to indicate that the parent argument's string
632 * should be passed to the subset parser instead of a custom string.
633 */
634#define ARG_SUBPASS ((const char *)-1)
635
636/** @} */
637/** @name Relation Evaluation Phases */
638/** @{ */
639
640/** @ingroup args_relations
641 * @brief Relation evaluation phase used by @ref ARG_DEPENDS and @ref ARG_CONFLICTS.
642 */
644 ARG_RELATION_PARSE, /**< Parse-time checks, effectively no-op when unset. */
645 ARG_RELATION_VALIDATE_ALWAYS, /**< Validate-time checks regardless of argument state. */
646 ARG_RELATION_VALIDATE_SET, /**< Validate-time checks only when the argument is set. */
647 ARG_RELATION_VALIDATE_UNSET, /**< Validate-time checks only when the argument is not set. */
648};
649
650/** @} */
651
652/** @addtogroup args_relations
653 * @name Cross-File Execution Ordering
654 * @brief Deterministic ordering controls for help/validate/action lists.
655 * @{ */
656
657/** @ingroup args_relations
658 * @brief Place argument first in the selected ordered list.
659 * @hideinitializer
660 * @details
661 * Value for @ref argument::validate_order, @ref argument::action_order, or @ref argument::help_order.
662 */
663#define ARG_ORDER_FIRST ((struct argument *)-1)
664
665/** @ingroup args_relations
666 * @brief Place argument immediately after another argument.
667 * @hideinitializer
668 * @details
669 * Value for @ref argument::validate_order, @ref argument::action_order, or @ref argument::help_order.
670 * Use @ref ARG_DECLARE or @ref ARG_EXTERN to forward-declare the referenced argument.
671 *
672 * Example:
673 * @code{.c}
674 * ARG_DECLARE(foo);
675 * ARGUMENT(bar) = {
676 * ...
677 * .validate_order = ARG_ORDER_AFTER(ARG(foo)),
678 * ...
679 * };
680 * @endcode
681 * @param arg Argument supplied using the @ref ARG macro.
682 */
683#define ARG_ORDER_AFTER(arg) (arg)
684
685/** @} */
686/** @cond ARGS_INTERNALS */
687
688/** @addtogroup args_internals
689 * @brief Internal structures and definitions not intended for public use.
690 * @details
691 * These are implementation details and may change without warning.
692 * Don't rely on them outside of the library itself.
693 */
694
695/** @ingroup args_internals
696 * @brief Internal runtime/link-list and relation metadata.
697 */
698struct _args_internal {
699 struct argument *next_args;
700 struct argument *next_help;
701 struct argument *next_validate;
702 struct argument *next_action;
703 struct argument **deps;
704 size_t deps_n;
705 struct argument **cons;
706 size_t cons_n;
707 struct argument **subs;
708 const char **subs_strs;
709 size_t subs_n;
710 size_t help_len;
711 enum arg_relation_phase deps_phase;
712 enum arg_relation_phase cons_phase;
713 bool valid;
714};
715
716/** @endcond */
717/** @name Creating New Arguments */
718/** @{ */
719
720/** @ingroup args_core
721 * @brief Declarative schema object for one CLI argument.
722 * @details
723 * Initialize this type through @ref ARGUMENT.
724 * C designated initializers let you specify only the fields you need.
725 * @invariant At least one of @ref argument::opt or @ref argument::lopt is set.
726 */
727struct argument {
728 /** @name Storage */
729 /** @{ */
730
731 /**
732 * @brief Tracks whether the user supplied this argument.
733 * @details
734 * If NULL, it is allocated implicitly when needed by schema features.
735 */
736 bool *set;
737
738 /**
739 * @brief Destination pointer passed as @p dest to parser callbacks.
740 */
741 void *dest;
742
743 /** @} */
744 /** @name Callbacks */
745 /** @{ */
746
747 /**
748 * @brief Function invoked during @ref args_parse.
749 * @details
750 * Use @ref ARG_PARSER helpers for numeric parsing, or provide custom logic.
751 * Callbacks aren't limited to parsing e.g. --help exiting immediately.
752 * @warning @p str is NULL when @ref argument::param_req is @ref arg_parameter::ARG_PARAM_NONE.
753 * @warning @p dest ( @ref argument::dest ) is NULL if you didn't initialize it.
754 *
755 * Example:
756 * @code{.c}
757 * static double mydouble;
758 * ARG_PARSE_D(mydoubles, double, , val < 5.0,
759 * "Must be >= 5.0\n");
760 * ARGUMENT(myarg) = {
761 * ...
762 * .dest = &mydouble,
763 * .parse_callback = parse_mydoubles,
764 * ...
765 * };
766 * @endcode
767 *
768 * You can also write custom parsers:
769 * @code{.c}
770 * enum Color { COLOR_INVALID = -1, RED, GREEN, BLUE };
771 * static enum Color color = COLOR_INVALID;
772 * static struct arg_callback parse_color(const char *str, void *dest) {
773 * // Using 'color' directly is also possible in this example
774 * enum Color col = COLOR_INVALID;
775 * if (strcmp(str, "red") == 0)
776 * col = RED;
777 * else if (strcmp(str, "green") == 0)
778 * col = GREEN;
779 * else if (strcmp(str, "blue") == 0)
780 * col = BLUE;
781 * else
782 * return ARG_INVALID("Invalid color\n");
783 * *(enum Color *)dest = col;
784 * return ARG_VALID();
785 * }
786 * ARGUMENT(color) = {
787 * ...
788 * .dest = &color,
789 * .parse_callback = parse_color,
790 * ...
791 * };
792 * @endcode
793 * @param str User-provided value string.
794 * @param dest Alias of @ref argument::dest, potentially NULL.
795 * @retval ARG_VALID Callback accepted input.
796 * @retval ARG_INVALID Callback rejected input.
797 */
798 struct arg_callback (*parse_callback)(const char *str, void *dest);
799
800 /**
801 * @brief Function invoked during @ref args_validate.
802 * @details
803 * Use for project-specific constraints after the parse stage.
804 *
805 * Example:
806 * @code{.c}
807 * enum Color { RED, GREEN, BLUE };
808 * static enum Color color;
809 * static bool other_option;
810 * static struct arg_callback validate_color(void) {
811 * if (color == RED && other_option)
812 * return ARG_INVALID("Red color cannot be used.\n");
813 * return ARG_VALID();
814 * }
815 * ARGUMENT(color) = {
816 * ...
817 * .dest = &color,
818 * .validate_callback = validate_color,
819 * ...
820 * };
821 * @endcode
822 *
823 * @retval ARG_VALID Validation succeeded.
824 * @retval ARG_INVALID Validation failed.
825 */
826 struct arg_callback (*validate_callback)(void);
827
828 /**
829 * @brief Function invoked during @ref args_actions.
830 * @details
831 * Intended for side effects such as applying runtime configuration.
832 *
833 * Example:
834 * @code{.c}
835 * static bool verbose;
836 * static void print_verbose(void) {
837 * printf("Verbose mode is on\n");
838 * }
839 * ARGUMENT(verbose) = {
840 * ...
841 * .set = &verbose,
842 * .action_callback = print_verbose,
843 * .action_phase = ARG_CALLBACK_IF_SET,
844 * ...
845 * };
846 * @endcode
847 */
848 void (*action_callback)(void);
849
850 /** @} */
851 /** @name Schema */
852 /** @{ */
853
854 /**
855 * @brief Specifies whether this argument is required, optional, or hidden.
856 * @see arg_requirement
857 */
859
860 /**
861 * @brief Specifies whether this argument takes a parameter and if it's required.
862 * @see arg_parameter
863 */
865
866 /**
867 * @brief Controls when @ref argument::validate_callback runs.
868 * @see arg_callback_phase
869 */
871
872 /**
873 * @brief Controls when @ref argument::action_callback runs.
874 * @see arg_callback_phase
875 */
877
878 /** @} */
879 /** @name Ordering */
880 /** @{ */
881
882 /**
883 * @brief Ordering for validation.
884 * @details
885 * Use @ref ARG_ORDER_FIRST or @ref ARG_ORDER_AFTER, NULL means append at the end.
886 */
888
889 /**
890 * @brief Ordering for action execution.
891 * @details
892 * Use @ref ARG_ORDER_FIRST or @ref ARG_ORDER_AFTER, NULL means append at the end.
893 */
895
896 /**
897 * @brief Ordering for help display.
898 * @details
899 * Use @ref ARG_ORDER_FIRST or @ref ARG_ORDER_AFTER, NULL means append at the end.
900 */
902
903 /** @} */
904 /** @name Help Presentation */
905 /** @{ */
906
907 /**
908 * @brief Help description for this argument.
909 * @details
910 * Multiline strings are supported (e.g. @c "Line1\nLine2").
911 * @see ARGS_STR_PREPAD
912 * @see ARGS_PARAM_OFFSET
913 * @see ARGS_HELP_OFFSET
914 */
915 const char *help;
916
917 /**
918 * @brief Parameter name for help display (e.g., "N" for a number).
919 * @invariant Required when @ref argument::param_req is not @ref arg_parameter::ARG_PARAM_NONE.
920 */
921 const char *param;
922
923 /**
924 * @brief Long option name (e.g. @c "output" -> @c --output).
925 */
926 const char *lopt;
927
928 /**
929 * @brief Short option character (e.g. @c 'o' -> @c -o).
930 */
931 char opt;
932
933 /** @} */
934 /** @cond ARGS_INTERNALS */
935
936 /**
937 * @brief Internal runtime metadata populated by registration.
938 */
939 struct _args_internal _;
940
941 /** @endcond */
942};
943
944/** @} */
945/** @cond ARGS_INTERNALS */
946
947/** @ingroup args_internals
948 * @brief Internal registration entry point used by @ref ARGUMENT.
949 * @warning Prefer @ref ARGUMENT for all public usage.
950 */
951void _args_register(struct argument *);
952
953#ifdef __cplusplus
954#define _ARGS_CONSTRUCTOR(f) \
955 static void f(void); \
956 struct f##_t_ { \
957 f##_t_(void) \
958 { \
959 f(); \
960 } \
961 }; \
962 static f##_t_ f##_; \
963 static void f(void)
964#elif defined(_MSC_VER) && !defined(__clang__)
965#pragma section(".CRT$XCU", read)
966#define _ARGS_CONSTRUCTOR2_(f, p) \
967 static void f(void); \
968 __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \
969 __pragma(comment(linker, "/include:" p #f "_")) static void f(void)
970#ifdef _WIN64
971#define _ARGS_CONSTRUCTOR(f) _ARGS_CONSTRUCTOR2_(f, "")
972#else /* _WIN32 */
973#define _ARGS_CONSTRUCTOR(f) _ARGS_CONSTRUCTOR2_(f, "_")
974#endif
975#else /* GCC, Clang */
976/** @ingroup args_internals
977 * @brief Constructor abstraction for GCC/Clang/MSVC registration hooks.
978 * @hideinitializer
979 * @see https://stackoverflow.com/questions/1113409/attribute-constructor-equivalent-in-vc
980 */
981#define _ARGS_CONSTRUCTOR(f) \
982 static void f(void) __attribute__((constructor)); \
983 static void f(void)
984#endif
985
986/** @endcond */
987
988#endif /* ARGS_H_ */
989#if defined(ARGS_IMPLEMENTATION) && !defined(_ARGS_IMPLEMENTED)
990#define _ARGS_IMPLEMENTED
991
992/** @addtogroup args_customizable
993 * @brief Customization points for overriding default behaviors.
994 * @details
995 * Define the following macros before including @ref args.h to override default
996 * implementations. You can also use these in your own code for consistency.
997 */
998
999/** @name Help Formatting */
1000/** @{ */
1001
1002#ifndef ARGS_STR_PREPAD
1003/** @ingroup args_customizable
1004 * @brief Pre-padding for help text.
1005 * @remark Overridable before including @ref args.h.
1006 */
1007#define ARGS_STR_PREPAD (2)
1008#elif ARGS_STR_PREPAD < 0
1009#error ARGS_STR_PREPAD cannot be negative
1010#endif
1011
1012#ifndef ARGS_PARAM_OFFSET
1013/** @ingroup args_customizable
1014 * @brief Offset between argument and parameter in help text.
1015 * @remark Overridable before including @ref args.h.
1016 */
1017#define ARGS_PARAM_OFFSET (1)
1018#elif ARGS_PARAM_OFFSET < 1
1019#error ARGS_PARAM_OFFSET must be at least 1 for proper formatting
1020#endif
1021
1022#ifndef ARGS_HELP_OFFSET
1023/** @ingroup args_customizable
1024 * @brief Offset from longest argument for help text.
1025 * @remark Overridable before including @ref args.h.
1026 */
1027#define ARGS_HELP_OFFSET (4)
1028#elif ARGS_HELP_OFFSET < 1
1029#error ARGS_HELP_OFFSET must be at least 1 for proper formatting
1030#endif
1031
1032#ifndef ARGS_PRINT_H
1033/** @ingroup args_customizable
1034 * @brief Allow using print.h functions for args_pe, args_pd, args_pi and args_abort.
1035 * @remark Define @ref ARGS_PRINT_H and include @ref print.h before including @ref args.h.
1036 * @attention Temporarily disabled
1037 * @hideinitializer
1038 */
1039#define ARGS_PRINT_H 0
1040#else
1041#ifndef PRINT_H_
1042#error "Include print.h before including args.h"
1043#endif /* PRINT_H_ */
1044#undef ARGS_PRINT_H
1045#define ARGS_PRINT_H 0 /* 1 */
1046#endif /* ARGS_PRINT_H */
1047
1048#ifndef args_po
1049/* Disabled for now
1050#if ARGS_PRINT_H
1051#define args_po print
1052#else Default */
1053/** @ingroup args_customizable
1054 * @brief Normal print.
1055 * @remark Overridable before including @ref args.h.
1056 */
1057#define args_po(...) printf(__VA_ARGS__)
1058/* #endif ARGS_PRINT_H */
1059#endif /* args_po */
1060
1061/** @} */
1062/** @name Error Handling */
1063/** @{ */
1064
1065#ifndef args_pe
1066#if ARGS_PRINT_H
1067#define args_pe perr
1068#else /* Default */
1069/** @ingroup args_customizable
1070 * @brief Error print.
1071 * @remark Overridable before including @ref args.h.
1072 */
1073#define args_pe(...) fprintf(stderr, __VA_ARGS__)
1074#endif /* ARGS_PRINT_H */
1075#endif /* args_pe */
1076
1077#ifndef NDEBUG /* DEBUG */
1078#ifndef args_pd
1079#if ARGS_PRINT_H
1080#define args_pd pdev
1081#else /* Default */
1082/** @ingroup args_customizable
1083 * @brief Developer-only debug print.
1084 * @note Only available when NDEBUG is not defined
1085 * @remark Overridable before including @ref args.h.
1086 */
1087#define args_pd(...) fprintf(stderr, __VA_ARGS__)
1088#endif /* ARGS_PRINT_H */
1089#endif /* args_pd */
1090#else /* RELEASE */
1091#undef args_pd
1092#define args_pd(...)
1093#endif /* NDEBUG */
1094
1095#ifndef args_pi
1096#if ARGS_PRINT_H
1097#define args_pi(arg) perr("Internal error for %s\n", arg_str(arg))
1098#else /* Default */
1099/** @ingroup args_customizable
1100 * @brief Internal error print, user-facing dev print.
1101 * @remark Overridable before including @ref args.h.
1102 */
1103#define args_pi(arg) args_pe("Internal error for %s\n", arg_str(arg))
1104#endif /* ARGS_PRINT_H */
1105#endif /* args_pi */
1106
1107#ifndef args_abort
1108#if ARGS_PRINT_H
1109#define args_abort pabort
1110#else /* Default */
1111/** @ingroup args_customizable
1112 * @brief Abort function.
1113 * @remark Overridable before including @ref args.h.
1114 */
1115#define args_abort() abort()
1116#endif /* ARGS_PRINT_H */
1117#endif /* args_abort */
1118
1119#ifndef ARGS_IMPLICIT_SETS
1120/** @ingroup args_customizable
1121 * @brief Maximum implicit allocations of @ref argument::set booleans.
1122 * @remark Overridable before including @ref args.h.
1123 */
1124#define ARGS_IMPLICIT_SETS (64)
1125#elif ARGS_IMPLICIT_SETS < 1
1126#error ARGS_IMPLICIT_SETS must be at least 1 for defined behavior
1127#endif
1128
1129/** @} */
1130/** @cond ARGS_INTERNALS */
1131/** @addtogroup args_internals
1132 * @{
1133 */
1134
1135#include <stdio.h>
1136#include <stdlib.h>
1137#include <string.h>
1138
1139struct args_raw argr = { 0 };
1140
1141/* Lists */
1142static struct argument *args;
1143static struct argument *help;
1144static struct argument *validate;
1145static struct argument *action;
1146
1147#define for_each_arg(a, list) \
1148 for (struct argument *a = (list); a; a = (a)->_.next_##list)
1149
1150#define for_each_rel(a, rel, var) \
1151 for (size_t var##i = 0; var##i < (a)->_.rel##_n; var##i++) \
1152 for (struct argument *var = (a)->_.rel[var##i]; var; var = NULL)
1153
1154static size_t args_num;
1155static size_t longest;
1156
1157#ifndef ARG_STR_MAX_CALLS
1158#define ARG_STR_MAX_CALLS (2)
1159#endif
1160
1161#ifndef ARG_STR_BUF_SIZE
1162#define ARG_STR_BUF_SIZE (BUFSIZ)
1163#endif
1164
1165static const char *arg_str(const struct argument *a)
1166{
1167 if (!a)
1168 return "<null-arg>";
1169
1170 static char buf[ARG_STR_MAX_CALLS][ARG_STR_BUF_SIZE];
1171 static size_t i = 0;
1172
1173 if (a->opt && a->lopt)
1174 snprintf(buf[i], sizeof(buf[i]), "-%c, --%s", a->opt, a->lopt);
1175 else if (a->opt) /* TODO: Handle case where every arg has no lopt */
1176 snprintf(buf[i], sizeof(buf[i]), "-%c ", a->opt);
1177 else if (a->lopt)
1178 snprintf(buf[i], sizeof(buf[i]), " --%s", a->lopt);
1179 else
1180 return "<invalid-arg>";
1181
1182 i = 1 - i;
1183 return buf[1 - i];
1184}
1185
1186static void arg_set_new(struct argument *a)
1187{
1188 static bool sets[ARGS_IMPLICIT_SETS] = { 0 };
1189 static size_t sets_n = 0;
1190
1191 if (sets_n >= ARGS_IMPLICIT_SETS) {
1192 args_pd("ARGS_IMPLICIT_SETS exceeded, try increasing it\n");
1193 args_pi(a);
1194 args_abort();
1195 }
1196
1197 a->set = &sets[sets_n++];
1198}
1199
1200void _args_register(struct argument *a)
1201{
1202 if (!a) {
1203 args_pd("Cannot register %s\n", arg_str(a));
1204 args_pi(a);
1205 args_abort();
1206 }
1207
1208 if (!a->opt && !a->lopt) {
1209 args_pd("%s must have an option\n", arg_str(a));
1210 args_pi(a);
1211 args_abort();
1212 }
1213
1214 if (a->_.valid) {
1215 args_pd("%s has internals pre-set\n", arg_str(a));
1216 args_pi(a);
1217 args_abort();
1218 }
1219
1220 if (a->param_req != ARG_PARAM_NONE && !a->param) {
1221 args_pd("%s requires parameter but has no .param\n",
1222 arg_str(a));
1223 args_pi(a);
1224 args_abort();
1225 }
1226
1227 if (a->param_req != ARG_PARAM_NONE && !a->parse_callback) {
1228 args_pd("%s has .param but has no .parse_callback\n",
1229 arg_str(a));
1230 args_pi(a);
1231 args_abort();
1232 }
1233
1235 args_pd("%s has .validate_phase but has no .validate_callback\n",
1236 arg_str(a));
1237 args_pi(a);
1238 args_abort();
1239 }
1240
1242 args_pd("%s has .action_phase but has no .action_callback\n",
1243 arg_str(a));
1244 args_pi(a);
1245 args_abort();
1246 }
1247
1248 if (a->arg_req == ARG_SOMETIME && !a->_.deps && !a->_.cons &&
1249 !a->validate_callback) {
1250 args_pd("%s has no dependencies, conflicts, or validator\n",
1251 arg_str(a));
1252 args_pi(a);
1253 args_abort();
1254 }
1255
1256 if (!a->set) {
1257 bool needs_set = false;
1258 if (a->param_req != ARG_PARAM_NONE)
1259 needs_set = true;
1260 if (a->arg_req != ARG_OPTIONAL && a->arg_req != ARG_HIDDEN)
1261 needs_set = true;
1264 needs_set = true;
1265 if (a->_.deps || a->_.cons || a->_.subs)
1266 needs_set = true;
1267 if (needs_set)
1268 arg_set_new(a);
1269 }
1270
1271 size_t ndeps = 0;
1272 size_t ncons = 0;
1273 size_t nsubs = 0;
1274
1275 if (!a->_.deps) {
1276 if (a->_.deps_n > 0) {
1277 args_pd("%s has deps_n=%zu but deps=NULL\n", arg_str(a),
1278 a->_.deps_n);
1279 args_pd("Add dependencies using ARG_DEPENDS()\n");
1280 args_pi(a);
1281 args_abort();
1282 }
1283
1284 if (a->_.deps_phase != ARG_RELATION_PARSE) {
1285 args_pd("%s has relation phase but no dependencies\n",
1286 arg_str(a));
1287 args_pi(a);
1288 args_abort();
1289 }
1290
1291 goto arg_no_deps;
1292 }
1293
1294 while (a->_.deps[ndeps])
1295 ndeps++;
1296
1297 if (ndeps != a->_.deps_n) {
1298 args_pd("%s deps_n=%zu but actual is %zu\n", arg_str(a),
1299 a->_.deps_n, ndeps);
1300 args_pd("Add dependencies using ARG_DEPENDS()\n");
1301 args_pi(a);
1302 args_abort();
1303 }
1304
1305 for_each_rel(a, deps, dep) {
1306 if (!dep) {
1307 args_pd("%s NULL deps[%zu]\n", arg_str(a), depi);
1308 args_pi(a);
1309 args_abort();
1310 }
1311
1312 if (dep == a) {
1313 args_pd("%s depends on itself\n", arg_str(a));
1314 args_pi(a);
1315 args_abort();
1316 }
1317
1318 if (!dep->set)
1319 arg_set_new(dep);
1320 }
1321
1322arg_no_deps:
1323 if (!a->_.cons) {
1324 if (a->_.cons_n > 0) {
1325 args_pd("%s cons_n=%zu but cons=NULL\n", arg_str(a),
1326 a->_.cons_n);
1327 args_pd("Add conflicts using ARG_CONFLICTS()\n");
1328 args_pi(a);
1329 args_abort();
1330 }
1331
1332 if (a->_.cons_phase != ARG_RELATION_PARSE) {
1333 args_pd("%s has relation phase but no conflicts\n",
1334 arg_str(a));
1335 args_pi(a);
1336 args_abort();
1337 }
1338
1339 goto arg_no_cons;
1340 }
1341
1342 while (a->_.cons[ncons])
1343 ncons++;
1344
1345 if (ncons != a->_.cons_n) {
1346 args_pd("%s cons_n=%zu but actual is %zu\n", arg_str(a),
1347 a->_.cons_n, ncons);
1348 args_pd("Add conflicts using ARG_CONFLICTS()\n");
1349 args_pi(a);
1350 args_abort();
1351 }
1352
1353 for_each_rel(a, cons, con) {
1354 if (!con) {
1355 args_pd("%s NULL cons[%zu]\n", arg_str(a), coni);
1356 args_pi(a);
1357 args_abort();
1358 }
1359
1360 if (con == a) {
1361 args_pd("%s conflicts itself\n", arg_str(a));
1362 args_pi(a);
1363 args_abort();
1364 }
1365
1366 if (!con->set)
1367 arg_set_new(con);
1368
1369 for_each_rel(a, deps, dep) {
1370 if (dep != con)
1371 continue;
1372
1373 args_pd("%s both depends and conflicts %s\n",
1374 arg_str(a), arg_str(con));
1375 args_pi(a);
1376 args_abort();
1377 }
1378 }
1379
1380arg_no_cons:
1381 if (!a->_.subs) {
1382 if (a->_.subs_n > 0) {
1383 args_pd("%s subs_n=%zu but subs=NULL\n", arg_str(a),
1384 a->_.subs_n);
1385 args_pd("Specify subsets using ARG_SUBSETS()\n");
1386 args_pi(a);
1387 args_abort();
1388 }
1389
1390 if (a->_.subs_strs) {
1391 args_pd("%s has subs_strs but no subsets\n",
1392 arg_str(a));
1393 args_pi(a);
1394 args_abort();
1395 }
1396
1397 goto arg_no_subs;
1398 }
1399
1400 while (a->_.subs[nsubs])
1401 nsubs++;
1402
1403 if (nsubs != a->_.subs_n) {
1404 args_pd("%s subs_n=%zu but actual is %zu\n", arg_str(a),
1405 a->_.subs_n, nsubs);
1406 args_pd("Specify subset args using ARG_SUBSETS()\n");
1407 args_pi(a);
1408 args_abort();
1409 }
1410
1411 if (a->_.subs_strs) {
1412 size_t nsstrs = 0;
1413 while (a->_.subs_strs[nsstrs])
1414 nsstrs++;
1415
1416 if (nsstrs != a->_.subs_n) {
1417 args_pd("%s subs_n=%zu but subs_strs has %zu entries\n",
1418 arg_str(a), a->_.subs_n, nsstrs);
1419 args_pd("Both lists must be the same size\n");
1420 args_pi(a);
1421 args_abort();
1422 }
1423 }
1424
1425 for_each_rel(a, subs, sub) {
1426 if (sub == a) {
1427 args_pd("%s subsets itself\n", arg_str(a));
1428 args_pi(a);
1429 args_abort();
1430 }
1431
1432 if (!sub->set)
1433 arg_set_new(sub);
1434
1435 if (a->param_req != ARG_PARAM_REQUIRED &&
1436 sub->param_req == ARG_PARAM_REQUIRED &&
1437 (!a->_.subs_strs || a->_.subs_strs[subi] == ARG_SUBPASS)) {
1438 args_pd("%s requires param but superset %s might not and has no custom string\n",
1439 arg_str(sub), arg_str(a));
1440 args_pi(a);
1441 args_abort();
1442 }
1443
1444 if (!a->set)
1445 arg_set_new(a);
1446
1447 for_each_rel(a, cons, con) {
1448 if (con == sub) {
1449 args_pd("%s both supersets and conflicts %s\n",
1450 arg_str(a), arg_str(sub));
1451 args_pi(a);
1452 args_abort();
1453 }
1454 }
1455
1456 for_each_rel(sub, deps, dep) {
1457 if (dep == a) {
1458 args_pd("%s supersets %s but also depends on it\n",
1459 arg_str(a), arg_str(sub));
1460 args_pi(a);
1461 args_abort();
1462 }
1463 }
1464 }
1465
1466arg_no_subs:
1467 for_each_arg(c, args) {
1468 if ((a->opt && c->opt && a->opt == c->opt) ||
1469 (a->lopt && c->lopt && strcmp(a->lopt, c->lopt) == 0)) {
1470 args_pd("%s same opts as %s\n", arg_str(a), arg_str(c));
1471 args_pi(a);
1472 args_abort();
1473 }
1474 }
1475
1476#define args_insert(list) \
1477 do { \
1478 a->_.next_##list = list; \
1479 list = a; \
1480 } while (0);
1481
1482 args_insert(args);
1483 args_insert(help);
1484 args_insert(validate);
1485 args_insert(action);
1486#undef args_insert
1487
1488 size_t len = ARGS_STR_PREPAD + strlen(arg_str(a));
1489 if (a->param)
1490 len += ARGS_PARAM_OFFSET + strlen(a->param);
1491 size_t check = len;
1492 if (a->help)
1493 check += ARGS_HELP_OFFSET + strlen(a->help);
1494 if (check >= ARG_STR_BUF_SIZE) {
1495 args_pd("%s combined opt, lopt, help string too long: %zu chars\n",
1496 arg_str(a), check);
1497 args_pi(a);
1498 args_abort();
1499 }
1500 a->_.help_len = len;
1501 if (len > longest)
1502 longest = len;
1503
1504 a->_.valid = true;
1505 args_num++;
1506}
1507
1508static bool arg_process(struct argument *a, const char *str)
1509{
1510 if (!a->_.valid) {
1511 args_pd("%s has internals pre-set\n", arg_str(a));
1512 args_pd("Please register arguments using ARGUMENT()\n");
1513 args_pi(a);
1514 args_abort();
1515 }
1516
1517 if (a->set && *a->set) {
1518 args_pe("Argument %s specified multiple times\n", arg_str(a));
1519 return false;
1520 }
1521
1522 if (a->parse_callback) {
1523 struct arg_callback ret = a->parse_callback(str, a->dest);
1524 if (ret.error) {
1525 args_pe("%s: %s\n", arg_str(a), ret.error);
1526 return false;
1527 }
1528 }
1529
1530 if (a->_.deps_phase == ARG_RELATION_PARSE) {
1531 for_each_rel(a, deps, dep) {
1532 if (!*dep->set) {
1533 args_pe("%s requires %s to be set first\n",
1534 arg_str(a), arg_str(dep));
1535 return false;
1536 }
1537 }
1538 }
1539
1540 if (a->_.cons_phase == ARG_RELATION_PARSE) {
1541 for_each_rel(a, cons, con) {
1542 if (*con->set) {
1543 args_pe("%s conflicts with %s\n", arg_str(a),
1544 arg_str(con));
1545 return false;
1546 }
1547 }
1548 }
1549
1550 if (a->set)
1551 *a->set = true;
1552
1553 for_each_rel(a, subs, sub) {
1554 if (*sub->set)
1555 continue;
1556
1557 const char *sub_str = str;
1558 if (a->_.subs_strs && a->_.subs_strs[subi] &&
1559 a->_.subs_strs[subi] != ARG_SUBPASS)
1560 sub_str = a->_.subs_strs[subi];
1561
1562 if (!arg_process(sub, sub_str))
1563 return false;
1564 }
1565
1566 return true;
1567}
1568
1569static bool arg_option(const char *token)
1570{
1571 if (!token || token[0] != '-' || token[1] == '\0')
1572 return false;
1573
1574 if (strcmp(token, "--") == 0)
1575 return true;
1576
1577 if (token[1] == '-') {
1578 const char *name = token + 2;
1579 const char *eq = strchr(name, '=');
1580 size_t name_len = eq ? (size_t)(eq - name) : strlen(name);
1581 for_each_arg(c, args) {
1582 if (c->lopt && strncmp(c->lopt, name, name_len) == 0 &&
1583 c->lopt[name_len] == '\0')
1584 return true;
1585 }
1586
1587 return false;
1588 }
1589
1590 for_each_arg(c, args) {
1591 if (c->opt && c->opt == token[1])
1592 return true;
1593 }
1594
1595 return false;
1596}
1597
1598static bool arg_parse_lopt(int *i)
1599{
1600 char *arg = argr.v[*i];
1601 char *name = arg + 2;
1602 char *value = strchr(name, '=');
1603 size_t name_len = value ? (size_t)(value - name) : strlen(name);
1604 if (value)
1605 value++;
1606
1607 struct argument *a = NULL;
1608 for_each_arg(c, args) {
1609 if (c->lopt && strncmp(c->lopt, name, name_len) == 0 &&
1610 c->lopt[name_len] == '\0') {
1611 a = c;
1612 break;
1613 }
1614 }
1615
1616 if (!a) {
1617 args_pe("Unknown: --%.*s\n", (int)name_len, name);
1618 return false;
1619 }
1620
1621 const char *str = NULL;
1622 if (a->param_req == ARG_PARAM_REQUIRED) {
1623 if (value) {
1624 str = value;
1625 } else if (*i + 1 < argr.c) {
1626 str = argr.v[++(*i)];
1627 } else {
1628 args_pe("--%s requires a parameter\n", a->lopt);
1629 return false;
1630 }
1631 } else if (a->param_req == ARG_PARAM_OPTIONAL) {
1632 if (value)
1633 str = value;
1634 else if (*i + 1 < argr.c && !arg_option(argr.v[*i + 1]))
1635 str = argr.v[++(*i)];
1636 } else {
1637 if (value) {
1638 args_pe("--%s does not take a parameter\n", a->lopt);
1639 return false;
1640 }
1641 }
1642
1643 return arg_process(a, str);
1644}
1645
1646static bool arg_parse_opt(int *i)
1647{
1648 char *arg = argr.v[*i];
1649 for (size_t j = 1; arg[j]; j++) {
1650 char opt = arg[j];
1651
1652 struct argument *a = NULL;
1653 for_each_arg(c, args) {
1654 if (c->opt == opt) {
1655 a = c;
1656 break;
1657 }
1658 }
1659
1660 if (!a) {
1661 args_pe("Unknown: -%c\n", opt);
1662 return false;
1663 }
1664
1665 const char *str = NULL;
1666 if (a->param_req == ARG_PARAM_REQUIRED) {
1667 if (arg[j + 1]) {
1668 str = arg + j + 1;
1669 j = strlen(arg);
1670 } else if (*i + 1 < argr.c) {
1671 str = argr.v[++(*i)];
1672 } else {
1673 args_pe("-%c requires a parameter\n", opt);
1674 return false;
1675 }
1676 } else if (a->param_req == ARG_PARAM_OPTIONAL) {
1677 if (arg[j + 1]) {
1678 str = arg + j + 1;
1679 j = strlen(arg);
1680 }
1681 }
1682
1683 if (!arg_process(a, str))
1684 return false;
1685 }
1686 return true;
1687}
1688
1689#define args_vaorder(list) \
1690 do { \
1691 for_each_arg(a, list) { \
1692 struct argument *order = a->list##_order; \
1693 if (order == NULL || order == (struct argument *)-1) \
1694 continue; \
1695 \
1696 bool found = false; \
1697 for_each_arg(check, list) { \
1698 if (check == order) { \
1699 found = true; \
1700 break; \
1701 } \
1702 } \
1703 \
1704 if (!found) { \
1705 args_pd("%s has invalid argument in " #list \
1706 "_order\n", \
1707 arg_str(a)); \
1708 args_pi(a); \
1709 args_abort(); \
1710 } \
1711 } \
1712 } while (0)
1713
1714#define args_reorder(list) \
1715 do { \
1716 struct argument *ordered = NULL; \
1717 struct argument *unordered = list; \
1718 list = NULL; \
1719 \
1720 struct argument **pp = &unordered; \
1721 while (*pp) { \
1722 struct argument *a = *pp; \
1723 if (a->list##_order == (struct argument *)-1) { \
1724 *pp = a->_.next_##list; \
1725 a->_.next_##list = ordered; \
1726 ordered = a; \
1727 } else { \
1728 pp = &(*pp)->_.next_##list; \
1729 } \
1730 } \
1731 \
1732 bool changed = true; \
1733 while (unordered && changed) { \
1734 changed = false; \
1735 pp = &unordered; \
1736 while (*pp) { \
1737 struct argument *a = *pp; \
1738 struct argument *ord = a->list##_order; \
1739 bool can_place = false; \
1740 struct argument **insert_pos = NULL; \
1741 \
1742 if (ord == NULL) { \
1743 can_place = true; \
1744 if (!ordered) { \
1745 insert_pos = &ordered; \
1746 } else { \
1747 struct argument *cur = \
1748 ordered; \
1749 while (cur->_.next_##list) \
1750 cur = cur->_.next_##list; \
1751 insert_pos = \
1752 &cur->_.next_##list; \
1753 } \
1754 } else { \
1755 struct argument **pord = &ordered; \
1756 while (*pord) { \
1757 if (*pord == ord) { \
1758 can_place = true; \
1759 insert_pos = \
1760 &(*pord)->_ \
1761 .next_##list; \
1762 break; \
1763 } \
1764 pord = &(*pord)->_.next_##list; \
1765 } \
1766 } \
1767 \
1768 if (can_place && insert_pos) { \
1769 *pp = a->_.next_##list; \
1770 a->_.next_##list = *insert_pos; \
1771 *insert_pos = a; \
1772 changed = true; \
1773 } else { \
1774 pp = &(*pp)->_.next_##list; \
1775 } \
1776 } \
1777 } \
1778 \
1779 if (unordered) { \
1780 if (!ordered) { \
1781 ordered = unordered; \
1782 } else { \
1783 struct argument *cur = ordered; \
1784 while (cur->_.next_##list) \
1785 cur = cur->_.next_##list; \
1786 cur->_.next_##list = unordered; \
1787 } \
1788 } \
1789 \
1790 list = ordered; \
1791 } while (0)
1792
1793bool args_parse(int argc, char *argv[])
1794{
1795 argr.c = argc;
1796 argr.v = argv;
1797
1798 args_vaorder(help);
1799 args_vaorder(validate);
1800 args_vaorder(action);
1801#undef args_vaorder
1802
1803 args_reorder(help);
1804 args_reorder(validate);
1805 args_reorder(action);
1806#undef args_reorder
1807
1808 bool success = true;
1809
1810 for (int i = 1; i < argr.c; i++) {
1811 char *arg = argr.v[i];
1812
1813 if (strcmp(arg, "--") == 0)
1814 break;
1815
1816 if (arg[0] != '-')
1817 continue;
1818
1819 if (arg[1] == '\0')
1820 continue;
1821
1822 if (arg[1] == '-') {
1823 if (!arg_parse_lopt(&i))
1824 success = false;
1825 } else {
1826 if (!arg_parse_opt(&i))
1827 success = false;
1828 }
1829 }
1830
1831 return success;
1832}
1833
1834bool args_validate(void)
1835{
1836 bool any_invalid = false;
1837
1838 for_each_arg(a, validate) {
1839 if (!a->_.valid) {
1840 args_pd("%s has internals pre-set\n", arg_str(a));
1841 args_pd("Please register arguments using ARGUMENT()\n");
1842 args_pi(a);
1843 args_abort();
1844 }
1845
1846 if (a->arg_req == ARG_REQUIRED && !*a->set) {
1847 bool any_conflict_set = false;
1848 for_each_rel(a, cons, con) {
1849 if (*con->set) {
1850 any_conflict_set = true;
1851 break;
1852 }
1853 }
1854 if (!any_conflict_set) {
1855 args_pe("Missing required argument: %s\n",
1856 arg_str(a));
1857 a->_.valid = false;
1858 any_invalid = true;
1859 }
1860 }
1861
1862 bool should_check_deps = false;
1863 switch (a->_.deps_phase) {
1864 case ARG_RELATION_PARSE:
1865 break;
1867 should_check_deps = true;
1868 break;
1870 should_check_deps = *a->set;
1871 break;
1873 should_check_deps = !*a->set;
1874 break;
1875 default:
1876 args_pd("Unknown dependency relation phase in %s\n",
1877 arg_str(a));
1878 args_pi(a);
1879 break;
1880 }
1881
1882 if (should_check_deps) {
1883 for_each_rel(a, deps, dep) {
1884 if (*dep->set)
1885 continue;
1886 args_pe("%s requires %s to be set\n",
1887 arg_str(a), arg_str(dep));
1888 any_invalid = true;
1889 }
1890 }
1891
1892 bool should_check_cons = false;
1893 switch (a->_.cons_phase) {
1894 case ARG_RELATION_PARSE:
1895 break;
1897 should_check_cons = true;
1898 break;
1900 should_check_cons = *a->set;
1901 break;
1903 should_check_cons = !*a->set;
1904 break;
1905 default:
1906 args_pd("Unknown conflict relation phase in %s\n",
1907 arg_str(a));
1908 args_pi(a);
1909 break;
1910 }
1911
1912 if (should_check_cons) {
1913 for_each_rel(a, cons, con) {
1914 if (!*con->set)
1915 continue;
1916 args_pe("%s conflicts with %s\n", arg_str(a),
1917 arg_str(con));
1918 any_invalid = true;
1919 }
1920 }
1921
1922 if (!a->validate_callback)
1923 continue;
1924
1925 bool should_validate = false;
1926 switch (a->validate_phase) {
1928 should_validate = true;
1929 break;
1931 should_validate = *a->set;
1932 break;
1934 should_validate = !*a->set;
1935 break;
1936 default:
1937 args_pd("Unknown .validate_phase in %s\n", arg_str(a));
1938 args_pi(a);
1939 args_abort();
1940 }
1941
1942 if (should_validate) {
1943 struct arg_callback ret = a->validate_callback();
1944 if (ret.error) {
1945 args_pe("%s: %s\n", arg_str(a), ret.error);
1946 any_invalid = true;
1947 a->_.valid = false;
1948 }
1949 }
1950 }
1951
1952 return !any_invalid;
1953}
1954
1955void args_actions(void)
1956{
1957 for_each_arg(a, action) {
1958 if (!a->action_callback)
1959 continue;
1960
1961 bool should_act = false;
1962 switch (a->action_phase) {
1964 should_act = true;
1965 break;
1967 should_act = *a->set;
1968 break;
1970 should_act = !*a->set;
1971 break;
1972 default:
1973 args_pd("Unknown .action_phase in %s\n", arg_str(a));
1974 args_pi(a);
1975 args_abort();
1976 }
1977
1978 if (should_act)
1979 a->action_callback();
1980 }
1981}
1982
1983static void arg_help_print(const struct argument *a)
1984{
1985 args_po("%*s%s", ARGS_STR_PREPAD, "", arg_str(a));
1986 if (a->param)
1987 args_po("%*s%s", ARGS_PARAM_OFFSET, "", a->param);
1988
1989 if (!a->help) {
1990 args_po("\n");
1991 return;
1992 }
1993
1994 size_t off = longest + ARGS_HELP_OFFSET;
1995 size_t pad = off > a->_.help_len ? off - a->_.help_len : 1;
1996
1997 bool first = true;
1998 const char *phelp = a->help;
1999 while (*phelp) {
2000 const char *nl = strchr(phelp, '\n');
2001 size_t line = nl ? (size_t)(nl - phelp) : strlen(phelp);
2002
2003 if (first) {
2004 args_po("%*s%.*s", (int)pad, "", (int)line, phelp);
2005 first = false;
2006 } else {
2007 args_po("\n%*s%.*s", (int)off, "", (int)line, phelp);
2008 }
2009
2010 if (!nl)
2011 break;
2012 phelp = nl + 1;
2013 }
2014
2015 args_po("\n");
2016}
2017
2018void args_help_print(const char *usage, const char *bin, const char *hint,
2019 const char *req, const char *opt)
2020{
2021 args_po("%s%s%s", usage, bin, hint);
2022
2023 bool first = true;
2024 for_each_arg(a, help) {
2025 if (a->arg_req == ARG_OPTIONAL || a->arg_req == ARG_HIDDEN)
2026 continue;
2027 if (first) {
2028 args_po("%s", req);
2029 first = false;
2030 }
2031 arg_help_print(a);
2032 }
2033
2034 first = true;
2035 for_each_arg(a, help) {
2036 if (a->arg_req != ARG_OPTIONAL)
2037 continue;
2038 if (first) {
2039 args_po("%s", opt);
2040 first = false;
2041 }
2042 arg_help_print(a);
2043 }
2044}
2045
2046#undef for_each_arg
2047#undef for_each_rel
2048
2049/** @} */
2050/** @endcond */
2051#endif /* ARGS_IMPLEMENTATION */
2052
2053/*
2054args.h
2055https://github.com/jakovdev/clix/
2056Copyright (c) 2026 Jakov Dragičević
2057Permission is hereby granted, free of charge, to any person obtaining a copy
2058of this software and associated documentation files (the "Software"), to deal
2059in the Software without restriction, including without limitation the rights
2060to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2061copies of the Software, and to permit persons to whom the Software is
2062furnished to do so, subject to the following conditions:
2063The above copyright notice and this permission notice shall be included in all
2064copies or substantial portions of the Software.
2065THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2066IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2067FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2068AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2069LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2070OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2071SOFTWARE.
2072*/
arg_callback_phase
Execution policy for argument::validate_phase and argument::action_phase.
Definition args.h:436
@ ARG_CALLBACK_IF_SET
Definition args.h:438
@ ARG_CALLBACK_ALWAYS
Definition args.h:437
@ ARG_CALLBACK_IF_UNSET
Definition args.h:439
arg_requirement
Requirement policy for argument::arg_req.
Definition args.h:170
bool args_validate(void)
Validates argument schema rules and user-provided combinations.
void args_actions(void)
Executes action callbacks for arguments matching action phase rules.
bool args_parse(int argc, char *argv[])
Parses command line arguments and runs parse-phase relations.
arg_parameter
Parameter requirement for argument::param_req.
Definition args.h:185
@ ARG_REQUIRED
Definition args.h:172
@ ARG_OPTIONAL
Definition args.h:171
@ ARG_SOMETIME
Definition args.h:174
@ ARG_HIDDEN
Definition args.h:173
@ ARG_PARAM_REQUIRED
Definition args.h:187
@ ARG_PARAM_OPTIONAL
Definition args.h:188
@ ARG_PARAM_NONE
Definition args.h:186
void args_help_print(const char *usage, const char *bin, const char *hint, const char *req, const char *opt)
Prints generated CLI help output.
#define args_pe(...)
Error print.
Definition args.h:1073
struct args_raw argr
Global storage of raw argc/argv values.
#define args_pd(...)
Developer-only debug print.
Definition args.h:1087
#define args_pi(arg)
Internal error print, user-facing dev print.
Definition args.h:1103
#define ARGS_STR_PREPAD
Pre-padding for help text.
Definition args.h:1007
#define ARGS_HELP_OFFSET
Offset from longest argument for help text.
Definition args.h:1027
#define args_abort()
Abort function.
Definition args.h:1115
#define ARGS_IMPLICIT_SETS
Maximum implicit allocations of argument::set booleans.
Definition args.h:1124
#define ARGS_PARAM_OFFSET
Offset between argument and parameter in help text.
Definition args.h:1017
#define args_po(...)
Normal print.
Definition args.h:1057
arg_relation_phase
Relation evaluation phase used by ARG_DEPENDS and ARG_CONFLICTS.
Definition args.h:643
#define ARG_SUBPASS
Special marker meaning "forward parent string to subset parser".
Definition args.h:634
@ ARG_RELATION_PARSE
Definition args.h:644
@ ARG_RELATION_VALIDATE_SET
Definition args.h:646
@ ARG_RELATION_VALIDATE_UNSET
Definition args.h:647
@ ARG_RELATION_VALIDATE_ALWAYS
Definition args.h:645
Result object returned by parser/validator callbacks.
Definition args.h:315
const char * error
Diagnostic user-facing message for failures, NULL on success.
Definition args.h:319
Copy of the raw process argument vector.
Definition args.h:341
int c
Definition args.h:342
char ** v
Definition args.h:343
Declarative schema object for one CLI argument.
Definition args.h:727
const char * help
Help description for this argument.
Definition args.h:915
struct argument * validate_order
Ordering for validation.
Definition args.h:887
char opt
Short option character (e.g. 'o' -> -o).
Definition args.h:931
const char * lopt
Long option name (e.g. "output" -> --output).
Definition args.h:926
struct arg_callback(* parse_callback)(const char *str, void *dest)
Function invoked during args_parse.
Definition args.h:798
enum arg_callback_phase action_phase
Controls when argument::action_callback runs.
Definition args.h:876
struct arg_callback(* validate_callback)(void)
Function invoked during args_validate.
Definition args.h:826
bool * set
Tracks whether the user supplied this argument.
Definition args.h:736
void * dest
Destination pointer passed as dest to parser callbacks.
Definition args.h:741
struct argument * action_order
Ordering for action execution.
Definition args.h:894
enum arg_callback_phase validate_phase
Controls when argument::validate_callback runs.
Definition args.h:870
void(* action_callback)(void)
Function invoked during args_actions.
Definition args.h:848
enum arg_requirement arg_req
Specifies whether this argument is required, optional, or hidden.
Definition args.h:858
struct argument * help_order
Ordering for help display.
Definition args.h:901
const char * param
Parameter name for help display (e.g., "N" for a number).
Definition args.h:921
enum arg_parameter param_req
Specifies whether this argument takes a parameter and if it's required.
Definition args.h:864