From 2810d86fb6db9abe95556a1c7fd57647dcb56d4f Mon Sep 17 00:00:00 2001 From: Romain Manni-Bucau Date: Wed, 7 Feb 2024 10:28:54 +0100 Subject: [PATCH] [env-manager] enable candidate to provide metadata --- env-manager/pom.xml | 1 + .../java/io/yupiik/dev/command/Delete.java | 12 +++-- .../main/java/io/yupiik/dev/command/Env.java | 26 +++++---- .../java/io/yupiik/dev/command/Install.java | 13 +++-- .../java/io/yupiik/dev/command/Resolve.java | 12 +++-- .../main/java/io/yupiik/dev/command/Run.java | 18 +++---- .../yupiik/dev/provider/ProviderRegistry.java | 35 +++++++----- .../provider/central/CentralBaseProvider.java | 7 ++- .../provider/central/CentralProviderInit.java | 7 ++- .../provider/github/MinikubeGithubClient.java | 3 +- .../yupiik/dev/provider/model/Candidate.java | 4 +- .../dev/provider/sdkman/SdkManClient.java | 16 +++++- .../dev/provider/zulu/ZuluCdnClient.java | 3 +- .../io/yupiik/dev/shared/MessageHelper.java | 54 +++++++++++++++++++ .../java/io/yupiik/dev/shared/RcService.java | 35 ++++++------ .../central/CentralBaseProviderTest.java | 3 +- .../dev/provider/sdkman/SdkManClientTest.java | 10 ++-- 17 files changed, 186 insertions(+), 73 deletions(-) create mode 100644 env-manager/src/main/java/io/yupiik/dev/shared/MessageHelper.java diff --git a/env-manager/pom.xml b/env-manager/pom.xml index b9aed8b7..9cabac71 100644 --- a/env-manager/pom.xml +++ b/env-manager/pom.xml @@ -123,6 +123,7 @@ skip + true diff --git a/env-manager/src/main/java/io/yupiik/dev/command/Delete.java b/env-manager/src/main/java/io/yupiik/dev/command/Delete.java index 332bce5f..51b08f07 100644 --- a/env-manager/src/main/java/io/yupiik/dev/command/Delete.java +++ b/env-manager/src/main/java/io/yupiik/dev/command/Delete.java @@ -16,6 +16,7 @@ package io.yupiik.dev.command; import io.yupiik.dev.provider.ProviderRegistry; +import io.yupiik.dev.shared.MessageHelper; import io.yupiik.fusion.framework.build.api.cli.Command; import io.yupiik.fusion.framework.build.api.configuration.Property; import io.yupiik.fusion.framework.build.api.configuration.RootConfiguration; @@ -28,20 +29,23 @@ public class Delete implements Runnable { private final Logger logger = Logger.getLogger(getClass().getName()); private final Conf conf; private final ProviderRegistry registry; + private final MessageHelper messageHelper; public Delete(final Conf conf, - final ProviderRegistry registry) { + final ProviderRegistry registry, + final MessageHelper messageHelper) { this.conf = conf; this.registry = registry; + this.messageHelper = messageHelper; } @Override public void run() { try { registry.findByToolVersionAndProvider(conf.tool(), conf.version(), conf.provider(), false) - .thenAccept(providerAndVersion -> { - providerAndVersion.getKey().delete(conf.tool(), providerAndVersion.getValue().identifier()); - logger.info(() -> "Deleted " + conf.tool() + "@" + providerAndVersion.getValue().version()); + .thenAccept(matched -> { + matched.provider().delete(conf.tool(), matched.version().identifier()); + logger.info(() -> "Deleted " + messageHelper.formatToolNameAndVersion(matched.candidate(), conf.tool(), matched.version().version())); }) .toCompletableFuture() .get(); diff --git a/env-manager/src/main/java/io/yupiik/dev/command/Env.java b/env-manager/src/main/java/io/yupiik/dev/command/Env.java index a662f53b..7c644473 100644 --- a/env-manager/src/main/java/io/yupiik/dev/command/Env.java +++ b/env-manager/src/main/java/io/yupiik/dev/command/Env.java @@ -15,6 +15,7 @@ */ package io.yupiik.dev.command; +import io.yupiik.dev.shared.MessageHelper; import io.yupiik.dev.shared.Os; import io.yupiik.dev.shared.RcService; import io.yupiik.fusion.framework.build.api.cli.Command; @@ -42,11 +43,13 @@ public class Env implements Runnable { private final Conf conf; private final RcService rc; private final Os os; + private final MessageHelper messageHelper; - public Env(final Conf conf, final Os os, final RcService rc) { + public Env(final Conf conf, final Os os, final RcService rc, final MessageHelper messageHelper) { this.conf = conf; this.os = os; this.rc = rc; + this.messageHelper = messageHelper; } @Override @@ -99,28 +102,29 @@ public void close() throws SecurityException { try { rc.toToolProperties(tools).thenAccept(resolved -> { - final var toolVars = resolved.entrySet().stream() + final var toolVars = resolved.stream() .flatMap(e -> Stream.of( - export + e.getKey().envPathVarName() + "=\"" + quoted(e.getValue()) + "\";", - export + e.getKey().envVersionVarName() + "=\"" + e.getKey().version() + "\";")) + export + e.properties().envPathVarName() + "=\"" + quoted(e.path()) + "\";", + export + e.properties().envVersionVarName() + "=\"" + e.properties().version() + "\";")) .sorted() .collect(joining("\n", "", "\n")); final var pathBase = ofNullable(System.getenv("YEM_ORIGINAL_PATH")) .or(() -> ofNullable(System.getenv(pathName))) .orElse(""); - final var pathVars = resolved.keySet().stream().anyMatch(RcService.ToolProperties::addToPath) ? + final var pathVars = resolved.stream().map(RcService.MatchedPath::properties).anyMatch(RcService.ToolProperties::addToPath) ? export + "YEM_ORIGINAL_PATH=\"" + pathBase + "\";\n" + - export + pathName + "=\"" + resolved.entrySet().stream() - .filter(r -> r.getKey().addToPath()) - .map(r -> quoted(rc.toBin(r.getValue()))) + export + pathName + "=\"" + resolved.stream() + .filter(r -> r.properties().addToPath()) + .map(r -> quoted(rc.toBin(r.path()))) .collect(joining(pathSeparator, "", pathSeparator)) + pathVar + "\";\n" : ""; final var echos = Boolean.parseBoolean(tools.getProperty("echo", "true")) ? - resolved.entrySet().stream() + resolved.stream() // don't log too much, if it does not change, don't re-log it - .filter(Predicate.not(it -> Objects.equals(it.getValue().toString(), System.getenv(it.getKey().envPathVarName())))) - .map(e -> "echo \"[yem] Resolved " + e.getKey().toolName() + "@" + e.getKey().version() + " to '" + e.getValue() + "'\";") + .filter(Predicate.not(it -> Objects.equals(it.path().toString(), System.getenv(it.properties().envPathVarName())))) + .map(e -> "echo \"[yem] Resolved " + messageHelper.formatToolNameAndVersion( + e.candidate(), e.properties().toolName(), e.properties().version()) + " to '" + e.path() + "'\";") .collect(joining("\n", "", "\n")) : ""; diff --git a/env-manager/src/main/java/io/yupiik/dev/command/Install.java b/env-manager/src/main/java/io/yupiik/dev/command/Install.java index ed5c9e67..7e4b553a 100644 --- a/env-manager/src/main/java/io/yupiik/dev/command/Install.java +++ b/env-manager/src/main/java/io/yupiik/dev/command/Install.java @@ -16,6 +16,7 @@ package io.yupiik.dev.command; import io.yupiik.dev.provider.ProviderRegistry; +import io.yupiik.dev.shared.MessageHelper; import io.yupiik.fusion.framework.build.api.cli.Command; import io.yupiik.fusion.framework.build.api.configuration.Property; import io.yupiik.fusion.framework.build.api.configuration.RootConfiguration; @@ -31,20 +32,24 @@ public class Install implements Runnable { private final Logger logger = Logger.getLogger(getClass().getName()); private final Conf conf; private final ProviderRegistry registry; + private final MessageHelper messageHelper; public Install(final Conf conf, - final ProviderRegistry registry) { + final ProviderRegistry registry, + final MessageHelper messageHelper) { this.conf = conf; this.registry = registry; + this.messageHelper = messageHelper; } @Override public void run() { try { registry.findByToolVersionAndProvider(conf.tool(), conf.version(), conf.provider(), conf.relaxed()) - .thenCompose(providerAndVersion -> providerAndVersion.getKey() - .install(conf.tool(), providerAndVersion.getValue().identifier(), this::onProgress) - .thenAccept(result -> logger.info(() -> "Installed " + conf.tool() + "@" + providerAndVersion.getValue().version() + " at '" + result + "'"))) + .thenCompose(matched -> matched.provider() + .install(conf.tool(), matched.version().identifier(), this::onProgress) + .thenAccept(result -> logger.info(() -> "Installed " + messageHelper.formatToolNameAndVersion( + matched.candidate(), conf.tool(), matched.version().version()) + " at '" + result + "'"))) .toCompletableFuture() .get(); } catch (final InterruptedException e) { diff --git a/env-manager/src/main/java/io/yupiik/dev/command/Resolve.java b/env-manager/src/main/java/io/yupiik/dev/command/Resolve.java index cd570c71..0e8094bc 100644 --- a/env-manager/src/main/java/io/yupiik/dev/command/Resolve.java +++ b/env-manager/src/main/java/io/yupiik/dev/command/Resolve.java @@ -16,6 +16,7 @@ package io.yupiik.dev.command; import io.yupiik.dev.provider.ProviderRegistry; +import io.yupiik.dev.shared.MessageHelper; import io.yupiik.fusion.framework.build.api.cli.Command; import io.yupiik.fusion.framework.build.api.configuration.Property; import io.yupiik.fusion.framework.build.api.configuration.RootConfiguration; @@ -28,21 +29,24 @@ public class Resolve implements Runnable { private final Logger logger = Logger.getLogger(getClass().getName()); private final Conf conf; private final ProviderRegistry registry; + private final MessageHelper messageHelper; public Resolve(final Conf conf, - final ProviderRegistry registry) { + final ProviderRegistry registry, + final MessageHelper messageHelper) { this.conf = conf; this.registry = registry; + this.messageHelper = messageHelper; } @Override public void run() { try { registry.findByToolVersionAndProvider(conf.tool(), conf.version(), conf.provider(), false) - .thenAccept(providerAndVersion -> { - final var resolved = providerAndVersion.getKey().resolve(conf.tool(), providerAndVersion.getValue().identifier()) + .thenAccept(matched -> { + final var resolved = matched.provider().resolve(conf.tool(), matched.version().identifier()) .orElseThrow(() -> new IllegalArgumentException("No matching instance for " + conf.tool() + "@" + conf.version() + ", ensure to install it before resolving it.")); - logger.info(() -> "Resolved " + conf.tool() + "@" + providerAndVersion.getValue().version() + ": '" + resolved + "'"); + logger.info(() -> "Resolved " + messageHelper.formatToolNameAndVersion(matched.candidate(), conf.tool(), matched.version().version()) + ": '" + resolved + "'"); }) .toCompletableFuture() .get(); diff --git a/env-manager/src/main/java/io/yupiik/dev/command/Run.java b/env-manager/src/main/java/io/yupiik/dev/command/Run.java index d1bfc89c..93478664 100644 --- a/env-manager/src/main/java/io/yupiik/dev/command/Run.java +++ b/env-manager/src/main/java/io/yupiik/dev/command/Run.java @@ -129,17 +129,17 @@ public void run() { } } - private void setEnv(final Map resolved, final Map environment) { - resolved.forEach((tool, home) -> { - final var homeStr = home.toString(); - logger.finest(() -> "Setting '" + tool.envPathVarName() + "' to '" + homeStr + "'"); - environment.put(tool.envPathVarName(), homeStr); + private void setEnv(final List resolved, final Map environment) { + resolved.forEach(it -> { + final var homeStr = it.path().toString(); + logger.finest(() -> "Setting '" + it.properties().envPathVarName() + "' to '" + homeStr + "'"); + environment.put(it.properties().envPathVarName(), homeStr); }); - if (resolved.keySet().stream().anyMatch(RcService.ToolProperties::addToPath)) { + if (resolved.stream().map(RcService.MatchedPath::properties).anyMatch(RcService.ToolProperties::addToPath)) { final var pathName = os.isWindows() ? "Path" : "PATH"; - final var path = resolved.entrySet().stream() - .filter(r -> r.getKey().addToPath()) - .map(r -> rc.toBin(r.getValue()).toString()) + final var path = resolved.stream() + .filter(r -> r.properties().addToPath()) + .map(r -> rc.toBin(r.path()).toString()) .collect(joining(pathSeparator, "", pathSeparator)) + ofNullable(System.getenv(pathName)).orElse(""); logger.finest(() -> "Setting 'PATH' to '" + path + "'"); diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/ProviderRegistry.java b/env-manager/src/main/java/io/yupiik/dev/provider/ProviderRegistry.java index 2453f2cc..f1f197f5 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/ProviderRegistry.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/ProviderRegistry.java @@ -33,7 +33,6 @@ import java.util.stream.Stream; import static java.util.Locale.ROOT; -import static java.util.Map.entry; import static java.util.Optional.empty; import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.logging.Level.FINEST; @@ -68,8 +67,8 @@ public List providers() { return providers; } - public CompletionStage> findByToolVersionAndProvider(final String tool, final String version, final String provider, - final boolean relaxed) { + public CompletionStage findByToolVersionAndProvider(final String tool, final String version, final String provider, + final boolean relaxed) { return tryFindByToolVersionAndProvider(tool, version, provider, relaxed, new Cache(new ConcurrentHashMap<>(), new ConcurrentHashMap<>())) .thenApply(found -> found.orElseThrow(() -> new IllegalArgumentException( "No provider for tool " + tool + "@" + version + "', available tools:\n" + @@ -102,10 +101,10 @@ public CompletionStage> findByToolVersionAndProvide .collect(joining("\n"))))); } - public CompletionStage>> tryFindByToolVersionAndProvider( + public CompletionStage> tryFindByToolVersionAndProvider( final String tool, final String version, final String provider, final boolean relaxed, final Cache cache) { - final var result = new CompletableFuture>>(); + final var result = new CompletableFuture>(); final var promises = providers().stream() .filter(it -> provider == null || // enable "--install-provider zulu" for example @@ -127,7 +126,13 @@ public CompletionStage>> tryFindByToolVers .thenApply(all -> all.stream() .filter(v -> matchVersion(v, version, relaxed)) .findFirst() - .map(v -> entry(it, v))) + .map(v -> new MatchedVersion( + it, + candidates.stream() + .filter(c -> Objects.equals(c.tool(), tool)) + .findFirst() + .orElse(null), + v))) .toCompletableFuture())); } return completedFuture(Optional.empty()); @@ -169,15 +174,16 @@ private CompletionStage> findRemoteVersions(final String tool, fin })); } - private Stream> findMatchingVersion(final String tool, final String version, - final boolean relaxed, final Provider provider, - final Map> versions) { + private Stream findMatchingVersion(final String tool, final String version, + final boolean relaxed, final Provider provider, + final Map> versions) { return versions.entrySet().stream() .filter(e -> Objects.equals(e.getKey().tool(), tool)) - .flatMap(e -> e.getValue().stream().filter(v -> matchVersion(v, version, relaxed))) - .findFirst() - .stream() - .map(v -> entry(provider, v)); + .flatMap(e -> e.getValue().stream() + .filter(v -> matchVersion(v, version, relaxed)) + .findFirst() + .stream() + .map(v -> new MatchedVersion(provider, e.getKey(), v))); } private boolean matchVersion(final Version v, final String version, final boolean relaxed) { @@ -188,4 +194,7 @@ private boolean matchVersion(final Version v, final String version, final boolea public record Cache(Map>> local, Map>> versions) { } + + public record MatchedVersion(Provider provider, Candidate candidate, Version version) { + } } diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralBaseProvider.java b/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralBaseProvider.java index e5174cf6..32143ca4 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralBaseProvider.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralBaseProvider.java @@ -50,13 +50,15 @@ public class CentralBaseProvider implements Provider { private final Gav gav; private final Path local; private final boolean enabled; + private final Map meta; public CentralBaseProvider(final YemHttpClient client, final CentralConfiguration conf, // children must use SingletonCentralConfiguration to avoid multiple creations final Archives archives, final Cache cache, final Gav gav, - final boolean enabled) { + final boolean enabled, + final Map meta) { this.client = client; this.archives = archives; this.cache = cache; @@ -64,6 +66,7 @@ public CentralBaseProvider(final YemHttpClient client, this.local = Path.of(conf.local()); this.gav = gav; this.enabled = enabled; + this.meta = meta; } public Gav gav() { @@ -185,7 +188,7 @@ public CompletionStage> listTools() { return completedFuture(List.of(new Candidate( gav.artifactId().startsWith("apache-") ? gav.artifactId().substring("apache-".length()) : gavString, - toName(gav.artifactId()), gavString + " downloaded from central.", base.toASCIIString()))); + toName(gav.artifactId()), gavString + " downloaded from central.", base.toASCIIString(), meta))); } @Override diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralProviderInit.java b/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralProviderInit.java index 3a9ca6c5..353f4d2a 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralProviderInit.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/central/CentralProviderInit.java @@ -27,6 +27,8 @@ import io.yupiik.fusion.framework.build.api.event.OnEvent; import io.yupiik.fusion.framework.build.api.order.Order; +import java.util.Map; + @ApplicationScoped public class CentralProviderInit { public void onStart(@OnEvent @Order(Integer.MIN_VALUE + 100) final Start start, @@ -40,7 +42,10 @@ public void onStart(@OnEvent @Order(Integer.MIN_VALUE + 100) final Start start, final var beans = container.getBeans(); registry.gavs().forEach(gav -> beans.doRegister(new ProvidedInstanceBean<>(DefaultScoped.class, CentralBaseProvider.class, () -> { final var enabled = "true".equals(conf.get(gav.artifactId() + ".enabled").orElse("true")); - return new CentralBaseProvider(client, configuration, archives, cache, gav, enabled); + return new CentralBaseProvider(client, configuration, archives, cache, gav, enabled, switch (gav.artifactId()) { + case "apache-maven" -> Map.of("emoji", "\uD83E\uDD89"); + default -> Map.of(); + }); }))); } } diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/github/MinikubeGithubClient.java b/env-manager/src/main/java/io/yupiik/dev/provider/github/MinikubeGithubClient.java index a78bc47e..4d0cfc79 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/github/MinikubeGithubClient.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/github/MinikubeGithubClient.java @@ -86,7 +86,8 @@ public CompletionStage> listTools() { return completedFuture(List.of()); } return completedFuture(List.of(new Candidate( - "minikube", "Minikube", "Local development Kubernetes binary.", "https://minikube.sigs.k8s.io/docs/"))); + "minikube", "Minikube", "Local development Kubernetes binary.", "https://minikube.sigs.k8s.io/docs/", + Map.of("emoji", "☸️")))); } @Override diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/model/Candidate.java b/env-manager/src/main/java/io/yupiik/dev/provider/model/Candidate.java index 535c9505..ce3d7baf 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/model/Candidate.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/model/Candidate.java @@ -15,5 +15,7 @@ */ package io.yupiik.dev.provider.model; -public record Candidate(String tool, String name, String description, String url) { +import java.util.Map; + +public record Candidate(String tool, String name, String description, String url, Map metadata) { } \ No newline at end of file diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/sdkman/SdkManClient.java b/env-manager/src/main/java/io/yupiik/dev/provider/sdkman/SdkManClient.java index da4a2365..aa589280 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/sdkman/SdkManClient.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/sdkman/SdkManClient.java @@ -141,7 +141,7 @@ public CompletionStage>> listLocal() { return completedFuture(tool.collect(toMap( it -> { final var name = it.getFileName().toString(); - return new Candidate(name, name, "", ""); + return new Candidate(name, name, "", "", toMetadata(name)); }, it -> { if (Files.notExists(it)) { @@ -346,11 +346,23 @@ private List parseList(final String body) { } candidates.add(new Candidate( tool, line1.substring(0, sep1), // version=line1.substring(sep1 + 2, sep2), - description.toString(), link > 0 ? line1.substring(link) : "")); + description.toString(), link > 0 ? line1.substring(link) : "", + toMetadata(tool))); } return candidates; } + private Map toMetadata(final String tool) { + if (tool == null) { + return Map.of(); + } + return switch (tool) { + case "java" -> Map.of("emoji", "☕"); + case "maven" -> Map.of("emoji", "\uD83E\uDD89"); + default -> Map.of(); + }; + } + private List lines(final String body) { final List allLines; try (final var reader = new BufferedReader(new StringReader(body))) { diff --git a/env-manager/src/main/java/io/yupiik/dev/provider/zulu/ZuluCdnClient.java b/env-manager/src/main/java/io/yupiik/dev/provider/zulu/ZuluCdnClient.java index ec648f4a..7365fc42 100644 --- a/env-manager/src/main/java/io/yupiik/dev/provider/zulu/ZuluCdnClient.java +++ b/env-manager/src/main/java/io/yupiik/dev/provider/zulu/ZuluCdnClient.java @@ -195,7 +195,8 @@ public CompletionStage> listTools() { if (!enabled) { return completedFuture(List.of()); } - return completedFuture(List.of(new Candidate("java", "java", "Java JRE or JDK downloaded from Azul CDN.", base.toASCIIString()))); + return completedFuture(List.of(new Candidate( + "java", "java", "Java JRE or JDK downloaded from Azul CDN.", base.toASCIIString(), Map.of("emoji", "☕")))); } @Override diff --git a/env-manager/src/main/java/io/yupiik/dev/shared/MessageHelper.java b/env-manager/src/main/java/io/yupiik/dev/shared/MessageHelper.java new file mode 100644 index 00000000..1b85bd95 --- /dev/null +++ b/env-manager/src/main/java/io/yupiik/dev/shared/MessageHelper.java @@ -0,0 +1,54 @@ +package io.yupiik.dev.shared; + +import io.yupiik.dev.provider.model.Candidate; +import io.yupiik.fusion.framework.api.scope.ApplicationScoped; +import io.yupiik.fusion.framework.build.api.configuration.Property; +import io.yupiik.fusion.framework.build.api.configuration.RootConfiguration; +import io.yupiik.fusion.framework.build.api.lifecycle.Init; + +import java.nio.file.Files; +import java.nio.file.Path; + +@ApplicationScoped +public class MessageHelper { + private final MessagesConfiguration configuration; + private boolean supportsEmoji; + + public MessageHelper(final MessagesConfiguration configuration) { + this.configuration = configuration; + } + + @Init + protected void init() { + supportsEmoji = switch (configuration.disableEmoji()) { + case "auto" -> !Boolean.parseBoolean(System.getenv("CI")) && + Files.exists(Path.of("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf")); + default -> !Boolean.parseBoolean(configuration.disableEmoji()); + }; + } + + public boolean supportsEmoji() { + return supportsEmoji; + } + + public String formatToolNameAndVersion(final Candidate candidate, final String tool, final String version) { + final var base = tool + '@' + version; + if (!supportsEmoji) { + return base; + } + + final var metadata = candidate.metadata(); + if (metadata.containsKey("emoji")) { + return metadata.get("emoji") + ' ' + base; + } + + return base; + } + + @RootConfiguration("messages") + public record MessagesConfiguration( + @Property(documentation = "If `false` emoji are totally disabled. " + + "`auto` will test `/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf` presence to enable emojis. " + + "`true`/`false` disable/enable emoji whatever the available fonts.", defaultValue = "\"auto\"") String disableEmoji) { + } +} diff --git a/env-manager/src/main/java/io/yupiik/dev/shared/RcService.java b/env-manager/src/main/java/io/yupiik/dev/shared/RcService.java index 000eb0dc..fc4d6606 100644 --- a/env-manager/src/main/java/io/yupiik/dev/shared/RcService.java +++ b/env-manager/src/main/java/io/yupiik/dev/shared/RcService.java @@ -17,6 +17,7 @@ import io.yupiik.dev.provider.Provider; import io.yupiik.dev.provider.ProviderRegistry; +import io.yupiik.dev.provider.model.Candidate; import io.yupiik.dev.provider.model.Version; import io.yupiik.dev.shared.http.YemHttpClient; import io.yupiik.fusion.framework.api.scope.ApplicationScoped; @@ -24,7 +25,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Properties; @@ -36,7 +37,6 @@ import java.util.stream.Stream; import static java.util.Locale.ROOT; -import static java.util.Map.entry; import static java.util.concurrent.CompletableFuture.allOf; import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.stream.Collectors.toMap; @@ -96,16 +96,16 @@ public Path toBin(final Path value) { .orElse(value); } - public CompletionStage> toToolProperties(final Properties props) { + public CompletionStage> toToolProperties(final Properties props) { final var promises = props.stringPropertyNames().stream() .filter(it -> it.endsWith(".version")) .map(versionKey -> toToolProperties(props, versionKey)) .map(tool -> { - final var providerAndVersionPromise = registry.tryFindByToolVersionAndProvider( + final var promise = registry.tryFindByToolVersionAndProvider( tool.toolName(), tool.version(), tool.provider() == null || tool.provider().isBlank() ? null : tool.provider(), tool.relaxed(), new ProviderRegistry.Cache(new ConcurrentHashMap<>(), new ConcurrentHashMap<>())); - return providerAndVersionPromise.thenCompose(providerAndVersionOpt -> providerAndVersionOpt + return promise.thenCompose(providerAndVersionOpt -> providerAndVersionOpt .map(providerVersion -> doResolveVersion(tool, providerVersion)) .orElseGet(() -> { if (tool.failOnMissing()) { @@ -120,14 +120,13 @@ public CompletionStage> toToolProperties(final Propert return allOf(promises.toArray(new CompletableFuture[0])) .thenApply(ok -> promises.stream() .flatMap(p -> p.getNow(Optional.empty()).stream()) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))); + .toList()); } - private CompletableFuture>> doResolveVersion(final ToolProperties tool, - final Map.Entry providerVersion) { - final var provider = providerVersion.getKey(); - final var version = providerVersion.getValue().identifier(); - return provider.resolve(tool.toolName(), providerVersion.getValue().identifier()) + private CompletableFuture> doResolveVersion(final ToolProperties tool, + final ProviderRegistry.MatchedVersion matchedVersion) { + final var version = matchedVersion.version().identifier(); + return matchedVersion.provider().resolve(tool.toolName(), version) .map(path -> completedFuture(Optional.of(path))) .or(() -> { if (!tool.installIfMissing()) { @@ -138,13 +137,14 @@ private CompletableFuture>> doResolveVe } logger.info(() -> "Installing " + tool.toolName() + '@' + version); - return Optional.of(provider.install(tool.toolName(), version, Provider.ProgressListener.NOOP) + return Optional.of(matchedVersion.provider().install(tool.toolName(), version, Provider.ProgressListener.NOOP) .exceptionally(this::onInstallException) .thenApply(Optional::ofNullable) .toCompletableFuture()); }) .orElseGet(() -> completedFuture(Optional.empty())) - .thenApply(path -> path.map(p -> entry(adjustToolVersion(tool, providerVersion), p))); + .thenApply(path -> path.map(p -> new MatchedPath( + p, adjustToolVersion(tool, matchedVersion.version()), matchedVersion.provider(), matchedVersion.candidate()))); } private Path onInstallException(final Throwable e) { @@ -158,12 +158,12 @@ private Path onInstallException(final Throwable e) { throw new IllegalStateException(unwrapped); } - private ToolProperties adjustToolVersion(final ToolProperties tool, final Map.Entry providerVersion) { - return Objects.equals(tool.version(), providerVersion.getValue().version()) ? + private ToolProperties adjustToolVersion(final ToolProperties tool, final Version version) { + return Objects.equals(tool.version(), version.version()) ? tool : new ToolProperties( tool.toolName(), - providerVersion.getValue().version(), + version.version(), tool.provider(), true, tool.envPathVarName(), @@ -225,4 +225,7 @@ public record ToolProperties( boolean failOnMissing, boolean installIfMissing) { } + + public record MatchedPath(Path path, ToolProperties properties, Provider provider, Candidate candidate) { + } } diff --git a/env-manager/src/test/java/io/yupiik/dev/provider/central/CentralBaseProviderTest.java b/env-manager/src/test/java/io/yupiik/dev/provider/central/CentralBaseProviderTest.java index 2f761874..83eeeae1 100644 --- a/env-manager/src/test/java/io/yupiik/dev/provider/central/CentralBaseProviderTest.java +++ b/env-manager/src/test/java/io/yupiik/dev/provider/central/CentralBaseProviderTest.java @@ -31,6 +31,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -115,6 +116,6 @@ private CentralBaseProvider newProvider(final URI uri, final YemHttpClient clien return new CentralBaseProvider( client, new CentralConfiguration(uri.toASCIIString(), local.toString(), ""), new Archives(), new Cache(new HttpConfiguration(false, 10_000, 1, false, 30_000L, 30_000L, 0, "none"), null), - Gav.of("org.foo:bar:tar.gz:simple"), true); + Gav.of("org.foo:bar:tar.gz:simple"), true, Map.of()); } } diff --git a/env-manager/src/test/java/io/yupiik/dev/provider/sdkman/SdkManClientTest.java b/env-manager/src/test/java/io/yupiik/dev/provider/sdkman/SdkManClientTest.java index c2e6cef4..0512a712 100644 --- a/env-manager/src/test/java/io/yupiik/dev/provider/sdkman/SdkManClientTest.java +++ b/env-manager/src/test/java/io/yupiik/dev/provider/sdkman/SdkManClientTest.java @@ -31,6 +31,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.stream.Stream; @@ -88,15 +89,18 @@ void listTools(final URI uri, @TempDir final Path work, final YemHttpClient clie new Candidate( "activemq", "Apache ActiveMQ (Classic)", // "5.17.1", "Apache ActiveMQ® is a popular open source, multi-protocol, Java-based message broker. It supports industry standard protocols so users get the benefits of client choices across a broad range of languages and platforms. Connect from clients written in JavaScript, C, C++, Python, .Net, and more. Integrate your multi-platform applications using the ubiquitous AMQP protocol. Exchange messages between your web applications using STOMP over websockets. Manage your IoT devices using MQTT. Support your existing JMS infrastructure and beyond. ActiveMQ offers the power and flexibility to support any messaging use-case.", - "https://activemq.apache.org/"), + "https://activemq.apache.org/", + Map.of()), new Candidate( "java", "Java", // "221-zulu-tem", "Java Platform, Standard Edition (or Java SE) is a widely used platform for development and deployment of portable code for desktop and server environments. Java SE uses the object-oriented Java programming language. It is part of the Java software-platform family. Java SE defines a wide range of general-purpose APIs – such as Java APIs for the Java Class Library – and also includes the Java Language Specification and the Java Virtual Machine Specification.", - "https://projects.eclipse.org/projects/adoptium.temurin/"), + "https://projects.eclipse.org/projects/adoptium.temurin/", + Map.of("emoji", "☕")), new Candidate( "maven", "Maven", // "3.9.6", "Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.", - "https://maven.apache.org/")); + "https://maven.apache.org/", + Map.of("emoji", "\uD83E\uDD89"))); assertEquals(expected, actual); }