Skip to content

Commit 1ff17c5

Browse files
committed
more prompters
1 parent 9bdf431 commit 1ff17c5

File tree

56 files changed

+794
-539
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+794
-539
lines changed

.idea/workspace.xml

Lines changed: 46 additions & 185 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

reflected.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# CLI basics
2+
com.dtsx.astra.cli.AstraCli$SetupExampleProvider
23
com.dtsx.astra.cli.core.output.Hint
34
com.dtsx.astra.cli.core.models.*
45

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.dtsx.astra.cli;
22

3+
import com.dtsx.astra.cli.AstraCli.SetupExampleProvider;
34
import com.dtsx.astra.cli.commands.*;
45
import com.dtsx.astra.cli.commands.config.ConfigCmd;
56
import com.dtsx.astra.cli.commands.db.DbCmd;
@@ -11,6 +12,7 @@
1112
import com.dtsx.astra.cli.commands.user.UserCmd;
1213
import com.dtsx.astra.cli.core.CliContext;
1314
import com.dtsx.astra.cli.core.TypeConverters;
15+
import com.dtsx.astra.cli.core.config.AstraConfig;
1416
import com.dtsx.astra.cli.core.config.AstraHome;
1517
import com.dtsx.astra.cli.core.datatypes.Ref;
1618
import com.dtsx.astra.cli.core.docs.AliasForSubcommand;
@@ -19,6 +21,7 @@
1921
import com.dtsx.astra.cli.core.exceptions.ExitCodeException;
2022
import com.dtsx.astra.cli.core.exceptions.ParameterExceptionHandler;
2123
import com.dtsx.astra.cli.core.help.*;
24+
import com.dtsx.astra.cli.core.help.Example.ExampleProvider;
2225
import com.dtsx.astra.cli.core.output.AstraColors;
2326
import com.dtsx.astra.cli.core.output.AstraConsole;
2427
import com.dtsx.astra.cli.core.output.AstraLogger;
@@ -28,6 +31,7 @@
2831
import com.dtsx.astra.cli.core.output.formats.OutputHuman;
2932
import com.dtsx.astra.cli.core.output.formats.OutputType;
3033
import com.dtsx.astra.cli.core.properties.CliEnvironmentImpl;
34+
import com.dtsx.astra.cli.core.properties.CliProperties.ConstEnvVars;
3135
import com.dtsx.astra.cli.core.properties.CliPropertiesImpl;
3236
import com.dtsx.astra.cli.core.properties.MemoizedCliProperties;
3337
import com.dtsx.astra.cli.core.upgrades.UpgradeNotifier;
@@ -38,6 +42,7 @@
3842
import lombok.SneakyThrows;
3943
import lombok.experimental.Accessors;
4044
import lombok.val;
45+
import org.graalvm.collections.Pair;
4146
import org.jetbrains.annotations.Nullable;
4247
import org.jetbrains.annotations.VisibleForTesting;
4348
import picocli.CommandLine;
@@ -47,6 +52,7 @@
4752
import picocli.CommandLine.Option;
4853

4954
import java.nio.file.FileSystems;
55+
import java.nio.file.Files;
5056
import java.util.Optional;
5157
import java.util.StringJoiner;
5258
import java.util.function.Supplier;
@@ -76,8 +82,7 @@
7682
}
7783
)
7884
@Example(
79-
comment = "Setup the Astra CLI",
80-
command = "${cli.name} setup"
85+
exampleProvider = SetupExampleProvider.class
8186
)
8287
@Example(
8388
comment = "List databases",
@@ -214,4 +219,27 @@ public <K> K create(Class<K> cls) throws Exception { // I miss having proper ran
214219
public static <T> T exit(int exitCode) {
215220
throw new ExitCodeException(exitCode);
216221
}
222+
223+
public static class SetupExampleProvider implements ExampleProvider {
224+
@Override
225+
public Pair<String, String> get(CliContext ctx) {
226+
try {
227+
val configFileExists = Files.exists(AstraConfig.resolveDefaultAstraConfigFile(ctx));
228+
229+
if (!configFileExists) {
230+
return Pair.create("Setup the Astra CLI", "${cli.name} setup");
231+
}
232+
233+
val autocompleteSetup = System.getenv(ConstEnvVars.COMPLETIONS_SETUP) != null;
234+
235+
if (!autocompleteSetup && ctx.isNotWindows()) {
236+
return Pair.create("Put this in your shell profile to generate completions and more!", "eval \"$(${cli.path} shellenv)\"");
237+
}
238+
} catch (Exception e) {
239+
ctx.log().exception("Error resolving main example for AstraCli", e);
240+
}
241+
242+
return Pair.create("Create a new profile", "astra setup");
243+
}
244+
}
217245
}

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

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

33
import com.dtsx.astra.cli.core.completions.DynamicCompletion;
4+
import com.dtsx.astra.cli.core.help.Example;
45
import com.dtsx.astra.cli.core.mixins.HelpMixin;
56
import com.dtsx.astra.cli.core.properties.CliEnvironmentImpl;
7+
import com.dtsx.astra.cli.core.properties.CliProperties.ConstEnvVars;
68
import com.dtsx.astra.cli.core.properties.CliPropertiesImpl;
79
import lombok.val;
810
import picocli.AutoComplete;
@@ -25,6 +27,10 @@
2527
descriptionHeading = "%n",
2628
hidden = true
2729
)
30+
@Example(
31+
comment = "Put this in your shell profile (e.g. @|code ~/.zprofile|@) to generate completions and set your PATH",
32+
command = "eval \"$(${cli.path} shellenv)\""
33+
)
2834
public class CompletionsCmd implements Runnable {
2935
@Spec
3036
private CommandSpec spec;
@@ -51,11 +57,12 @@ public void run() {
5157
val lines = script.split(NL);
5258
val estimatedSize = script.length() + (instances.size() * 100) + 500;
5359
val sb = new StringBuilder(estimatedSize);
54-
var i = 0;
60+
var linesIdx = 0;
5561

56-
i = appendUtilityFunctions(lines, sb, i);
62+
linesIdx = appendUtilityFunctions(lines, sb, linesIdx);
63+
appendSetupEnvVrs(sb);
5764
appendCompletionFunctions(sb, instances);
58-
updateCompletions(sb, lines, instances, i);
65+
updateCompletions(sb, lines, instances, linesIdx);
5966

6067
spec.commandLine().getOut().println(sb);
6168
}
@@ -75,6 +82,10 @@ private int appendUtilityFunctions(String[] lines, StringBuilder sb, int i) {
7582
return i;
7683
}
7784

85+
private void appendSetupEnvVrs(StringBuilder sb) {
86+
sb.append("export " + ConstEnvVars.COMPLETIONS_SETUP + "=true").append(NL).append(NL);
87+
}
88+
7889
private void appendCompletionFunctions(StringBuilder sb, Set<DynamicCompletion> instances) {
7990
for (val instance : instances) {
8091
sb.append(arr(instance)).append("=()").append(NL);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ protected OutputHuman executeHuman(Supplier<Void> v) {
5757
);
5858

5959
val generated = ctx.log().loading("Generating documentation tree", (_) -> {
60-
return new AsciidocGenerator(ctx.properties().cliName(), spec.root(), docsSpec).generate();
60+
return new AsciidocGenerator(ctx, spec.root(), docsSpec).generate();
6161
});
6262

6363
ctx.log().loading("Writing documentation to " + outputDir, (_) -> {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
" @|blue:300 *|@ Enable shell completions",
3030
" @|blue:300 *|@ Optionally set any other configuration environment variables.",
3131
"",
32-
"Intended to be added to your shell profile (@|code .zshrc|@, @|code .zprofile|@, @|code .bashrc|@, etc.)",
32+
"Intended to be added to your shell profile (@|code .zshrc|@, @|code .zprofile|@, @|code .bashrc|@, etc.), but you can technically just eval it in any shell session to get completions and update your PATH for that session.",
3333
},
3434
descriptionHeading = "%n"
3535
)

src/main/java/com/dtsx/astra/cli/commands/config/ConfigDeleteCmd.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.dtsx.astra.cli.core.help.Example;
88
import com.dtsx.astra.cli.core.output.Hint;
99
import com.dtsx.astra.cli.core.output.formats.OutputAll;
10+
import com.dtsx.astra.cli.core.output.prompters.specific.ProfileNamePrompter;
1011
import com.dtsx.astra.cli.operations.Operation;
1112
import com.dtsx.astra.cli.operations.config.ConfigDeleteOperation;
1213
import com.dtsx.astra.cli.operations.config.ConfigDeleteOperation.*;
@@ -17,6 +18,7 @@
1718

1819
import java.util.LinkedHashMap;
1920
import java.util.List;
21+
import java.util.Optional;
2022
import java.util.function.Supplier;
2123

2224
import static com.dtsx.astra.cli.core.output.ExitCode.PROFILE_NOT_FOUND;
@@ -36,11 +38,12 @@
3638
)
3739
public class ConfigDeleteCmd extends AbstractConfigCmd<ConfigDeleteResult> {
3840
@Parameters(
41+
arity = "0..1",
3942
description = "Name of the profile to delete",
4043
completionCandidates = AvailableProfilesCompletion.class,
4144
paramLabel = $Profile.LABEL
4245
)
43-
public ProfileName $profileName;
46+
public Optional<ProfileName> $profileName;
4447

4548
@Option(
4649
names = { "--if-exists" },
@@ -51,33 +54,33 @@ public class ConfigDeleteCmd extends AbstractConfigCmd<ConfigDeleteResult> {
5154
@Override
5255
public final OutputAll execute(Supplier<ConfigDeleteResult> result) {
5356
return switch (result.get()) {
54-
case ProfileDeleted() -> handleProfileDeleted();
55-
case ProfileDoesNotExist() -> handleProfileDoesNotExist();
56-
case ProfileIllegallyDoesNotExist() -> throwProfileNotFound();
57+
case ProfileDeleted(var profileName) -> handleProfileDeleted(profileName);
58+
case ProfileDoesNotExist(var profileName) -> handleProfileDoesNotExist(profileName);
59+
case ProfileIllegallyDoesNotExist(var profileName) -> throwProfileNotFound(profileName);
5760
};
5861
}
5962

60-
private OutputAll handleProfileDeleted() {
61-
val message = "Profile %s deleted successfully.".formatted(ctx.highlight($profileName));
63+
private OutputAll handleProfileDeleted(ProfileName profileName) {
64+
val message = "Profile %s deleted successfully.".formatted(ctx.highlight(profileName));
6265

6366
return OutputAll.response(message, mkData(true));
6467
}
6568

66-
private OutputAll handleProfileDoesNotExist() {
67-
val message = "Profile %s does not exist; nothing to delete.".formatted(ctx.highlight($profileName));
69+
private OutputAll handleProfileDoesNotExist(ProfileName profileName) {
70+
val message = "Profile %s does not exist; nothing to delete.".formatted(ctx.highlight(profileName));
6871

6972
return OutputAll.response(message, mkData(false), List.of(
7073
new Hint("See your existing profiles:", "${cli.name} config list")
7174
));
7275
}
7376

74-
private <T> T throwProfileNotFound() {
77+
private <T> T throwProfileNotFound(ProfileName profileName) {
7578
throw new AstraCliException(PROFILE_NOT_FOUND, """
7679
@|bold,red Error: A profile with the name '%s' could not be found.|@
7780
7881
To ignore this error, you can use the @'!--if-exists!@ option to avoid failing if the profile does not exist.
7982
""".formatted(
80-
$profileName
83+
profileName
8184
), List.of(
8285
new Hint("Example fix:", originalArgs(), "--if-exists"),
8386
new Hint("See your existing profiles:", "${cli.name} config list")
@@ -92,6 +95,15 @@ private LinkedHashMap<String, Object> mkData(Boolean wasDeleted) {
9295

9396
@Override
9497
protected Operation<ConfigDeleteResult> mkOperation() {
95-
return new ConfigDeleteOperation(config(false), new CreateDeleteRequest($profileName, $ifExists));
98+
return new ConfigDeleteOperation(config(false), new CreateDeleteRequest($profileName.orElseGet(this::promptForProfileName), $ifExists));
99+
}
100+
101+
private ProfileName promptForProfileName() {
102+
val selected = ProfileNamePrompter.prompt(ctx, config(false).profiles(), "Select a profile to delete",
103+
(list) -> list,
104+
(b) -> b.fallbackIndex(0).fix(originalArgs(), "<profile>")
105+
);
106+
107+
return ProfileName.mkUnsafe(selected);
96108
}
97109
}

src/main/java/com/dtsx/astra/cli/commands/config/ConfigGetCmd.java

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
import com.dtsx.astra.cli.core.CliConstants.$Profile;
44
import com.dtsx.astra.cli.core.completions.impls.AvailableProfilesCompletion;
55
import com.dtsx.astra.cli.core.completions.impls.ProfileKeysCompletion;
6-
import com.dtsx.astra.cli.core.config.InvalidProfile;
7-
import com.dtsx.astra.cli.core.config.Profile;
8-
import com.dtsx.astra.cli.core.datatypes.Either;
9-
import com.dtsx.astra.cli.core.datatypes.NEList;
106
import com.dtsx.astra.cli.core.exceptions.AstraCliException;
117
import com.dtsx.astra.cli.core.help.Example;
128
import com.dtsx.astra.cli.core.output.Hint;
139
import com.dtsx.astra.cli.core.output.formats.OutputAll;
1410
import com.dtsx.astra.cli.core.output.formats.OutputCsv;
1511
import com.dtsx.astra.cli.core.output.formats.OutputHuman;
1612
import com.dtsx.astra.cli.core.output.formats.OutputJson;
13+
import com.dtsx.astra.cli.core.output.prompters.specific.ProfileNamePrompter;
1714
import com.dtsx.astra.cli.core.output.table.ShellTable;
1815
import com.dtsx.astra.cli.core.parsers.ini.ast.IniSection;
1916
import com.dtsx.astra.cli.operations.Operation;
@@ -157,48 +154,13 @@ private String mkKeysMsg(String key, IniSection section) {
157154

158155
@Override
159156
protected Operation<GetConfigResult> mkOperation() {
160-
return new ConfigGetOperation(config(false), new GetConfigRequest($profileName, $key, this::promptForProfileName));
157+
return new ConfigGetOperation(config(false), new GetConfigRequest($profileName.orElseGet(this::promptForProfileName), $key));
161158
}
162159

163-
private String promptForProfileName(NEList<Either<InvalidProfile, Profile>> candidates) {
164-
val maxNameLength = candidates.stream()
165-
.map(p -> extractProfileName(p).length())
166-
.max(Integer::compareTo)
167-
.orElse(0);
168-
169-
val profileToDisplayMap = candidates.stream()
170-
.collect(Collectors.toMap(
171-
(p) -> p,
172-
(p) -> {
173-
val paddedName = extractProfileName(p) + " ".repeat(maxNameLength - extractProfileName(p).length());
174-
175-
return p.fold(
176-
(_) -> paddedName + " @|bold,red (invalid)|@",
177-
(profile) -> paddedName + " " + ctx.colors().NEUTRAL_500.use("(" + profile.env().name().toLowerCase() + ")")
178-
);
179-
}
180-
));
181-
182-
val defaultProfile = candidates.stream()
183-
.filter(p -> p.isRight() && p.getRight().isDefault())
184-
.findFirst()
185-
.orElse(null);
186-
187-
val selected = ctx.console().select("Select a profile to look at")
188-
.options(candidates)
189-
.defaultOption(defaultProfile)
190-
.mapper(profileToDisplayMap::get)
191-
.fallbackIndex(0)
192-
.fix(originalArgs(), "<profile>")
193-
.clearAfterSelection();
194-
195-
return extractProfileName(selected);
196-
}
197-
198-
private String extractProfileName(Either<InvalidProfile, Profile> profile) {
199-
return profile.fold(
200-
(invalid) -> invalid.section().name(),
201-
(valid) -> valid.nameOrDefault().unwrap()
160+
private String promptForProfileName() {
161+
return ProfileNamePrompter.prompt(ctx, config(false).profiles(), "Select a profile to look at",
162+
(list) -> list,
163+
(b) -> b.fallbackIndex(0).fix(originalArgs(), "<profile>")
202164
);
203165
}
204166
}

0 commit comments

Comments
 (0)