-
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 commands
that parse their own raw tokens. 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". -
A multi-word value needs quotes, single or double. Without quotes, only the immediately following token is taken and the rest stays positional. 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 flag with nothing after it (or followed by another flag/
--) is a presence flag: its value is the literal string"true". ABoolean-typed@Arg.Flagnever looks past this - see below. -
--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. -
A negative number is never a flag.
-5and--5are left as positional arguments (the check is "dash(es) followed by a non-digit"). -
A flag token only counts after the command path is complete. 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. But 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 the flag before the path ends breaks the circle and keeps the traversal a single pass. 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 (no separating space, 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'. Always put a space before an opening quote.
An unrecognized flag on a command that declares @Arg.Flag parameters aborts the command with a message
listing the flags that are available (filtered to the ones the sender has permission for):
Unknown flag
[--froce]! Available flags:--force
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. An unparseabledef()errors at registration/first-use instead of silently becomingnull- unlike@Arg.def(), whose internalArgInfois intentionallyOPTIONAL(see Argument Parsing), a flag's internalArgInfoisREQUIREDso a typo indef()is never swallowed. - 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.
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 does not start with at least one
-. - 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.
- Both
@Argand@Arg.Flagon the same parameter - a parameter is one or the other, never both. - 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 4 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 the only place a player can discover a short alias at all:
[--network | -n] <- usage line: name plus every alias, pipe-separated, when present
✯ [--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 three situations:
-
The word being typed looks like a flag (
--,--ne, ...): suggests the declared long names, filtered by permission, prefix, and flags already used earlier on the line. -
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). - 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 token after a literal -- is never treated as a flag by tab-complete either, matching dispatch.
Some commands parse their own raw tokens instead of declaring @Arg/@Arg.Flag - the escape hatch is
MultiArgumentos, injected as a contextual parameter. It understands the same --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.
@FinalCMD.SubCMD(subcmd = "set")
public void set(FCommandSender sender, MultiArgumentos argumentos, HelpLine helpLine) {
if (argumentos.emptyArgs(0)) { helpLine.sendTo(sender); return; }
FlagedArgumento force = argumentos.getFlag("force"); // same lookup as "-force"/"--force"
if (force.isSet()) {
// ...
}
}getFlags()/getFlag(name) trigger flagification lazily (and idempotently) the first time either is
called - a plain positional-only command that never touches them 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.
A token starting with - is only ever treated as a flag when:
- The method declares at least one
@Arg.Flagparameter (the declarative pipeline), or - The command calls
MultiArgumentos.getFlags()/getFlag(...)itself (the manual path).
A method with @Arg parameters and no @Arg.Flag never enters flag mode at all - a token like
--anything stays a plain positional argument, parsed like any other token would be. This is
deliberate: adding a flag to one sub-command can never change how an unrelated sub-command interprets
a dash-prefixed positional value.
-
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