Option Relations and Grouping¶
For CLIs that need cross-option validation, declare option relationships directly in the option spec so parsing and help stay aligned — no hand-written post-parse checks required.
(list
(cl-cli:make-option :name "profile"
:kind :value)
(cl-cli:make-option :name "config"
:kind :value
:requires '(:profile)
:description "Config file.")
(cl-cli:make-option :name "token"
:kind :value)
(cl-cli:make-option :name "password"
:kind :value
:conflicts-with '("token")))
Relation targets may be written either as option keys such as :profile or
as names such as "profile" / "--profile". They are evaluated after CLI
values, environment defaults, :config values, and literal defaults are
resolved — so a :default can satisfy a :requires target without the user
typing it.
An unsatisfied :requires signals cli-missing-dependent-option; a violated
:conflicts-with signals cli-conflicting-options. :requires is transitive
— if --a requires --b and --b requires --c, supplying --a demands
both. :conflicts-with is symmetric: declaring it on one side is enough.
Contradictory specs are rejected by make-app itself with
cli-invalid-specification — a :requires cycle, a requirement closure that
contains two conflicting options, an unknown or self-referential target, and a
:requires-any-of whose every alternative also conflicts with the option.
When a relation target points at a hidden option, parsing still honors the relation, but generated help omits that hidden target from public metadata and runtime relationship errors avoid printing the hidden option's name.
Requiring one of several alternatives¶
:requires demands every listed target. When only one of several
alternatives must be present — such as authenticating with a token or a
username and password — declare :requires-any-of instead:
Parsing signals cli-missing-any-of-options when none of the declared
alternatives are supplied, and help lists them as
requires one of: --token, --username.
Conditional requirements¶
For requirements that depend on other options, use :required-if (the
option becomes mandatory when any listed option is present) or
:required-unless (it is mandatory unless any listed option is present).
Both signal cli-missing-option-value and render in help as
required if: ... / required unless: ...:
(list
(cl-cli:make-option :name "profile" :kind :value)
(cl-cli:make-option :name "config" :kind :value :required-if '(:profile))
(cl-cli:make-option :name "token" :kind :value)
(cl-cli:make-option :name "user" :kind :value :required-unless '(:token)))
Exclusive and inclusive groups¶
To make a set of options mutually exclusive (at most one may be supplied),
splice them through cl-cli:exclusive-group instead of hand-writing every
pairwise :conflicts-with. Each member gains the others as conflicts, so
exclusivity reuses the same validation and hidden-target-safe error messages:
:global-options (cl-cli:exclusive-group
(cl-cli:make-option :name "json" :kind :flag)
(cl-cli:make-option :name "yaml" :kind :flag)
(cl-cli:make-option :name "table" :kind :flag))
Conflicts an option already declares are preserved, so a group member can
still conflict with options outside the group. Help annotates each member with
an at most one of: ... line naming the whole group, rather than repeating
every pairwise conflict.
When the choice is mandatory, use cl-cli:required-exclusive-group instead:
exclusivity is enforced as above, and parsing additionally signals
cli-missing-option-value (Exactly one of ...) when none of the members is
supplied — expressing an "exactly one of" obligation that pairwise
:conflicts-with cannot. Help renders those members as exactly one of: ....
For the opposite relationship — options meant to be used together, such as
a paired --host and --port — splice them through cl-cli:inclusive-group.
If any member is supplied, all must be (supplying none is fine); a partial
set signals cli-missing-dependent-option and help renders the members as
all or none of: ...:
:global-options (cl-cli:inclusive-group
(cl-cli:make-option :name "host" :kind :value)
(cl-cli:make-option :name "port" :kind :value))
Grouping in help output¶
Options accept a :group label too, which sections them under their own
heading in help (and appears in JSON), mirroring command
grouping: