Skip to main content

Git-like Tool

Subcommand-based tool demonstrating hierarchical commands, actions, and context sharing.

Complete Example​

vcs_tool.c
#include <argus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Shared application state
typedef struct {
sqlite3 *db; // Database connection
config_t *config; // Application configuration
log_file_t *logger; // Logging system
} app_context_t;

// Action handlers
int add_action(argus_t *argus, void *data);
int commit_action(argus_t *argus, void *data);
int push_action(argus_t *argus, void *data);
int status_action(argus_t *argus, void *data);

// Add command options
ARGUS_OPTIONS(
add_options,
HELP_OPTION(),
OPTION_FLAG('A', "all", HELP("Add all modified files")),
OPTION_FLAG('f', "force", HELP("Force add ignored files")),
OPTION_ARRAY_STRING('i', "include", HELP("Include specific patterns")),
POSITIONAL_STRING("files", HELP("Files to add"), FLAGS(FLAG_OPTIONAL)),
)

// Commit command options
ARGUS_OPTIONS(
commit_options,
HELP_OPTION(),
OPTION_STRING('m', "message", HELP("Commit message"), FLAGS(FLAG_REQUIRED)),
OPTION_STRING('\0', "author", HELP("Override author")),
OPTION_FLAG('\0', "amend", HELP("Amend previous commit")),
OPTION_FLAG('s', "signoff", HELP("Add signed-off-by line")),
)

// Push command options
ARGUS_OPTIONS(
push_options,
HELP_OPTION(),
OPTION_STRING('\0', "remote", HELP("Remote name"), DEFAULT("origin")),
OPTION_STRING('\0', "branch", HELP("Branch to push")),
OPTION_FLAG('f', "force", HELP("Force push")),
OPTION_FLAG('\0', "tags", HELP("Push tags too")),
OPTION_FLAG('n', "dry-run", HELP("Show what would be pushed")),
)

// Status command options
ARGUS_OPTIONS(
status_options,
HELP_OPTION(),
OPTION_FLAG('s', "short", HELP("Short format output")),
OPTION_FLAG('\0', "porcelain", HELP("Machine-readable output")),
)

// Main options with subcommands
ARGUS_OPTIONS(
options,
HELP_OPTION(),
VERSION_OPTION(),

// Global options
OPTION_FLAG('v', "verbose", HELP("Verbose output")),
OPTION_STRING('C', "directory", HELP("Run in directory"),
HINT("DIR"), DEFAULT(".")),

// Subcommands
SUBCOMMAND("add", add_options, HELP("Add files to staging"), ACTION(add_action)),
SUBCOMMAND("commit", commit_options, HELP("Record changes"), ACTION(commit_action)),
SUBCOMMAND("push", push_options, HELP("Push to remote"), ACTION(push_action)),
SUBCOMMAND("status", status_options, HELP("Show working tree status"), ACTION(status_action)),
)

int add_action(argus_t *argus, void *data)
{
app_context_t *ctx = (app_context_t *)data;

// Access subcommand options
bool add_all = argus_get(argus, "all").as_bool;
bool force = argus_get(argus, "force").as_bool;
const char *files = argus_get(argus, "files").as_string;

// Access global options
bool verbose = argus_get(argus, ".verbose").as_bool;
const char *repo_path = argus_get(argus, ".directory").as_string;

if (verbose) {
printf("Repository: %s\n", repo_path);
printf("Add command options:\n");
printf(" All files: %s\n", add_all ? "yes" : "no");
printf(" Force: %s\n", force ? "yes" : "no");
}

if (add_all) {
printf("Adding all modified files...\n");
// Simulate adding all files
printf("Added: file1.c file2.h docs/readme.md\n");
} else if (files) {
printf("Adding specific files: %s\n", files);
} else {
printf("No files specified. Use --all or specify files.\n");
return 1;
}

// Check include patterns
if (argus_is_set(argus, "include")) {
printf("Include patterns:\n");
argus_array_it_t it = argus_array_it(argus, "include");
while (argus_array_next(&it)) {
printf(" %s\n", it.value.as_string);
}
}

if (force) {
printf("Force adding ignored files...\n");
}

printf("Files staged for commit.\n");
return 0;
}

int commit_action(argus_t *argus, void *data)
{
app_context_t *ctx = (app_context_t *)data;

const char *message = argus_get(argus, "message").as_string;
const char *author = argus_get(argus, "author").as_string;
bool amend = argus_get(argus, "amend").as_bool;
bool signoff = argus_get(argus, "signoff").as_bool;

// Access global options
bool verbose = argus_get(argus, ".verbose").as_bool;
const char *repo_path = argus_get(argus, ".directory").as_string;

if (verbose) {
printf("Repository: %s\n", repo_path);
printf("Commit message: %s\n", message);
if (author) printf("Author override: %s\n", author);
printf("Amend: %s\n", amend ? "yes" : "no");
printf("Sign-off: %s\n", signoff ? "yes" : "no");
}

if (amend) {
printf("Amending previous commit...\n");
} else {
printf("Creating new commit...\n");
}

printf("Commit: abc1234 \"%s\"\n", message);

if (signoff) {
printf("Signed-off-by: Current User <user@example.com>\n");
}

return 0;
}

int push_action(argus_t *argus, void *data)
{
app_context_t *ctx = (app_context_t *)data;

const char *remote = argus_get(argus, "remote").as_string;
const char *branch = argus_get(argus, "branch").as_string;
bool force = argus_get(argus, "force").as_bool;
bool tags = argus_get(argus, "tags").as_bool;
bool dry_run = argus_get(argus, "dry-run").as_bool;

// Access global options
bool verbose = argus_get(argus, ".verbose").as_bool;
const char *repo_path = argus_get(argus, ".directory").as_string;

if (verbose) {
printf("Repository: %s\n", repo_path);
printf("Remote: %s\n", remote);
if (branch) printf("Branch: %s\n", branch);
}

if (dry_run) {
printf("Would push to %s", remote);
if (branch) printf("/%s", branch);
printf("\n");

if (tags) printf("Would also push tags\n");
if (force) printf("Would force push\n");
return 0;
}

printf("Pushing to %s", remote);
if (branch) printf("/%s", branch);
printf("...\n");

if (force) {
printf("Force pushing (this may overwrite remote changes)\n");
}

if (tags) {
printf("Pushing tags...\n");
}

printf("Push successful!\n");
return 0;
}

int status_action(argus_t *argus, void *data)
{
app_context_t *ctx = (app_context_t *)data;

bool short_format = argus_get(argus, "short").as_bool;
bool porcelain = argus_get(argus, "porcelain").as_bool;

// Access global options
bool verbose = argus_get(argus, ".verbose").as_bool;
const char *repo_path = argus_get(argus, ".directory").as_string;

if (verbose && !short_format && !porcelain) {
printf("Repository: %s\n", repo_path);
}

if (porcelain) {
// Machine-readable format
printf("M file1.c\n");
printf("A file2.h\n");
printf("?? untracked.txt\n");
} else if (short_format) {
printf(" M file1.c\n");
printf("A file2.h\n");
printf("?? untracked.txt\n");
} else {
printf("On branch main\n");
printf("Changes to be committed:\n");
printf(" (use \"vcs reset HEAD <file>...\" to unstage)\n\n");
printf("\tnew file: file2.h\n\n");

printf("Changes not staged for commit:\n");
printf(" (use \"vcs add <file>...\" to update what will be committed)\n\n");
printf("\tmodified: file1.c\n\n");

printf("Untracked files:\n");
printf(" (use \"vcs add <file>...\" to include in what will be committed)\n\n");
printf("\tuntracked.txt\n\n");
}

return 0;
}

int main(int argc, char **argv)
{
argus_t argus = argus_init(options, "vcs", "1.0.0");
argus.description = "A simple version control system";

if (argus_parse(&argus, argc, argv) != ARGUS_SUCCESS)
return 1;

// Create application context for shared resources
app_context_t context = {
.db = init_database("repo.db"),
.config = load_config("vcs.conf"),
.logger = open_log("vcs.log")
};

if (argus_has_command(&argus)) {
// Execute the parsed subcommand
int result = argus_exec(&argus, &context);
argus_free(&argus);
return result;
}
else {
// No subcommand provided
printf("No command specified.\n\n");
argus_print_usage(&argus);
argus_print_help(&argus);
argus_free(&argus);
return 1;
}
}

Usage Examples​

Basic Commands​

# Build
gcc vcs_tool.c -o vcs -largus

# Add files
./vcs add file1.c file2.h
./vcs add --all
./vcs add --force ignored_file.tmp

# Commit changes
./vcs commit --message "Add new features"
./vcs commit -m "Fix bug" --author "John Doe"
./vcs commit -m "Update docs" --signoff

# Push to remote
./vcs push
./vcs push --remote origin --branch feature/new
./vcs push --force --tags --dry-run

# Check status
./vcs status
./vcs status --short
./vcs status --porcelain

Global Options​

# Verbose output for any command
./vcs --verbose add file.c
./vcs -v commit -m "Verbose commit"

# Run in different directory
./vcs -C /path/to/repo status
./vcs --directory ../other-repo add --all

Help at Each Level​

# Main help
./vcs --help

# Subcommand help
./vcs add --help
./vcs commit --help
./vcs push --help

Generated Help​

Main Help​

vcs v1.0.0

A simple version control system

Usage: vcs [OPTIONS] COMMAND

Options:
-h, --help - Display this help message (exit)
-V, --version - Display version information (exit)
-v, --verbose - Verbose output
-C, --directory <DIR> - Run in directory (default: ".")

Commands:
add - Add files to staging
commit - Record changes
push - Push to remote
status - Show working tree status

Run 'vcs COMMAND --help' for more information on a command.

Subcommand Help​

$ ./vcs add --help
vcs v1.0.0

Usage: vcs add [OPTIONS] [files]

Add files to staging

Arguments:
[files] - Files to add (optional)

Options:
-h, --help - Display this help message (exit)
-A, --all - Add all modified files
-f, --force - Force add ignored files
-i, --include <STR,...> - Include specific patterns

$ ./vcs commit --help
vcs v1.0.0

Usage: vcs commit [OPTIONS]

Record changes

Options:
-h, --help - Display this help message (exit)
-m, --message <STR> - Commit message (required)
--author <STR> - Override author
--amend - Amend previous commit
-s, --signoff - Add signed-off-by line

Key Features Demonstrated​

Subcommand Structure​

  • Global options available to all commands (-v, -C)
  • Command-specific options for each subcommand
  • Action handlers that receive parsed arguments and shared context
  • Contextual help for each command level

Value Access Patterns​

// In action handlers:
bool verbose = argus_get(argus, ".verbose").as_bool; // Global option (dot prefix)
const char *msg = argus_get(argus, "message").as_string; // Local option (relative)

Context Sharing​

typedef struct {
bool verbose;
const char *repo_path;
} app_context_t;

// Pass to argus_exec()
argus_exec(&argus, &context);

Error Handling​

$ ./vcs commit
vcs: Required option is missing: 'message' with option 'commit'

$ ./vcs unknown-command
vcs: Unknown positional: 'unknown-command'

$ ./vcs
No command specified.
Try 'vcs COMMAND --help' for command-specific help.

Real-World Patterns​

This example demonstrates common CLI tool patterns:

  • Git-style command structure
  • Docker-style global + local options
  • Kubectl-style context passing
  • Npm-style hierarchical help