diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index fd04c23a0c224..536ac80b6d753 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -3,10 +3,12 @@ import static io.quarkus.maven.utilities.MojoUtils.readPom; import java.io.IOException; -import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; import org.apache.maven.model.Dependency; @@ -74,7 +76,22 @@ static SelectionResult select(String query, List extensions, boolean matchesLabels = extensions.stream() .filter(extension -> extension.labels().contains(q)).collect(Collectors.toList()); } else { - matchesLabels = new ArrayList<>(); + matchesLabels = Collections.emptyList(); + } + + // find by pattern + Set matchesPatterns; + Pattern pattern = toRegex(q); + if (pattern != null) { + matchesPatterns = extensions.stream() + .filter(extension -> pattern.matcher(extension.getName().toLowerCase()).matches() + || pattern.matcher(extension.getArtifactId().toLowerCase()).matches() + || pattern.matcher(extension.getShortName().toLowerCase()).matches() + || matchLabels(pattern, extension.getLabels())) + .collect(Collectors.toSet()); + return new SelectionResult(matchesPatterns, true); + } else { + matchesPatterns = Collections.emptySet(); } Set candidates = new LinkedHashSet<>(); @@ -82,9 +99,76 @@ static SelectionResult select(String query, List extensions, boolean candidates.addAll(matchesShortName); candidates.addAll(partialMatches); candidates.addAll(matchesLabels); + candidates.addAll(matchesPatterns); return new SelectionResult(candidates, false); } + private static boolean matchLabels(Pattern pattern, String[] labels) { + boolean matches = false; + // if any label match it's ok + for (int i = 0; i < labels.length; i++) { + matches = matches | pattern.matcher(labels[i].toLowerCase()).matches(); + } + return matches; + } + + private static Pattern toRegex(final String str) { + try { + String wildcardToRegex = wildcardToRegex(str); + if (wildcardToRegex != null && !wildcardToRegex.isEmpty()) { + return Pattern.compile(wildcardToRegex); + } + } catch (PatternSyntaxException e) { + //ignore it + } + return null; + } + + public static String wildcardToRegex(String wildcard) { + if (wildcard == null || wildcard.isEmpty()) { + return null; + } + // don't try with file match char in pattern + if (!(wildcard.contains("*") || wildcard.contains("?"))) { + return null; + } + StringBuffer s = new StringBuffer(wildcard.length()); + s.append("^.*"); + for (int i = 0, is = wildcard.length(); i < is; i++) { + char c = wildcard.charAt(i); + switch (c) { + case '*': + s.append(".*"); + break; + case '?': + s.append("."); + break; + case '^': // escape character in cmd.exe + s.append("\\"); + break; + // escape special regexp-characters + case '(': + case ')': + case '[': + case ']': + case '$': + case '.': + case '{': + case '}': + case '|': + case '\\': + s.append("\\"); + s.append(c); + break; + default: + s.append(c); + break; + } + } + s.append(".*$"); + return (s.toString()); + } + private static boolean matchesShortName(Extension extension, String q) { return q.equalsIgnoreCase(extension.getShortName()); } @@ -131,9 +215,10 @@ public AddExtensionResult addExtensions(final Set extensions) throws IOE success = false; } } else { // Matches. - final Extension extension = result.getMatch(); - // Don't set success to false even if the dependency is not added; as it's should be idempotent. - updated = buildFile.addDependency(dependenciesFromBom, extension) || updated; + for (Extension extension : result) { + // Don't set success to false even if the dependency is not added; as it's should be idempotent. + updated = buildFile.addDependency(dependenciesFromBom, extension) || updated; + } } } } diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java b/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java index f46e5740684b4..f7acb878a5e7b 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java @@ -1,10 +1,12 @@ package io.quarkus.cli.commands; +import java.util.Collections; +import java.util.Iterator; import java.util.Set; import io.quarkus.dependencies.Extension; -public class SelectionResult { +public class SelectionResult implements Iterable { private final Set extensions; private final boolean matches; @@ -22,13 +24,11 @@ public boolean matches() { return matches; } - public Extension getMatch() { + @Override + public Iterator iterator() { if (matches) { - if (extensions.isEmpty() || extensions.size() > 1) { - throw new IllegalStateException("Invalid selection result"); - } - return extensions.iterator().next(); + return extensions.iterator(); } - return null; + return Collections.emptyIterator(); } } diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java index dbdec663158f8..a5096b17797c9 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java @@ -64,6 +64,38 @@ void testPartialMatches() throws IOException { hasDependency(model, "quarkus-jdbc-postgresql"); } + @Test + void testRegexpMatches() throws IOException { + File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile())) + .addExtensions(new HashSet<>(asList("Sm??lRye**"))); + + result.isUpdated(); + + Model model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-smallrye-reactive-messaging"); + hasDependency(model, "quarkus-smallrye-reactive-streams-operators"); + hasDependency(model, "quarkus-smallrye-opentracing"); + hasDependency(model, "quarkus-smallrye-metrics"); + hasDependency(model, "quarkus-smallrye-reactive-messaging-kafka"); + hasDependency(model, "quarkus-smallrye-health"); + hasDependency(model, "quarkus-smallrye-openapi"); + hasDependency(model, "quarkus-smallrye-jwt"); + hasDependency(model, "quarkus-smallrye-context-propagation"); + hasDependency(model, "quarkus-smallrye-reactive-type-converters"); + hasDependency(model, "quarkus-smallrye-reactive-messaging-amqp"); + hasDependency(model, "quarkus-smallrye-fault-tolerance"); + } + @Test void addMissingExtension() throws IOException { final File pom = new File("target/extensions-test", "pom.xml"); @@ -276,8 +308,9 @@ void testShortNameSelection() { SelectionResult matches = AddExtensions.select("foo", extensions, false); Assertions.assertTrue(matches.matches()); Assertions.assertEquals(1, matches.getExtensions().size()); - Assertions.assertNotNull(matches.getMatch()); - Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("some-complex-seo-unaware-artifactid")); + Assertions.assertTrue(matches.iterator().hasNext()); + Assertions + .assertTrue(matches.iterator().next().getArtifactId().equalsIgnoreCase("some-complex-seo-unaware-artifactid")); } @Test @@ -297,8 +330,8 @@ void testArtifactIdSelectionWithQuarkusPrefix() { Collections.shuffle(extensions); SelectionResult matches = AddExtensions.select("foo", extensions, false); Assertions.assertEquals(1, matches.getExtensions().size()); - Assertions.assertNotNull(matches.getMatch()); - Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("quarkus-foo")); + Assertions.assertTrue(matches.iterator().hasNext()); + Assertions.assertTrue(matches.iterator().next().getArtifactId().equalsIgnoreCase("quarkus-foo")); } @Test diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index 4228c2d403480..03fcf5bf4feaa 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -18,8 +18,9 @@ luckily setting up a Quarkus project with Gradle is very simple. You only need t ---- plugins { id 'java' - id 'io.quarkus' version '{quarkus-version}' } + +apply plugin: 'io.quarkus' ---- or, if you use the Gradle Kotlin DSL: @@ -28,8 +29,9 @@ or, if you use the Gradle Kotlin DSL: ---- plugins { java - id("io.quarkus") version "{quarkus-version}" } + +apply(plugin = "io.quarkus") ---- === Gradle configuration for a local SNAPSHOT version of Quarkus @@ -87,8 +89,8 @@ Here's the same build script, using the Gradle Kotlin DSL: ---- plugins { java - id("io.quarkus") version "{quarkus-version}" } +apply(plugin = "io.quarkus") repositories { mavenCentral() @@ -186,7 +188,40 @@ From inside a Quarkus project, you can obtain a list of the available extensions ./gradlew listExtensions ---- -Functionality to automatically add extensions to your Gradle project is not implemented yet (coming soon). +You can enable an extension using: + +[source,shell] +---- +./gradlew addExtension --extensions="hibernate-validator" +---- + +Extensions are passed using a comma-separated list. + +The extension name is the GAV name of the extension: e.g. `io.quarkus:quarkus-agroal`. +But you can pass a partial name and Quarkus will do its best to find the right extension. +For example, `agroal`, `Agroal` or `agro` will expand to `io.quarkus:quarkus-agroal`. +If no extension is found or if more than one extensions match, you will see a red check mark ❌ in the command result. + +[source,shell] +---- +./gradlew addExtension --extensions="jdbc,agroal,non-exist-ent" +[...] +❌ Multiple extensions matching 'jdbc' + * io.quarkus:quarkus-jdbc-h2 + * io.quarkus:quarkus-jdbc-mariadb + * io.quarkus:quarkus-jdbc-postgresql + Be more specific e.g using the exact name or the full gav. +✅ Adding extension io.quarkus:quarkus-agroal +❌ Cannot find a dependency matching 'non-exist-ent', maybe a typo? +[...] +---- + +You can install all extensions wich match a globbing pattern : + +[source,shell] +---- +./gradlew addExtension --extensions="hibernate*" +---- == Development mode diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index 066298baa233c..ad9636f651a60 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -104,6 +104,13 @@ $ ./mvnw quarkus:add-extensions -Dextensions=jdbc,agroal,non-exist-ent [...] ---- +You can install all extensions wich match a globbing pattern : + +[source,shell] +---- +./mvnw quarkus:add-extension -Dextensions="hibernate-*" +---- + == Development mode Quarkus comes with a built-in development mode.