-
Notifications
You must be signed in to change notification settings - Fork 7
Flags
What this page covers: the --name value flag syntax on FinalCMD commands - the syntax a player
or admin types, the declarative @Arg.Flag pipeline a command author uses to bind it to a method
parameter, and the lower-level manual API (MultiArgumentos.getFlags()/getFlag(...)) for tokens no
declaration covers. Companion pages: Command Framework (the command itself) and
Argument Parsing (@Arg, the context mini-DSL, custom ArgParsers - flags reuse all of it).
A flag is a token starting with one or more -, followed by a name: --force, --page, -n. There
is no other supported form - the old -name:value syntax is gone.
-
A value flag consumes exactly the next token:
--page 2setspageto"2". -
--name=valueis the same thing, spelled with the value glued on:--page=2and--page 2are the identical flag with the identical value. The split happens at the FIRST=, so--title=a=bis the flagtitlewith valuea=b. -
A multi-word value needs quotes, single or double. Without quotes, only the immediately following token is taken and the rest stays positional. Quoting works the same on both spellings, so
--title='Title Message'is one value. These two cases come straight from the tests that pin the tokenizer (MultiArgumentosSystemTest):Case A (quoted): /dbroad Teste My Friend --title 'Title Message' -> flag title = "Title Message" Case B (unquoted): /dbroad Teste My Friend --title Title Message -> flag title = "Title" (only the next token) -> "Message" remains a positional argument -
A declared value flag never degrades to presence. Left with nothing to take - end of the line, another flag marker, the bare
--, or an=with nothing after it - it aborts the command naming itself, instead of handing its parser the invented token"true":The flag
[--page]needs a value:--page <value>Conversely, a declared presence flag (a
Booleanparameter) takes no value at all, so--force=falseis refused rather than read as "force is false":The flag
[--force]takes no value - write just--forceThe lower-level sniffed mode (
MultiArgumentos.getFlags(), below) still degrades to the literal string"true"in these cases: there is no declaration there to hold the spelling to. -
--alone ends flag scanning. It is removed from the input, and every token after it stays positional literally, even one that looks like a flag:set -- --forcetreats--forceas a plain positional argument, not a flag. This holds on every command, including one that declares no flag at all - the escape is not something a declaration switches on. Inside a variadic tail it is not needed and not consumed: the scan already stopped there, so a bare--is text like everything else. -
A negative number is never a flag.
-5and--5are left as positional arguments (the check is "dash(es) followed by a non-digit"). -
There is no short-flag stacking.
-fvis one flag namedfv, not-fplus-v. Grouping would collide with the single-dash multi-character aliases that already exist (-fo), and in a chat box the saving is a keystroke - so this is settled, not pending. -
A flag token only counts once something that reads it has been reached. Anywhere in the executable's own window is fine - before, between or after the positional arguments, and they are stripped out before the positional parser ever sees the line. A flag written while the path is still being walked is an error that says where the flag belongs:
Valid: /home Steve info myhouse --details Error: /home --details Steve info myhouseThe reason is mechanical, not stylistic: finding the leaf would need to know the arity of each flag, and knowing the arity needs the leaf. Refusing a flag nothing has claimed yet breaks the circle and keeps the traversal a single pass.
The exception is the node standing right there: if the node the traversal has reached already declares the flag - through its
@FinalCMD.Execute, or through an ancestor's capture - the path is over and the token belongs to that node's window. That is what makes a flag on a node with children typeable at all:/lp --verbosereaches the root's own method,/lp admin --detailsreachesadmin's@Execute. Declaring is a different matter - a node may declare flags, and the whole subtree below it recognizes them (see below). -
Lookup is dash-count- and case-insensitive:
force,-force,--FORCEand--Forceall resolve the same flag. -
A quote glued directly onto the name with no
=(e.g.--title'X') is part of the flag's name, not the start of a quoted value - it is a flag literally calledtitle'X'. Put a space or an=before an opening quote.
An unrecognized flag aborts the command. The message lists the flags that are available - all of the
mistyped markers at once, so one round of corrections is enough - filtered to the ones the sender could
have discovered anywhere else (permission granted, showOnUsage not turned off):
Unknown flag
[--froce, --paeg]! Available flags:--force, --page
The marker syntax belongs to the line, not to the declaration. The scan runs on every command,
including one that declares no @Arg.Flag at all - so -- is always consumed there too, and a marker
nothing reads is answered instead of quietly becoming a positional argument:
The flag
[--qualquer]means nothing here - write--before it to keep it as plain text
That is what the escape is for: /note -- --qualquer hands --qualquer to the positional argument
verbatim, whether or not the command has flags.
Put @Arg.Flag on a method parameter exactly like @Arg, except a flag parameter is never positional -
it can appear anywhere on the command line, and its presence is always optional. It nests under @Arg
because every source of a parameter's value does; a flag really does eat a word off the line, and its
value goes through the very same ArgParser a positional would.
public @interface Flag { // nested: br...annotations.Arg.Flag
String value(); // canonical spelling, always long form: "--force"
String[] aliases() default {}; // extra spellings, short or long: {"-f"}
String context() default ""; // same mini-DSL as @Arg, applied to the VALUE parser
Class<? extends ArgParser> parser() default ArgParser.class;
FCLocale[] locales() default {}; // hover description, same mechanism as @Arg
String def() default ""; // parsed by the value parser when the flag is absent
String permission() default ""; // required to USE the flag (not to see the command)
String usageName() default ""; // which declared spelling shows on the usage line
boolean showOnUsage() default true; // false hides it from usage+hover; tab/dispatch unaffected
}value() names the flag, so @Arg.Flag("--force") is the short spelling. Java allows it only while
value is the only element - add aliases, def or anything else and it becomes
@Arg.Flag(value = "--force", ...).
usageName() picks which of the declared spellings renders on the usage line; empty means the long
value(). The hover always lists every spelling, so the long form stays discoverable even when the line
shows a short one.
This is the shipped code (common/src/main/java/.../commands/misc/CMDECCooldown.java) - the fire-test
that collapsed the old 4-command matrix (set/setnetwork/setplayer/setplayernetwork) down to two:
@FinalCMD.SubCMD(subcmd = "set")
public void set(FCommandSender sender,
@Arg("<CooldownID>") String cooldownId,
@Arg("<duration>") String duration,
@Arg.Flag(value = "--network", aliases = "-n", def = "false", locales = {
@FCLocale(lang = LocaleType.EN_US, text = "Apply network-wide (follows the account across servers)."),
@FCLocale(lang = LocaleType.PT_BR, text = "Aplica na rede inteira (segue a conta entre servidores).")
})
Boolean network) {
Cooldown cooldown = network ? Cooldown.network(cooldownId) : Cooldown.of(cooldownId);
// ...
}/eccooldown set myid 5m sets a local cooldown; /eccooldown set myid 5m --network (or -n) sets a
network one. setPlayer carries the same --network/-n flag alongside its own <player> argument.
-
A flag parameter is never a primitive.
boolean forcefails registration; useBooleanso an absent flag can benull. This is checked fail-fast when the command registers, not at dispatch time. -
Booleanis the presence type: aridade 0, it never consumes the next token. Present ->TRUE. Absent ->null, unlessdef()says otherwise (def = "false"makes absenceFALSEinstead ofnull- seeCMDECCooldownabove). -
Every other type consumes exactly one token (or one quoted group): the flag's value is parsed by the same
ArgParsera positional@Argof that type would use - built-in or your ownparser = MyParser.classoverride, including thecontextmini-DSL (bounds, choices - see Argument Parsing). -
A flag is never required. There is no
requiredattribute - a mandatory flag is a positional argument in disguise. Handle absence yourself, typically by checking fornull(or relying ondef()). -
def()is parsed like a typed value, not stored raw, and it is parsed as a default: the text belongs to the command author, so adef()the value parser cannot read is reported as the command's own bug (logged, with the generic "not valid here" message naming the flag) instead of telling the sender that a word they never typed is invalid. This is the samefailedOnItsOwnDefaultprotection@Arg.def()gets - see Argument Parsing. -
A value the player actually typed always aborts the command on a parse failure, exactly like a bad value on a required positional - the command never runs with a bogus flag value. If the parser answers that the value is missing (an explicitly empty
--title '', say), the answer is the command's own usage line, exactly as for a positional - never a silent abort. -
A flag written twice is refused, not resolved. A flag holds one value, so
--page 1 --page 2is two answers to one question - and an alias counts as the same flag, so--page 1 -p 2is too. Neither value is taken:The flag
[--page]was written twice - it holds one value, so keep the one you meant -
The scan stops where a variadic tail begins. Everything from the tail's first token on is text - a flag marker inside it is the sender's own word, and so is the bare
--:/report Steve --force he said --force and left ^^^^^^^ the flag ^^^^^^^ part of the reasonSo a flag written for a command that ends in
<reason...>goes before the reason, exactly as in every CLI with trailing text. The upside is that the tail is handed to the method exactly as typed: nothing is ever removed from it, so nothing in it ever needs escaping.
These fail the command's registration outright (ArgMountException), so a bad @Arg.Flag never ships
silently:
- A primitive parameter type.
-
valuenot in the long form--name(no spaces, non-empty after the dashes). - An alias that is not a token the tokenizer would ever recognize as a flag marker: it needs one or
more leading
-, a name after them that does not start with a digit, and no whitespace."-"(dashes alone),"-5"(a negative number) and"-a b"all used to register a binding nothing could ever match. - A
usageNamethat is neither the flag's own name nor one of its aliases - the usage line can only show a spelling the sender may actually type. - The same normalized spelling (name or alias, dashes stripped, case-insensitive) claimed twice on the same method - by two different flags, or a flag whose alias collides with another flag's name.
- The same normalized spelling claimed twice along one path - a leaf and one of its ancestor nodes. The whole path shares one namespace and one extraction pass, so the deeper declaration would quietly win; the error names both declarations instead.
- Two of the four
@Arg...families on the same parameter -@Arg,@Arg.Flag,@Arg.Contextualand@Arg.NodeCapturedeach name a different place the value comes from, and a parameter has one source. Every pair is refused, not just@Arg+@Arg.Flag. - The declared name claimed twice on one method, across the three naming families (
@Arg,@Arg.Flag,@Arg.Contextual). A name is how a value is addressed once resolved, so two parameters cannot answer to one. Names are compared exactly as written, which is why--amountand<amount>never collide - nobody would type one meaning the other.
A @FinalCMD.Capture may declare @Arg.Flag parameters, and every leaf below that node recognizes them:
@FinalCMD.Node(subcmd = "duel")
public static class DuelNode {
@FinalCMD.Capture
public DuelArena capture(@Arg("<arena>") DuelArena arena,
@Arg.Flag(value = "--dry", aliases = "-d", def = "false") Boolean dry) { ... }
}
// /arena duel Colosseum spawn set 2 --dry -> the capture receives dry = trueOne extraction pass over the tail feeds the whole path, root first, so a flag declared four levels up costs the leaf nothing and shows up on its usage line and tab-complete like its own. That single namespace is exactly why guard 6 above exists.
permission() gates using the flag, not seeing the command: absent, it is never checked; present
without the permission, the sender gets the standard permission message and the command aborts (the
value, if any, is never parsed). Tab-complete additionally filters out flags the sender cannot use.
ParseCall.isFlagValue() tells a custom ArgParser whether it is currently resolving a flag's value
(true, including its def()) or a positional value (false) - the same parser class serves both call
sites, so this is the hook to branch on if a parser needs to behave differently for the two. It is
fixed for the whole call and never changes mid-parse.
A visible flag (showOnUsage() == true, the default) renders as a compact [--name] token on the
usage line, in declaration order, after the positional arguments - the value's shape (required-vs-not,
bounds) never appears there, only in the hover.
Unlike a plain @Arg, a flag with no locales() still gets its own hover block - the title alone
(with its aliases) is where a player reads every spelling at once:
[--network] <- usage line: ONE spelling - usageName(), or the long name
✯ [--network | -n] <- hover block title (always present for a visible flag)
● Apply network-wide (follows the account across servers). <- only when locales() is set
showOnUsage = false removes the flag from both the usage line and the hover block entirely, but
it keeps working normally: it still tab-completes and still dispatches. Use permission(), not
showOnUsage, to hide a flag from players who should not even know it exists (a showOnUsage=false
flag with no permission() is still suggested to everyone on tab).
Tab-complete understands flags in four situations:
-
The word being typed looks like a flag (
--,--ne, ...): suggests the declared names, filtered by permission, prefix, and flags already used earlier on the line. Each flag answers with ONE spelling: its long name when that matches what was typed, otherwise the first alias that does - so-n<TAB>completes to-n, which is the only moment a short alias can be discovered by typing. -
The word being typed already carries an
=(--page=<TAB>): the flag's own value parser answers, exactly as it would for the spaced form, and every suggestion comes back with the marker glued in front (--page=1). A completion replaces the whole word, so a bare value would have replaced the flag with it. -
The previous token is a declared value-flag: delegates to that flag's own value parser (so
--page <TAB>suggests that argument's own choices, exactly like a positional would). A marker that already took its value inline (--page=3) is not that: what follows it is a positional. - Otherwise: the raw token index is corrected for whatever flags (and their consumed values) sit earlier on the line, then falls through to the normal positional tab-complete - so a flag typed before a positional argument never throws off which positional is being completed. A marker the extraction could not take (a name nobody declared, a value flag with nothing to consume) stays on the line but is never counted as a positional either.
A token after a literal -- is never treated as a flag by tab-complete either, matching dispatch - and
so is every word from a variadic tail's first token on: once the tail has opened, flag names stop being
offered, because the dispatch is about to hand them over as plain text.
MultiArgumentos carries a flag API of its own, for tokens no declaration covers. It understands the
same --name value and --name=value syntax, but in sniffed mode: there is no declared arity, so a
value token that itself looks like a flag (or is --) makes the marker degrade to a presence flag
instead of consuming it, and nothing is ever reported back as an error - there is no declaration to
check the spelling against.
The window injected into a @FinalCMD method is not in that mode. The dispatch runs the declared
extraction over it on every command, so it is already flagified when the method receives it and
getFlags()/getFlag(...) answer exactly what the path declared - nothing more, because a marker
nobody declared never got that far: it was refused, with the -- escape offered. Sniffed mode is what
an instance nothing has scanned does - one you build yourself, the one sliceFrom(...) hands back, and
the one a variadic <rest...> delivers, since the scan stops where the tail begins and those tokens
arrive exactly as typed:
@FinalCMD.SubCMD(subcmd = "broadcast")
public void broadcast(FCommandSender sender, @Arg("<rest...>") MultiArgumentos rest) {
FlagedArgumento force = rest.getFlag("force"); // same lookup as "-force"/"--force"
if (force.isSet()) {
// ...
}
}getFlags()/getFlag(name) trigger flagification lazily (and idempotently) the first time either is
called - an instance nobody ever asks pays nothing for it. A missing flag returns
FlagedArgumento.EMPTY_ARG (isSet() == false), never null.
A single MultiArgumentos instance is flagified in exactly one mode: calling the declarative
extractDeclaredFlags(...) (what @Arg.Flag uses internally) on an instance and then calling
getFlags()/getFlag(...) on the SAME instance sees exactly what was already extracted - the two
modes are mutually exclusive per instance, not something you mix by hand.
The shape of the token decides it, and nothing else: one or more leading -, then a name whose
first character is not a digit. So -5 and --5 are numbers, and a token of dashes alone names nothing
at all (the bare -- is the escape, not a flag). No declaration switches that recognition on or off.
What the marker then means is the path's business: a spelling something between the root and the
target declared becomes that flag, and any other marker is refused. So an @Arg.Flag added to one
sub-command still cannot change how a sibling reads a dash-prefixed word - only a flag declared on a
node reaches further, and it reaches exactly its own subtree.
-
Command Framework -
@FinalCMD, sub-commands, the command registry, help and permissions. -
Argument Parsing -
@Arg, thecontextmini-DSL,def(), writing a customArgParser.
EverNifeCore · Home · made by Petrus Pradella
Getting Started
Commands & Text
Player Data & Storage
- PlayerData & PDSections
- Accounts
- Storage Backends
- Inline Backends for Plugins
- Legacy Data Migration
- Cooldowns
Config & Minecraft Systems
- Configuration
- Scheduler & Threading
- Items & NBT
- GUI Framework
- Integrations
- Economy
- Version Compatibility
Architecture & Reference