Skip to content

Commit cdb3273

Browse files
Merge pull request #926 from tronprotocol/fix/output-formater-and-global-flag
feat(cli): allow known globals after command; query output + contract…
2 parents 847ed4d + df64324 commit cdb3273

11 files changed

Lines changed: 579 additions & 117 deletions

File tree

docs/standard-cli-contract-spec.md

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ The standard CLI has two distinct option layers:
5050

5151
### Global Options
5252

53-
Global options affect the overall execution environment before the command is known.
53+
Global options affect the overall execution environment for the whole command run.
5454

5555
Examples:
5656

@@ -61,7 +61,9 @@ Examples:
6161
- quiet / verbose behavior
6262
- global help / version handling
6363

64-
Global options are parsed by `GlobalOptions.parse(String[] args)`.
64+
Global options are parsed by `GlobalOptions.parse(String[] args)`. Execution modifiers may appear either before or
65+
after the command token. Top-level mode selectors are pre-command only, except for command-help handling described
66+
below.
6567

6668
### Command-Local Options
6769

@@ -77,28 +79,28 @@ Command-local options are parsed by `CommandDefinition.parseArgs(String[] args)`
7779

7880
### Layer Boundary
7981

80-
- global options configure how the CLI run is executed
82+
- global execution modifiers configure how the CLI run is executed
83+
- top-level mode selectors choose an alternate program mode before command execution
8184
- command-local options configure what the chosen command does
82-
- global parsing happens first
85+
- global parsing happens first and extracts known global options from the full argument list
8386
- command-local parsing happens only after the command token is known
8487
- a token must not be interpreted as both a global option and a command-local option in the same pass
8588

8689
## Contract 1: Global Option Parsing
8790

88-
`GlobalOptions.parse(String[] args)` is responsible only for parsing options that affect the whole run before
89-
the command is known.
91+
`GlobalOptions.parse(String[] args)` is responsible only for parsing known options that affect the whole run.
9092

9193
### Parsing Model
9294

93-
Global option parsing is a left-to-right first-pass scan.
95+
Global option parsing is a left-to-right first-pass scan across the full argument list.
9496

9597
The parser consumes tokens until one of these happens:
9698

97-
1. it reaches the first command token
98-
2. it reaches the end of input
99-
3. it encounters a malformed global option and fails with a usage error
99+
1. it reaches the end of input
100+
2. it encounters a malformed known global option and fails with a usage error
100101

101-
This pass is intentionally narrow. It must not inspect or reinterpret command-local options.
102+
This pass is intentionally narrow. It extracts only known global options and must not reinterpret unknown
103+
post-command options that belong to command-local parsing.
102104

103105
### Supported Syntax
104106

@@ -126,11 +128,15 @@ For Contract 1, this applies to valued global options only.
126128

127129
### Boundary Rules
128130

129-
- Global options are only recognized before the command token.
131+
- Execution modifier global options are recognized before and after the command token.
132+
- Top-level mode selectors `--version` and `--interactive` are recognized only before the command token.
130133
- The first token before command resolution that does not begin with `-` is the command token.
131134
- The command token is normalized to lowercase for registry lookup.
132-
- After the command token is found, all remaining tokens are passed through unchanged as command arguments.
133-
- Tokens after the command token are never reinterpreted as global options, even if they look like global flags.
135+
- After the command token is found, execution modifier global options are extracted and all other tokens are passed
136+
through unchanged as command arguments.
137+
- Unknown options after the command token are never reinterpreted as global options.
138+
- Post-command `--help` and `-h` are reserved for command help and are passed through as command arguments.
139+
- Post-command `--version` and `--interactive` are command-local tokens and are passed through as command arguments.
134140

135141
Examples:
136142

@@ -143,13 +149,29 @@ Examples:
143149
- command: `get-balance`
144150
- command args: `--address T...`
145151
- `wallet-cli get-balance --network nile`
152+
- global options: `--network nile`
146153
- command: `get-balance`
147-
- command args: `--network nile`
148-
- `--network` is not treated as a global option because it appears after the command token
154+
- command args: none
149155
- `wallet-cli get-balance --network=nile`
156+
- global options: `--network=nile`
150157
- command: `get-balance`
151-
- command args: `--network=nile`
152-
- `--network=nile` is not treated as a global option because it appears after the command token
158+
- command args: none
159+
- `wallet-cli get-balance --address T... --network nile`
160+
- global options: `--network nile`
161+
- command: `get-balance`
162+
- command args: `--address T...`
163+
- `wallet-cli get-balance --help`
164+
- command: `get-balance`
165+
- command args: `--help`
166+
- `--help` is not treated as global help because it appears after the command token
167+
- `wallet-cli get-balance --version`
168+
- command: `get-balance`
169+
- command args: `--version`
170+
- `--version` is not treated as global version because it appears after the command token
171+
- `wallet-cli get-balance --interactive`
172+
- command: `get-balance`
173+
- command args: `--interactive`
174+
- `--interactive` is not treated as global interactive mode because it appears after the command token
153175

154176
### No-Command Cases
155177

@@ -196,7 +218,7 @@ The following are usage errors at the global parsing layer:
196218
- unknown global option before the command token
197219
- missing value for a valued global option
198220
- invalid value for a constrained global option
199-
- malformed global syntax before the command token
221+
- malformed syntax for a known global option
200222

201223
Specific expectations:
202224

@@ -216,6 +238,10 @@ Specific expectations:
216238
- usage error: missing or empty value for `--output`
217239
- `--quiet=true`
218240
- usage error: option `--quiet` does not take a value
241+
- `get-balance --network beta`
242+
- usage error: invalid value for `--network`
243+
- `get-balance --unknown value`
244+
- not a global parsing error; `--unknown value` remains command-local input
219245

220246
Error classification for valued global options:
221247

qa/contracts.tsv

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ contract__global_invalid_network_value_json|empty|none|default|json|usage|["--ou
1212
contract__global_flag_with_value_json|empty|none|default|json|usage|["--output","json","--quiet=true","current-network"]|||usage_error|||
1313
contract__global_repeated_network_json|empty|none|default|json|usage|["--output","json","--network","nile","--network","main","current-network"]|||usage_error|||
1414
contract__global_quiet_verbose_conflict_json|empty|none|default|json|usage|["--output","json","--quiet","--verbose","current-network"]|||usage_error|||
15-
contract__post_command_global_like_token_text|empty|none|default|text|usage|["current-network","--network","nile"]||||Unknown option: --network||
15+
contract__post_command_global_network_text|empty|none|default|text|success|["current-network","--network","nile"]||||Current network:||
16+
contract__post_command_unknown_option_text|empty|none|default|text|usage|["current-network","--unknown","value"]||||Unknown option: --unknown||
17+
contract__post_command_version_text|empty|none|default|text|usage|["current-network","--version"]||||Unknown option: --version||
18+
contract__post_command_interactive_text|empty|none|default|text|usage|["current-network","--interactive"]||||Unknown option: --interactive||
1619
contract__command_missing_required_value_text|empty|none|default|text|usage|["get-block-by-latest-num","--count"]||||Missing value for --count||
1720
contract__command_empty_required_value_json|empty|none|default|json|usage|["--output","json","get-block-by-latest-num","--count="]|||usage_error|||
1821
contract__command_boolean_flag_exec|empty|none|default|text|execution|["freeze-balance-v2","--amount","1","--multi"]|||execution_error|Error:||

src/main/java/org/tron/walletcli/Client.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4798,11 +4798,9 @@ static boolean shouldLaunchInteractiveByDefault(String[] args, GlobalOptions glo
47984798
}
47994799

48004800
private static boolean requestsJsonOutput(String[] args) {
4801+
// Fallback scan for parse errors assumes --output is a standard-CLI global-only option.
48014802
for (int i = 0; i < args.length; i++) {
48024803
String token = args[i];
4803-
if (!token.startsWith("-")) {
4804-
return false;
4805-
}
48064804
if ("--output".equals(token)) {
48074805
return i + 1 < args.length && "json".equalsIgnoreCase(args[i + 1]);
48084806
}

src/main/java/org/tron/walletcli/cli/GlobalOptions.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.tron.walletcli.cli;
22

3-
import java.util.Arrays;
3+
import java.util.ArrayList;
4+
import java.util.List;
45
import java.util.Locale;
56

67
public class GlobalOptions {
@@ -41,39 +42,61 @@ public static GlobalOptions parse(String[] args) {
4142
boolean networkSeen = false;
4243
boolean walletSeen = false;
4344
boolean grpcEndpointSeen = false;
45+
List<String> commandArgs = new ArrayList<String>();
4446
for (int i = 0; i < args.length; i++) {
4547
String token = args[i];
48+
boolean commandSeen = opts.command != null;
4649

47-
if (!token.startsWith("-")) {
50+
if (!commandSeen && !token.startsWith("-")) {
4851
opts.command = token.toLowerCase(Locale.ROOT);
49-
opts.commandArgs = Arrays.copyOfRange(args, i + 1, args.length);
50-
return opts;
52+
continue;
5153
}
5254

5355
if ("-h".equals(token)) {
54-
opts.help = true;
56+
if (commandSeen) {
57+
commandArgs.add(token);
58+
} else {
59+
opts.help = true;
60+
}
5561
continue;
5662
}
5763

58-
if (token.startsWith("-h=")) {
64+
if (!commandSeen && token.startsWith("-h=")) {
5965
throw new CliUsageException("Option -h does not take a value");
6066
}
6167

6268
if (!token.startsWith("--")) {
63-
throw new CliUsageException("Unknown global option: " + token);
69+
if (commandSeen) {
70+
commandArgs.add(token);
71+
} else {
72+
throw new CliUsageException("Unknown global option: " + token);
73+
}
74+
continue;
6475
}
6576

6677
ParsedLongOption parsed = parseLongOptionToken(token);
6778
switch (parsed.name) {
6879
case "interactive":
80+
if (commandSeen) {
81+
commandArgs.add(token);
82+
break;
83+
}
6984
ensureNoInlineValue(parsed, "--interactive");
7085
opts.interactive = true;
7186
break;
7287
case "help":
88+
if (commandSeen) {
89+
commandArgs.add(token);
90+
break;
91+
}
7392
ensureNoInlineValue(parsed, "--help");
7493
opts.help = true;
7594
break;
7695
case "version":
96+
if (commandSeen) {
97+
commandArgs.add(token);
98+
break;
99+
}
77100
ensureNoInlineValue(parsed, "--version");
78101
opts.version = true;
79102
break;
@@ -124,9 +147,14 @@ public static GlobalOptions parse(String[] args) {
124147
}
125148
break;
126149
default:
127-
throw new CliUsageException("Unknown global option: --" + parsed.name);
150+
if (commandSeen) {
151+
commandArgs.add(token);
152+
} else {
153+
throw new CliUsageException("Unknown global option: --" + parsed.name);
154+
}
128155
}
129156
}
157+
opts.commandArgs = commandArgs.toArray(new String[0]);
130158
return opts;
131159
}
132160

@@ -154,6 +182,7 @@ private static String requireValue(String[] args, int optionIndex, ParsedLongOpt
154182
if (valueIndex >= args.length || args[valueIndex].startsWith("-")) {
155183
throw new CliUsageException("Missing value for " + optionName);
156184
}
185+
// Valued globals intentionally consume the next non-flag token greedily; command names are not special here.
157186
return args[valueIndex];
158187
}
159188

0 commit comments

Comments
 (0)