From 5102ae657fe0fb8a7d5f3add54a8555835997757 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 5 Sep 2024 11:50:43 +1000 Subject: [PATCH] add cmd config back to console Signed-off-by: Phillip Kruger --- .../deployment/console/ConsoleProcessor.java | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/console/ConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/console/ConsoleProcessor.java index 54c14ca595937..89e9706a0eb84 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/console/ConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/console/ConsoleProcessor.java @@ -2,18 +2,47 @@ import static io.quarkus.devui.deployment.ide.IdeProcessor.openBrowser; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.aesh.command.Command; +import org.aesh.command.CommandDefinition; +import org.aesh.command.CommandException; +import org.aesh.command.CommandResult; +import org.aesh.command.GroupCommand; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.completer.CompleterInvocation; +import org.aesh.command.completer.OptionCompleter; +import org.aesh.command.invocation.CommandInvocation; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Option; +import org.aesh.command.validator.CommandValidator; +import org.aesh.command.validator.CommandValidatorException; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Produce; +import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; +import io.quarkus.deployment.builditem.ConsoleCommandBuildItem; +import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.console.ConsoleCommand; import io.quarkus.deployment.console.ConsoleStateManager; +import io.quarkus.dev.config.CurrentConfig; import io.quarkus.dev.spi.DevModeType; +import io.quarkus.devui.runtime.config.ConfigDescription; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; +import io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsManager; public class ConsoleProcessor { @@ -36,4 +65,162 @@ void setupConsole(HttpRootPathBuildItem rp, NonApplicationRootPathBuildItem np, new ConsoleCommand('d', "Open the Dev UI in a browser", null, () -> openBrowser(rp, np, "/q/dev-ui", host, port))); } + + @BuildStep(onlyIf = IsDevelopment.class) + public void config(List configDescriptionBuildItems, + BuildProducer consoleCommandBuildItemBuildProducer, + Optional devServicesLauncherConfig) { + List configDescriptions = new ArrayList<>(); + for (ConfigDescriptionBuildItem item : configDescriptionBuildItems) { + configDescriptions.add( + new ConfigDescription(item.getPropertyName(), + cleanUpAsciiDocIfNecessary(item.getDocs()), + item.getDefaultValue(), + isSetByDevServices(devServicesLauncherConfig, item.getPropertyName()), + item.getValueTypeName(), + item.getAllowedValues(), + item.getConfigPhase().name())); + } + + Set devServicesConfig = new HashSet<>(); + if (devServicesLauncherConfig.isPresent()) { + devServicesConfig.addAll(devServicesLauncherConfig.get().getConfig().keySet()); + } + + consoleCommandBuildItemBuildProducer.produce( + new ConsoleCommandBuildItem(new ConfigCommandGroup(new ConfigDescriptionsManager(configDescriptions)))); + + } + + public static boolean isSetByDevServices(Optional devServicesLauncherConfig, + String propertyName) { + if (devServicesLauncherConfig.isPresent()) { + return devServicesLauncherConfig.get().getConfig().containsKey(propertyName); + } + return false; + } + + public static String cleanUpAsciiDocIfNecessary(String docs) { + if (docs == null || !docs.toLowerCase(Locale.ROOT).contains("@asciidoclet")) { + return docs; + } + // TODO #26199 Ideally we'd use a proper AsciiDoc renderer, but for now we'll just clean it up a bit. + return docs.replace("@asciidoclet", "") + // Avoid problems with links. + .replace("<<", "<<") + .replace(">>", ">>") + // Try to render line breaks... kind of. + .replace("\n\n", "

") + .replace("\n", "
"); + } + + @GroupCommandDefinition(name = "config", description = "Config Editing Commands") + public static class ConfigCommandGroup implements GroupCommand { + + final ConfigDescriptionsManager configDescriptionsManager; + + @Option(shortName = 'h', hasValue = false, overrideRequired = true) + public boolean help; + + public ConfigCommandGroup(ConfigDescriptionsManager configDescriptionsManager) { + this.configDescriptionsManager = configDescriptionsManager; + } + + @Override + public List getCommands() { + return List.of(new SetConfigCommand(configDescriptionsManager)); + } + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + commandInvocation.getShell().writeln(commandInvocation.getHelpInfo()); + return CommandResult.SUCCESS; + } + } + + @CommandDefinition(name = "set", description = "Sets a config value", validator = SetValidator.class) + public static class SetConfigCommand implements Command { + + final ConfigDescriptionsManager configDescriptionsManager; + + @Argument(required = true, completer = SetConfigCompleter.class) + private String command; + + public SetConfigCommand(ConfigDescriptionsManager configDescriptionsManager) { + this.configDescriptionsManager = configDescriptionsManager; + } + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + int pos = command.indexOf('='); + String key = command.substring(0, pos); + String value = command.substring(pos + 1); + CurrentConfig.EDITOR.accept(Map.of(key, value)); + return CommandResult.SUCCESS; + } + } + + public static class SetConfigCompleter implements OptionCompleter { + + @Override + public void complete(CompleterInvocation completerInvocation) { + String soFar = completerInvocation.getGivenCompleteValue(); + String configKey = null; + if (soFar.indexOf('=') != -1) { + configKey = soFar.substring(0, soFar.indexOf('=')); + } + SetConfigCommand command = (SetConfigCommand) completerInvocation.getCommand(); + Set possible = new HashSet<>(); + for (var j : command.configDescriptionsManager.values().values()) { + for (var i : j) { + if (i.getDescription() == null) { + continue; + } + //we have found the entry for the selected key + if (configKey != null && configKey.equals(i.getName())) { + if (i.getAllowedValues() != null && !i.getAllowedValues().isEmpty()) { + for (String val : i.getAllowedValues()) { + String value = i.getName() + "=" + val; + if (value.startsWith(soFar)) { + completerInvocation.addCompleterValue(value); + } + } + } + return; + } + if (i.isWildcardEntry()) { + continue; + } + if (i.getName().equals(soFar)) { + possible.add(soFar + "="); + } else if (i.getName().startsWith(soFar)) { + //we just want to complete the next segment + int pos = i.getName().indexOf('.', soFar.length() + 1); + if (pos == -1) { + possible.add(i.getName() + "="); + } else { + possible.add(i.getName().substring(0, pos) + "."); + } + } + } + } + completerInvocation.setAppendSpace(false); + completerInvocation.addAllCompleterValues(possible); + + } + } + + public static class SetValidator implements CommandValidator { + + @Override + public void validate(SetConfigCommand command) throws CommandValidatorException { + //-1 because the last char can't be equals + for (int i = 0; i < command.command.length() - 1; ++i) { + if (command.command.charAt(i) == '=') { + return; + } + } + throw new CommandValidatorException("Set command must be in the form key=value"); + } + } }