Argus Documentation

website·Indexed 31 days ago

https://argus-lib.netlify.app/

A library for building command-line interfaces with declarative macros, featuring type-safe value access, automatic help generation, hierarchical subcommands, and a comprehensive validation system including built-in and custom validators.

Tokens
25.2K
Snippets
140
Records
150
Agent score
74%

What's inside Argus

  1. Overview of Argus declarative argument parsing

    Argus is a modern, declarative argument parsing library for C (C11 or newer) designed to replace manual getopt loops. It allows developers to describe desired CLI options once to automatically handle parsing, type conversion, validation, and help message generation.
  2. Implement a complex configuration tool using Argus

    This example demonstrates how to define a robust CLI configuration tool using the Argus library. It covers the use of ARGUS_OPTIONS to define various input types, including arrays for tags and ports, maps for environment variables and feature toggles, and required strings with environment variable fallbacks. It also showcases the use of validators for counts, numeric ranges, and email regex patterns.
    #include <argus.h>
    #include <stdio.h>
    
    ARGUS_OPTIONS(
        options,
        HELP_OPTION(),
        VERSION_OPTION(),
        
        // Arrays: Unique and sorted strings, count limited to 1-10
        OPTION_ARRAY_STRING('t', "tags", HELP("Resource tags"),
                            FLAGS(FLAG_UNIQUE | FLAG_SORTED),
                            VALIDATOR(V_COUNT(1, 10))),
        
        // Arrays: Integers with range support, count limited to 1-5
        OPTION_ARRAY_INT('p', "ports", HELP("Port numbers (supports ranges)"),
                         VALIDATOR(V_COUNT(1, 5))),
        
        // Maps: Key-value pairs with environment variable integration
        OPTION_MAP_STRING('e', "env", HELP("Environment variables"),
                          ENV_VAR("CONFIG_ENV"), FLAGS(FLAG_SORTED_KEY)),
        
        OPTION_MAP_BOOL('f', "features", HELP("Feature toggles"),
                        FLAGS(FLAG_SORTED_KEY)),
        
        // Required string with environment fallback
        OPTION_STRING('n', "name", HELP("Service name"),
                      ENV_VAR("SERVICE_NAME"), FLAGS(FLAG_REQUIRED)),
        
        // String with default value and environment fallback
        OPTION_STRING('h', "host", HELP("Host address"),
                      ENV_VAR("HOST"), DEFAULT("localhost")),
        
        // Integer with range validation and default
        OPTION_INT('P', "port", HELP("Main port"),
                   ENV_VAR("PORT"), VALIDATOR(V_RANGE(1, 65535)), DEFAULT(8080)),
        
        // Regex validation for email
        OPTION_STRING('c', "contact", HELP("Contact email"),
                      VALIDATOR(V_REGEX(ARGUS_RE_EMAIL))),
        
        // Optional positional argument
        POSITIONAL_STRING("output", HELP("Output file"), FLAGS(FLAG_OPTIONAL)),
    )
    
    int main(int argc, char **argv)
    {
        argus_t argus = argus_init(options, "config_tool", "2.0.0");
        argus.description = "Configuration management with collections and environment support";
        argus.env_prefix = "CONFIG"; // Prefix for all ENV_VAR lookups
        
        if (argus_parse(&argus, argc, argv) != ARGUS_SUCCESS)
            return 1;
        
        // Accessing values
        const char *name = argus_get(&argus, "name").as_string;
        int port = argus_get(&argus, "port").as_int;
        
        // Iterating through an array
        if (argus_is_set(&argus, "tags")) {
            argus_array_it_t it = argus_array_it(&argus, "tags");
            while (argus_array_next(&it))
                printf("Tag: %s\n", it.value.as_string);
        }
        
        argus_free(&argus);
        return 0;
    }
  3. Install Argus using vcpkg

    Argus can be installed via vcpkg. You can choose whether to include regex support, which requires the PCRE2 dependency.
    # Install with Regex support (requires PCRE2)
    vcpkg install argus[regex]
    
    # Install without Regex support (default)
    vcpkg install argus
  4. Share application context with Argus action handlers

    You can pass a custom data structure (context) to all action handlers by passing a pointer to that structure as the second argument of argus_exec. This is useful for sharing database connections, configuration, or loggers across different subcommands.
    typedef struct {
        sqlite3 *db;
        config_t *config;
    } app_context_t;
    
    int add_action(argus_t *argus, void *data) {
        app_context_t *ctx = (app_context_t *)data;
        // Use ctx->db or ctx->config here
        return 0;
    }
    
    int main(int argc, char **argv) {
        // ... init and parse argus ...
        app_context_t context = { .db = init_db() };
        
        if (argus_has_command(&argus)) {
            // Pass the context pointer to the action handler
            int result = argus_exec(&argus, &context);
            argus_free(&argus);
            return result;
        }
    }
  5. Use the Option Terminator (`--`) to Stop Parsing

    The double dash -- acts as an option terminator. Everything following the -- is treated as a positional argument, regardless of whether it starts with a dash. This is useful for files that start with dashes or when passing arguments to sub-processes.
    # Everything after -- is positional
    ./program --verbose -- --not-an-option file-with-dashes.txt
    ./program -v -- -x -y -z
  6. Combine multiple validators for a single option

    To apply multiple validation rules to one option, list them as comma-separated arguments within a single VALIDATOR() call. Do not use multiple VALIDATOR() calls for the same option.
    ARGUS_OPTIONS(
        options,
        HELP_OPTION(),
        // Value must be in range AND in the choice list
        OPTION_INT('p', "port", HELP("Common HTTP ports"),
                   VALIDATOR(V_RANGE(1, 65535), V_CHOICE_INT(80, 443, 8080, 8443))),
    
        // Value must meet length requirements AND match a regex
        OPTION_STRING('u', "username", HELP("Valid username"),
                      VALIDATOR(V_LENGTH(3, 20), V_REGEX(RE_USERNAME))),
    )
    
  7. Compare Argus with other C argument parsing libraries

    Depending on project requirements, different libraries are suitable for CLI parsing in C:

    • Argus: Best for developer productivity and reducing boilerplate. Requires C11+. Provides built-in validation and help generation.
    • getopt: Best for embedded systems or strict memory constraints where maximum control and minimal overhead are required. Requires manual validation and help text.
    • argp: The GNU standard, ideal for core GNU/Linux utilities and ecosystem compatibility.
    • argtable3: Focuses on type safety for legacy C compilers, though it lacks subcommands and has verbose initialization.
  8. Argus platform compatibility and requirements

    Argus supports Linux, macOS, and Windows.

    Critical Constraints:

    • Compiler: Requires GCC 13.0+ or Clang 14+.
    • Windows: MSVC is NOT supported. Use GCC 13+ via MSYS2/MinGW or Clang 14+.
    • Dependencies: Meson 1.1.0+ is required for building. Ninja is recommended.
  9. Argus installation and system requirements

    Argus is cross-platform (Linux, macOS, Windows) and can be installed via vcpkg, meson-wrap, or xmake-repo. It has zero mandatory dependencies, though PCRE2 is optional for those requiring regex validation.
  10. Combine multiple validators

    Multiple validators can be passed to the VALIDATOR() macro. They are executed in a specific sequence: Pre-validators (ORDER_PRE), then type conversion, then Post-validators (ORDER_POST), and finally built-in validations (like choices).
    ARGUS_OPTIONS(
        options,
        // Multiple validators: pre-validation, then post-validation
        OPTION_STRING('p', "password", HELP("Secure password"),
                     VALIDATOR(V_LENGTH(8, 128),           // Built-in
                              V_MIXED_CASE(),             // Custom pre-validator
                              V_NO_COMMON_PASSWORDS())),  // Custom post-validator
                              
        // Range + custom validation
        OPTION_INT('p', "port", HELP("Even port number"),
                  VALIDATOR(V_RANGE(1, 65535), V_EVEN())),
    )
    
  11. Implement context-aware cross-option validation

    Context-aware validation allows one option to be validated based on the value of another option. This is achieved by passing the name of the reference option via validator_data_t and retrieving its value using argus_get.
    // Validate that max > min
    int max_greater_than_min_validator(argus_t *argus, void *option_ptr, validator_data_t data)
    {
        argus_option_t *option = (argus_option_t *)option_ptr;
        const char *min_option = (const char *)data.custom;
        
        int max_value = option->value.as_int;
        int min_value = argus_get(argus, min_option).as_int;
        
        if (max_value <= min_value) {
            ARGUS_PARSING_ERROR(argus, "Max value %d must be greater than min value %d",
                             max_value, min_value);
            return ARGUS_ERROR_INVALID_VALUE;
        }
        
        return ARGUS_SUCCESS;
    }
    
    #define V_GREATER_THAN(option_name) \
        MAKE_VALIDATOR(max_greater_than_min_validator, NULL, _V_DATA_CUSTOM_(option_name), ORDER_POST)
    
    ARGUS_OPTIONS(
        options,
        OPTION_INT('m', "min", HELP("Minimum value")),
        OPTION_INT('M', "max", HELP("Maximum value"), 
                  VALIDATOR(V_GREATER_THAN("min"))),
    )
    
  12. Access and iterate over collection values

    Collection values can be accessed via direct array/map pointers or through iterator patterns for sequential processing.
    int main(int argc, char **argv)
    {
        argus_t argus = argus_init(options, "myapp", "1.0.0");
        argus_parse(&argus, argc, argv);
        
        // Direct Access for arrays
        if (argus_is_set(&argus, "names")) {
            size_t count = argus_count(&argus, "names");
            argus_value_t *names = argus_get(&argus, "names").as_array;
            for (size_t i = 0; i < count; i++)
                printf("  %zu: %s\n", i + 1, names[i].as_string);
        }
        
        // Direct Access for maps
        if (argus_is_set(&argus, "env")) {
            size_t count = argus_count(&argus, "env");
            argus_pair_t *env = argus_get(&argus, "env").as_map;
            for (size_t i = 0; i < count; i++)
                printf("  %s = %s\n", env[i].key, env[i].value.as_string);
        }
    
        // Iterator Pattern for arrays
        argus_array_it_t targets_it = argus_array_it(&argus, "targets");
        while (argus_array_next(&targets_it)) {
            printf("Building target: %s\n", targets_it.value.as_string);
        }
        
        // Iterator Pattern for maps
        argus_map_it_t features_it = argus_map_it(&argus, "features");
        while (argus_map_next(&features_it)) {
            printf("Feature: %s = %d\n", features_it.key, features_it.value.as_bool);
        }
        
        argus_free(&argus);
        return 0;
    }