Skip to content

Commit 3633166

Browse files
committed
Even more work on resolving #255...
1 parent df7d3cd commit 3633166

File tree

13 files changed

+1517
-93
lines changed

13 files changed

+1517
-93
lines changed

src/main/java/com/dtsx/astra/cli/AstraCli.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ public static int run(Ref<CliContext> ctxRef, String... args) {
200200
});
201201

202202
val allArgs = ArrayUtils.addAll(defaultArgs(), args);
203+
203204
return cmd.execute(allArgs);
204205
}
205206

src/main/java/com/dtsx/astra/cli/commands/AbstractCmd.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void initCtx(Ref<CliContext> ctxRef) {
5454
}
5555

5656
@ArgGroup(validate = false, heading = "%nCommon Options:%n", order = 99)
57-
public CommonOptions common = new CommonOptions();
57+
public CommonOptions common = CommonOptions.EMPTY;
5858

5959
protected OutputAll execute(Supplier<OpRes> _result) {
6060
val otherTypes = Arrays.stream(OutputType.values()).filter(o -> o != ctx.outputType()).map(o -> o.name().toLowerCase()).toList();
@@ -105,6 +105,8 @@ public final void run() {
105105
throw new CongratsYouFoundABugException("initCtx(...) was not called before run()");
106106
}
107107

108+
val common = mergeCommonOptions();
109+
108110
val ansi = common.ansi().orElse(
109111
(common.outputType().isHuman())
110112
? ctx.colors().ansi()
@@ -133,6 +135,18 @@ public final void run() {
133135
));
134136
}
135137

138+
private CommonOptions mergeCommonOptions() {
139+
var common = this.common;
140+
141+
for (var spec = this.spec.parent(); spec != null; spec = spec.parent()) {
142+
if (spec.userObject() instanceof AbstractCmd<?> cmd) {
143+
common = common.merge(cmd.common);
144+
}
145+
}
146+
147+
return common;
148+
}
149+
136150
@VisibleForTesting
137151
public final void run(CliContext ctx) {
138152
ctxRef.modify((_) -> ctx);

src/main/java/com/dtsx/astra/cli/commands/AbstractConnectedCmd.java

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44
import com.dtsx.astra.cli.commands.AbstractConnectedCmd.ProfileSource.DefaultFile;
55
import com.dtsx.astra.cli.commands.AbstractConnectedCmd.ProfileSource.Forced;
66
import com.dtsx.astra.cli.commands.AbstractConnectedCmd.ProfileSource.FromArgs;
7-
import com.dtsx.astra.cli.core.CliConstants.$ConfigFile;
8-
import com.dtsx.astra.cli.core.CliConstants.$Env;
9-
import com.dtsx.astra.cli.core.CliConstants.$Profile;
10-
import com.dtsx.astra.cli.core.CliConstants.$Token;
11-
import com.dtsx.astra.cli.core.completions.impls.AstraEnvCompletion;
12-
import com.dtsx.astra.cli.core.completions.impls.AvailableProfilesCompletion;
137
import com.dtsx.astra.cli.core.config.AstraConfig;
148
import com.dtsx.astra.cli.core.config.Profile;
159
import com.dtsx.astra.cli.core.config.ProfileName;
@@ -20,9 +14,9 @@
2014
import com.dtsx.astra.sdk.utils.AstraEnvironment;
2115
import lombok.val;
2216
import org.apache.commons.lang3.tuple.Pair;
17+
import org.jetbrains.annotations.MustBeInvokedByOverriders;
2318
import org.jetbrains.annotations.Nullable;
2419
import picocli.CommandLine.ArgGroup;
25-
import picocli.CommandLine.Option;
2620

2721
import java.nio.file.Path;
2822
import java.util.ArrayList;
@@ -34,50 +28,7 @@
3428

3529
public abstract class AbstractConnectedCmd<OpRes> extends AbstractCmd<OpRes> {
3630
@ArgGroup(heading = "%nConnection Options:%n", order = 100)
37-
private @Nullable CredsProvider $credsProvider;
38-
39-
public static class CredsProvider {
40-
@ArgGroup(exclusive = false)
41-
public @Nullable ConfigSpec $config;
42-
43-
@ArgGroup(exclusive = false)
44-
public @Nullable CredsSpec $creds;
45-
}
46-
47-
public static class CredsSpec {
48-
@Option(
49-
names = { $Token.LONG },
50-
description = "The astra token to use for this command. Use the @|code --token @file|@ syntax to read the token from a file, to avoid potential leaks.",
51-
paramLabel = $Token.LABEL,
52-
required = true
53-
)
54-
public AstraToken $token;
55-
56-
@Option(
57-
names = { $Env.LONG },
58-
completionCandidates = AstraEnvCompletion.class,
59-
description = "Override the target astra environment",
60-
paramLabel = $Env.LABEL
61-
)
62-
private Optional<AstraEnvironment> $env;
63-
}
64-
65-
public static class ConfigSpec {
66-
@Option(
67-
names = { $ConfigFile.LONG, $ConfigFile.SHORT },
68-
description = { "The @|code .astrarc|@ file to use for this command", SHOW_CUSTOM_DEFAULT + "${cli.rc-file.path}" },
69-
paramLabel = $ConfigFile.LABEL
70-
)
71-
private Optional<Path> $configFile;
72-
73-
@Option(
74-
names = { $Profile.LONG, $Profile.SHORT },
75-
completionCandidates = AvailableProfilesCompletion.class,
76-
description = "The @|code .astrarc|@ profile to use for this command. Can be set via @|code " + ConstEnvVars.PROFILE + "|@.",
77-
paramLabel = $Profile.LABEL
78-
)
79-
public Optional<ProfileName> $profileName;
80-
}
31+
private ConnectionOptions $connOpts = ConnectionOptions.EMPTY;
8132

8233
private @Nullable ProfileSource cachedProfileSource;
8334
private @Nullable Profile cachedProfile;
@@ -89,6 +40,24 @@ record CustomFile(Path path, ProfileName profile) implements ProfileSource {}
8940
record DefaultFile(ProfileName profile) implements ProfileSource {}
9041
}
9142

43+
@MustBeInvokedByOverriders
44+
protected void prelude() {
45+
super.prelude();
46+
$connOpts = mergeConnectionOptions();
47+
}
48+
49+
private ConnectionOptions mergeConnectionOptions() {
50+
var opts = $connOpts;
51+
52+
for (var spec = this.spec.parent(); spec != null; spec = spec.parent()) {
53+
if (spec.userObject() instanceof AbstractConnectedCmd<?> cmd) {
54+
opts = opts.merge(cmd.$connOpts);
55+
}
56+
}
57+
58+
return opts;
59+
}
60+
9261
public final Profile profile() {
9362
if (cachedProfile != null) {
9463
return cachedProfile;
@@ -115,21 +84,21 @@ private ProfileSource profileSource() {
11584
return new Forced(ctx.forceProfileForTesting().get());
11685
}
11786

118-
if ($credsProvider != null && $credsProvider.$creds != null) {
119-
return new FromArgs($credsProvider.$creds.$token, $credsProvider.$creds.$env);
87+
if ($connOpts.$creds != null) {
88+
return new FromArgs($connOpts.$creds.$token, $connOpts.$creds.$env);
12089
}
12190

12291
val defaultFilePath = AstraConfig.resolveDefaultAstraConfigFile(ctx);
12392

124-
if ($credsProvider != null && $credsProvider.$config != null) {
125-
val configFile = $credsProvider.$config.$configFile;
93+
if ($connOpts.$config != null) {
94+
val configFile = $connOpts.$config.$configFile;
12695

12796
val defaultProfileName = Optional.ofNullable(System.getenv(ConstEnvVars.PROFILE))
12897
.map(ProfileName::parse)
12998
.map((p) -> p.getRight((e) -> new AstraCliException(PARSE_ISSUE, "Invalid profile name in " + ConstEnvVars.PROFILE + ": '" + e + "'")))
13099
.orElse(ProfileName.DEFAULT);
131100

132-
val profileName = $credsProvider.$config.$profileName
101+
val profileName = $connOpts.$config.$profileName
133102
.orElse(defaultProfileName);
134103

135104
// the check for if it's the default file is later used to decide whether to update the completions cache or not

src/main/java/com/dtsx/astra/cli/commands/CommonOptions.java

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.dtsx.astra.cli.commands;
22

33
import com.dtsx.astra.cli.core.completions.impls.OutputTypeCompletion;
4-
import com.dtsx.astra.cli.core.exceptions.internal.cli.OptionValidationException;
54
import com.dtsx.astra.cli.core.mixins.HelpMixin;
65
import com.dtsx.astra.cli.core.output.formats.OutputType;
7-
import lombok.Getter;
6+
import lombok.AllArgsConstructor;
7+
import lombok.NoArgsConstructor;
88
import lombok.experimental.Accessors;
99
import picocli.CommandLine.Help.Ansi;
1010
import picocli.CommandLine.Help.Visibility;
@@ -16,12 +16,15 @@
1616
import static com.dtsx.astra.cli.commands.AbstractCmd.SHOW_CUSTOM_DEFAULT;
1717

1818
@Accessors(fluent = true)
19+
@AllArgsConstructor
20+
@NoArgsConstructor
1921
public class CommonOptions extends HelpMixin { // I don't like extending here but mixins don't compose w/ arg groups :(
20-
@Getter
21-
private Optional<Ansi> ansi = Optional.empty();
22+
public static CommonOptions EMPTY = new CommonOptions();
2223

2324
public enum ColorMode { auto, never, always }
2425

26+
private Optional<Ansi> ansi = Optional.empty();
27+
2528
@Option(
2629
names = "--color",
2730
description = { "One of: ${COMPLETION-CANDIDATES}", SHOW_CUSTOM_DEFAULT + "auto" },
@@ -48,42 +51,35 @@ private void setAnsi(boolean noColor) {
4851
@Option(
4952
names = { "--output", "-o" },
5053
completionCandidates = OutputTypeCompletion.class,
51-
defaultValue = "human",
52-
description = "One of: ${COMPLETION-CANDIDATES}",
54+
description = { "One of: ${COMPLETION-CANDIDATES}", SHOW_CUSTOM_DEFAULT + "human" },
5355
paramLabel = "FORMAT"
5456
)
55-
@Getter
56-
private OutputType outputType;
57+
private Optional<OutputType> outputType = Optional.empty();
5758

58-
@Getter
5959
@Option(
6060
names = { "-V", "--verbose" },
6161
description = "Enable verbose logging output",
6262
showDefaultValue = Visibility.NEVER
6363
)
64-
private boolean verbose;
64+
private Optional<Boolean> verbose = Optional.empty();
6565

66-
@Getter
6766
@Option(
6867
names = { "-q", "--quiet" },
6968
description = "Only output essential information",
7069
showDefaultValue = Visibility.NEVER
7170
)
72-
private boolean quiet;
71+
private Optional<Boolean> quiet = Optional.empty();
7372

74-
@Getter
7573
@Option(
7674
names = { "--spinner" },
7775
description = { "Enable/disable loading spinners", SHOW_CUSTOM_DEFAULT + "enabled if tty and not quiet" },
7876
negatable = true,
7977
fallbackValue = "true"
8078
)
81-
private Optional<Boolean> enableSpinner;
79+
private Optional<Boolean> enableSpinner = Optional.empty();
8280

83-
@Getter
84-
private boolean shouldDumpLogs = false;
81+
private Optional<Boolean> shouldDumpLogs = Optional.empty();
8582

86-
@Getter
8783
private Optional<Path> dumpLogsTo = Optional.empty();
8884

8985
@Option(
@@ -96,10 +92,10 @@ private void setAnsi(boolean noColor) {
9692
private void setDumpLogs(Optional<Path> dest) {
9793
dest.ifPresent((path) -> {
9894
if (path.toString().equalsIgnoreCase("false")) {
99-
shouldDumpLogs = false;
95+
shouldDumpLogs = Optional.of(false);
10096
dumpLogsTo = Optional.empty();
10197
} else {
102-
shouldDumpLogs = true;
98+
shouldDumpLogs = Optional.of(true);
10399

104100
if (!path.toString().equalsIgnoreCase("__fallback__")) {
105101
dumpLogsTo = Optional.of(path);
@@ -113,18 +109,65 @@ private void setDumpLogs(Optional<Path> dest) {
113109
hidden = true
114110
)
115111
private void setDumpLogs(boolean noDumpLogs) {
116-
if (noDumpLogs) {
117-
shouldDumpLogs = false;
118-
} else {
119-
throw new OptionValidationException("--no-dump-logs", "--no-dump-logs must be called without a value (or with 'true'); use --dump-logs[=FILE] instead");
120-
}
112+
shouldDumpLogs = Optional.of(!noDumpLogs);
113+
dumpLogsTo = Optional.empty();
121114
}
122115

123-
@Getter
124116
@Option(
125117
names = "--no-input",
126118
description = "Don't ask for user input (e.g. confirmation prompts)",
127119
showDefaultValue = Visibility.NEVER
128120
)
129-
private boolean noInput;
121+
private Optional<Boolean> noInput = Optional.empty();
122+
123+
public Optional<Ansi> ansi() {
124+
return ansi;
125+
}
126+
127+
public OutputType outputType() {
128+
return outputType.orElse(OutputType.HUMAN);
129+
}
130+
131+
public boolean verbose() {
132+
return verbose.orElse(false);
133+
}
134+
135+
public boolean quiet() {
136+
return quiet.orElse(false);
137+
}
138+
139+
public Optional<Boolean> enableSpinner() {
140+
return enableSpinner;
141+
}
142+
143+
public boolean shouldDumpLogs() {
144+
return shouldDumpLogs.orElse(false);
145+
}
146+
147+
public Optional<Path> dumpLogsTo() {
148+
return dumpLogsTo;
149+
}
150+
151+
public boolean noInput() {
152+
return noInput.orElse(false);
153+
}
154+
155+
public CommonOptions merge(CommonOptions other) {
156+
if (this == EMPTY) {
157+
return other;
158+
}
159+
if (other == EMPTY) {
160+
return this;
161+
}
162+
return new CommonOptions(
163+
(this.ansi.isPresent()) ? this.ansi : other.ansi,
164+
(this.outputType.isPresent()) ? this.outputType : other.outputType,
165+
(this.verbose.isPresent()) ? this.verbose : other.verbose,
166+
(this.quiet.isPresent()) ? this.quiet : other.quiet,
167+
(this.enableSpinner.isPresent()) ? this.enableSpinner : other.enableSpinner,
168+
(this.shouldDumpLogs.isPresent()) ? this.shouldDumpLogs : other.shouldDumpLogs,
169+
(this.dumpLogsTo.isPresent()) ? this.dumpLogsTo : other.dumpLogsTo,
170+
(this.noInput.isPresent()) ? this.noInput : other.noInput
171+
);
172+
}
130173
}

0 commit comments

Comments
 (0)