Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/docs/examples/advanced.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 7
title: 6. Advanced
sidebar_position: 8
title: 7. Advanced
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/bind.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 5
title: 4. Bind
sidebar_position: 6
title: 5. Bind
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/callback-entry-point.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 6
title: 5. Callback Entry Point
sidebar_position: 7
title: 6. Callback Entry Point
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/compiler.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 9
title: 9. Compiler Wrapper
sidebar_position: 11
title: 10. Compiler Wrapper
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/custom-action.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 7
title: 7. Custom Action
sidebar_position: 9
title: 8. Custom Action
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/distribution-and-platform.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 10
title: 10. Distribution and Platform Examples
sidebar_position: 12
title: 11. Distribution and Platform Examples
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/env-fallback.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 11
title: 11. Env Fallback
sidebar_position: 13
title: 12. Env Fallback
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/examples/git.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_position: 8
title: 8. Git
sidebar_position: 10
title: 9. Git
---

import {ExampleSource} from '@site/src/components/ExampleDoc';
Expand Down
18 changes: 10 additions & 8 deletions docs/docs/examples/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,23 @@ Each one is small enough to copy into a project, but specific enough to answer
Nested commands with their own help output and behavior.
3. [Validation](./validation)
Parse-time checks for numbers, names, and filesystem arguments.
4. [Bind](./bind)
4. [Relations](./relations)
Schema-level relationships such as groups, conflicts, and depends_on.
5. [Bind](./bind)
Write values directly into existing variables.
5. [Callback Entry Point](./callback-entry-point)
6. [Callback Entry Point](./callback-entry-point)
Fill application state during parsing, then call the real entry point.
6. [Advanced](./advanced)
7. [Advanced](./advanced)
Lower-level composition with explicit conversion, validation, and packing steps.
7. [Custom Action](./custom-action)
8. [Custom Action](./custom-action)
Full struct-based action definition with `accepts_input`, `after_type`, and `storage_type`.
8. [Git](./git)
9. [Git](./git)
A command-heavy CLI inspired by clap's git example, including nested subcommands.
9. [Compiler Wrapper](./compiler)
10. [Compiler Wrapper](./compiler)
Short-cluster flags, negatable key-value options, and `std::map` storage with `insert_or_assign`.
10. [Distribution and Platform Examples](./distribution-and-platform)
11. [Distribution and Platform Examples](./distribution-and-platform)
Single-header usage, C++20 modules, and the Windows Unicode sample used by CI.
11. [Env Fallback](./env-fallback)
12. [Env Fallback](./env-fallback)
Populate options from environment variables with the same conversion and validation pipeline.

## Building
Expand Down
53 changes: 53 additions & 0 deletions docs/docs/examples/relations.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
sidebar_position: 5
title: 4. Relations
---

import {ExampleSource} from '@site/src/components/ExampleDoc';

# Relations

This example shows schema-level relationships between options. Use this pattern
when the rule can be described from occurrence alone and should also appear in
generated help. Relation metadata is rendered inline on affected options, while
named groups get their own `Option groups:` entry.

## Source

<ExampleSource
example={{
sourcePath: 'examples/relations.cc',
}}
/>

## What This Example Shows

The example defines three static relations:

- `user` and `password` are a named `legacy_auth` group and must be provided together.
- `json` conflicts with `markdown`.
- the named `legacy_auth` group depends on `profile`.

The parser enforces these rules after normal option parsing and before the
program uses the parsed values.

## Try It

```bash
./build/examples/relations --json
./build/examples/relations --user alice --password secret --profile prod
./build/examples/relations --markdown --profile prod
```

Failure cases:

- `--user alice` fails because `password` is missing from the `legacy_auth` group.
- `--json --markdown` fails because the two output formats conflict.
- `--user alice --password secret` fails because `legacy_auth` depends on `profile`.
- `--help` shows relation metadata next to `--user`, `--password`, `--json`,
`--markdown`, and `--profile`.

## Related Examples

- [Validation](/docs/examples/validation)
- [Advanced](/docs/examples/advanced)
103 changes: 103 additions & 0 deletions docs/docs/relations.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
sidebar_position: 4
title: Relations
---

# Relations

Use `static constexpr auto relations` when a relationship between fields should
be part of the typed schema, generated help, and parse-time validation. The
help generator renders short relation metadata inline on affected options, and
named groups in an `Option groups:` section.

This is intentionally metadata, not a replacement for `constraints()`.
Relations cover simple occurrence-based rules that are independent of runtime
values. Keep `constraints()` for value-dependent logic such as "`--type foo`
requires `--foo-config`".

## API

```cpp
struct Args {
cli::StringOption<"user"> user;
cli::StringOption<"password"> password;
cli::Flag<"json"> json;
cli::Flag<"markdown"> markdown;
cli::StringOption<"profile"> profile;

static constexpr auto relations = cli::relations(
cli::group({.name = "legacy_auth", .members = {"user", "password"}}),
cli::conflicts("json", "markdown"),
cli::depends_on("legacy_auth", "profile"));
};
```

Names are written without the command-line prefix: use `"json"`, not
`"--json"`.

## Relation Types

- `cli::group({.name = "...", .members = {...}})` requires all members in the
named group to be provided together, or none of them.
- `cli::conflicts("a", "b")` rejects input when both operands are provided.
- `cli::depends_on("a", "b")` rejects input when `a` is provided but `b` is not.

All checks are based on `provided()`: defaults do not count as CLI occurrence.

## Named Groups

`group` creates a named group that can be reused as an operand in another
relation:

```cpp
static constexpr auto relations = cli::relations(
cli::group({.name = "legacy_auth", .members = {"user", "password"}}),
cli::depends_on("legacy_auth", "profile"));
```

This means `--user` and `--password` must appear together, and that legacy auth
requires `--profile`.

The generated help shows the rule where users look for it:

```text
--user <string> User name [requires: --profile]
--password <string> Password [requires: --profile]
--profile <string> Profile [required by: legacy_auth]

Option groups:
legacy_auth:
--user, --password must be used together
requires: --profile
```

## Well-Formed Schema Rules

The relation schema is checked at compile time as part of `ArgumentSpec`.

- Every field or group name used by a relation must exist.
- A `group` needs a non-empty unique name and at least two members.
- A group name must not collide with an option or command name.
- Each field may participate in only one direct relation.
- `conflicts("a", "a")` and `depends_on("a", "a")` are invalid.

These rules are deliberately strict. They keep help text and parse-time
validation unambiguous.

## Relation Metadata vs `constraints()`

Use relations when the rule is static and occurrence-based:

- `--json` conflicts with `--markdown`
- `legacy_auth` depends on `--profile`
- `--user` and `--password` must be provided together

Use `constraints()` when the rule depends on parsed values:

- `--type s3` requires `--bucket`
- `--mode fast` rejects `--exact`
- one numeric option must be greater than another

## Example

See [Relations Example](./examples/relations) for a complete program.
10 changes: 10 additions & 0 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@docusaurus/plugin-google-gtag": "^3.10.1",
"@docusaurus/preset-classic": "3.10.1",
"@mdx-js/react": "^3.0.0",
"ansi_up": "^6.0.6",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const sidebars: SidebarsConfig = {
'intro',
'tutorial',
'actions',
'relations',
{
type: 'category',
label: 'Examples',
Expand Down
14 changes: 13 additions & 1 deletion docs/src/components/ExamplePlayground.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import React, {useEffect, useRef, useState} from 'react';
import useBaseUrl from '@docusaurus/useBaseUrl';
import {AnsiUp} from 'ansi_up';

const ansi = new AnsiUp();
ansi.escape_html = true;

function ansiToHtml(text: string): string {
return ansi.ansi_to_html(text);
}

function splitArgs(input: string): string[] {
const args: string[] = [];
Expand Down Expand Up @@ -361,7 +369,11 @@ export function ExamplePlayground({
)}
</div>
{entry.output && (
<pre className="pg-output">{entry.output}</pre>
<pre
className="pg-output"
// biome-ignore lint/security/noDangerouslySetInnerHtml: ANSI output is escaped by ansi_up before rendering.
dangerouslySetInnerHTML={{__html: ansiToHtml(entry.output)}}
/>
)}
</div>
))}
Expand Down
36 changes: 36 additions & 0 deletions examples/relations.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <iostream>

#include "cli/argument.hh"
#include "cli/parser.hh"

struct Args {
cli::Description description{
"Relation metadata example covering groups, conflicts, and depends_on."};
cli::Help<> help;

cli::StringOption<"user"> user{{.help = "Legacy auth user name"}};
cli::StringOption<"password"> password{{.help = "Legacy auth password"}};

cli::Flag<"json"> json{{.help = "Write JSON output"}};
cli::Flag<"markdown"> markdown{{.help = "Write Markdown output"}};

cli::StringOption<"profile"> profile{{.help = "Deployment profile"}};

static constexpr auto relations = cli::relations(
cli::group({.name = "legacy_auth", .members = {"user", "password"}}),
cli::conflicts("json", "markdown"),
cli::depends_on("legacy_auth", "profile"));
};

auto main(int argc, char* argv[]) -> int {
const auto args = cli::parse_or_exit<Args>(argc, argv);

std::cout << "format: " << (args.json ? "json" : "markdown") << '\n';
if (args.user) {
std::cout << "legacy auth: " << *args.user << '\n';
}
if (args.profile) {
std::cout << "profile: " << *args.profile << '\n';
}
return 0;
}
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
npmDeps = pkgs.fetchNpmDeps {
name = "cli20-docs-0.0.0-npm-deps";
src = ./docs;
hash = "sha256-DOI2KP2qq8zbUjX3Kktgfe16n567pNkkGz5ABGSgSdo=";
hash = "sha256-ntnQTTWdih6fLx6QLqarpJQ6pIOdDWRxmysDQQVWswQ=";
};
nativeBuildInputs = [
pkgs.emscripten
Expand Down
Loading
Loading