This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new b6169df6e2 GROOVY-12004: grape command line tool should accept
maven/ivy shorthands
b6169df6e2 is described below
commit b6169df6e27dfff9672348305e4332b165ac589b
Author: Paul King <[email protected]>
AuthorDate: Mon May 11 07:08:07 2026 +1000
GROOVY-12004: grape command line tool should accept maven/ivy shorthands
---
.../org/codehaus/groovy/tools/GrapeMain.groovy | 30 +++++++++++---
src/main/java/groovy/grape/Grape.java | 21 +++++++++-
.../java/org/codehaus/groovy/tools/GrapeUtil.java | 14 ++++++-
.../org/codehaus/groovy/tools/GrapeUtilTest.java | 48 ++++++++++++++++++++++
4 files changed, 104 insertions(+), 9 deletions(-)
diff --git a/src/main/groovy/org/codehaus/groovy/tools/GrapeMain.groovy
b/src/main/groovy/org/codehaus/groovy/tools/GrapeMain.groovy
index 491757e6c6..a0009a9944 100644
--- a/src/main/groovy/org/codehaus/groovy/tools/GrapeMain.groovy
+++ b/src/main/groovy/org/codehaus/groovy/tools/GrapeMain.groovy
@@ -150,18 +150,19 @@ class GrapeMain implements Runnable {
}
@Command(name = 'install', header = 'Installs a particular grape',
- description = 'Installs the specified groovy module or maven
artifact. If a version is specified that specific version will be installed,
otherwise the most recent version will be used (as if `*` was passed in).')
+ description = ['Installs the specified groovy module or maven
artifact. If a version is specified that specific version will be installed,
otherwise the most recent version will be used (as if `*` was passed in).',
+ 'Accepts three coordinate forms: `group module
[version] [classifier]` (positional), `group:module:version[:classifier][@ext]`
(Maven shorthand), or `group#module;version` (Ivy shorthand).'])
private static class Install implements Runnable {
/**
- * Module group to install.
+ * Module group to install, or a full coordinate in Maven/Ivy
shorthand.
*/
- @Parameters(index = '0', arity = '1', description = 'Which module
group the module comes from. Translates directly to a Maven groupId or an Ivy
Organization. Any group matching /groovy[x][\\..*]^/ is reserved and may have
special meaning to the groovy endorsed modules.')
+ @Parameters(index = '0', arity = '1', description = 'Either the module
group (Maven groupId / Ivy organisation), or a full coordinate in Maven
shorthand `g:m:v[:c][@e]` or Ivy shorthand `g#m;v`. Any group matching
/groovy[x][\\..*]^/ is reserved and may have special meaning to the groovy
endorsed modules.')
String group
/**
- * Module name to install.
+ * Module name to install. Optional when a shorthand coordinate is
supplied as the first parameter.
*/
- @Parameters(index = '1', arity = '1', description = 'The name of the
module to load. Translated directly to a Maven artifactId or an Ivy artifact.')
+ @Parameters(index = '1', arity = '0..1', description = 'The name of
the module to load. Translated directly to a Maven artifactId or an Ivy
artifact. Omit when supplying a shorthand coordinate.')
String module
/**
@@ -203,7 +204,24 @@ class GrapeMain implements Runnable {
throw new CommandLine.ExecutionException(new
CommandLine(this), "Grape engine not initialized")
}
- def result = engine.grab(autoDownload: true, group: group, module:
module, version: version, classifier: classifier, noExceptions: true)
+ // If the first positional carries Maven/Ivy shorthand separators,
parse it and let any
+ // explicit later positionals override what the shorthand supplied.
+ String g = group, m = module, v = version, c = classifier
+ if (g != null && (g.indexOf(':') >= 0 || g.indexOf('#') >= 0)) {
+ Map<String, Object> parts = GrapeUtil.getIvyParts(g)
+ if (parts.get('group') && parts.get('module')) {
+ g = parts.group
+ if (!m) m = parts.module as String
+ if (v == '*' && parts.version) v = parts.version as String
+ if (!c && parts.classifier) c = parts.classifier as String
+ }
+ }
+ if (!m) {
+ System.err.println "Missing module: pass three positionals or
a shorthand like 'g:m:v'"
+ throw new CommandLine.ExecutionException(new
CommandLine(this), "Missing module")
+ }
+
+ def result = engine.grab(autoDownload: true, group: g, module: m,
version: v, classifier: c, noExceptions: true)
if (result instanceof Exception) {
System.err.println "Error grabbing Grapes -- ${result.message}"
throw new CommandLine.ExecutionException(new
CommandLine(this), "Failed to install grape", result)
diff --git a/src/main/java/groovy/grape/Grape.java
b/src/main/java/groovy/grape/Grape.java
index cde00194b3..654696c241 100644
--- a/src/main/java/groovy/grape/Grape.java
+++ b/src/main/java/groovy/grape/Grape.java
@@ -18,6 +18,8 @@
*/
package groovy.grape;
+import org.codehaus.groovy.tools.GrapeUtil;
+
import java.net.URI;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -226,14 +228,29 @@ public class Grape {
}
/**
- * Grabs a dependency expressed using the endorsed module shorthand.
+ * Grabs a dependency expressed as a single string.
+ * <p>
+ * Recognized forms:
+ * <ul>
+ * <li>Maven shorthand: {@code
group:module:version[:classifier][@ext]}</li>
+ * <li>Ivy shorthand: {@code group#module;version}</li>
+ * <li>Endorsed module: a bare module name (legacy; resolves under the
+ * {@code groovy.endorsed} group at the current Groovy version)</li>
+ * </ul>
*
- * @param endorsed the endorsed module notation
+ * @param endorsed the dependency notation
*/
public static void grab(String endorsed) {
if (enableGrapes) {
GrapeEngine instance = getInstance();
if (instance != null) {
+ if (endorsed != null && (endorsed.indexOf(':') >= 0 ||
endorsed.indexOf('#') >= 0)) {
+ Map<String, Object> parts =
GrapeUtil.getIvyParts(endorsed);
+ if (parts.get("group") != null && parts.get("module") !=
null) {
+ grab(parts);
+ return;
+ }
+ }
instance.grab(endorsed);
}
}
diff --git a/src/main/java/org/codehaus/groovy/tools/GrapeUtil.java
b/src/main/java/org/codehaus/groovy/tools/GrapeUtil.java
index 7a0ed4f793..3289289a8c 100644
--- a/src/main/java/org/codehaus/groovy/tools/GrapeUtil.java
+++ b/src/main/java/org/codehaus/groovy/tools/GrapeUtil.java
@@ -26,8 +26,14 @@ import java.util.Map;
*/
public class GrapeUtil {
/**
- * Parses a dependency coordinate in Ivy/Grape shorthand form into its
+ * Parses a dependency coordinate in Maven or Ivy shorthand form into its
* component parts.
+ * <p>
+ * Recognized forms:
+ * <ul>
+ * <li>Maven: {@code group:module:version[:classifier][@ext]}</li>
+ * <li>Ivy: {@code group#module;version} (translated internally to the
Maven form before parsing)</li>
+ * </ul>
*
* @param allstr the dependency coordinate to parse
* @return a map containing any parsed {@code group}, {@code module},
@@ -35,6 +41,12 @@ public class GrapeUtil {
*/
public static Map<String, Object> getIvyParts(String allstr) {
Map<String, Object> result = new LinkedHashMap<String, Object>();
+ if (allstr == null) return result;
+ // Accept the Ivy shorthand "group#module;version" by translating its
separators to
+ // the Maven form before parsing. Neither '#' nor ';' is legal in a
groupId, artifactId,
+ // or version (Ivy ranges use '[]()' not '#;'), so a straight replace
cannot collide
+ // with the Maven form.
+ allstr = allstr.replace('#', ':').replace(';', ':');
String ext = "";
String[] parts;
if (allstr.contains("@")) {
diff --git a/src/test/groovy/org/codehaus/groovy/tools/GrapeUtilTest.java
b/src/test/groovy/org/codehaus/groovy/tools/GrapeUtilTest.java
index 6596f66ea4..ebd8134648 100644
--- a/src/test/groovy/org/codehaus/groovy/tools/GrapeUtilTest.java
+++ b/src/test/groovy/org/codehaus/groovy/tools/GrapeUtilTest.java
@@ -47,4 +47,52 @@ public class GrapeUtilTest extends TestCase {
assert ivyParts.size() == 4;
}
+ public void testMavenShorthand_groupModuleVersion(){
+ Map<String, Object> parts =
GrapeUtil.getIvyParts("com.example:foo:1.2.3");
+ assert "com.example".equals(parts.get("group"));
+ assert "foo".equals(parts.get("module"));
+ assert "1.2.3".equals(parts.get("version"));
+ }
+
+ public void testMavenShorthand_versionDefaultsToWildcard(){
+ Map<String, Object> parts = GrapeUtil.getIvyParts("com.example:foo");
+ assert "com.example".equals(parts.get("group"));
+ assert "foo".equals(parts.get("module"));
+ assert "*".equals(parts.get("version"));
+ }
+
+ public void testMavenShorthand_withClassifierAndExt(){
+ Map<String, Object> parts =
GrapeUtil.getIvyParts("com.example:foo:1.2.3:jdk15@zip");
+ assert "com.example".equals(parts.get("group"));
+ assert "foo".equals(parts.get("module"));
+ assert "1.2.3".equals(parts.get("version"));
+ assert "jdk15".equals(parts.get("classifier"));
+ assert "zip".equals(parts.get("ext"));
+ }
+
+ public void testIvyShorthand_groupModuleVersion(){
+ Map<String, Object> parts =
GrapeUtil.getIvyParts("com.example#foo;1.2.3");
+ assert "com.example".equals(parts.get("group"));
+ assert "foo".equals(parts.get("module"));
+ assert "1.2.3".equals(parts.get("version"));
+ }
+
+ public void testIvyShorthand_dottedAndHyphenatedNames(){
+ Map<String, Object> parts =
GrapeUtil.getIvyParts("org.apache.commons#commons-lang3;3.9");
+ assert "org.apache.commons".equals(parts.get("group"));
+ assert "commons-lang3".equals(parts.get("module"));
+ assert "3.9".equals(parts.get("version"));
+ }
+
+ public void testIvyShorthand_versionRange(){
+ Map<String, Object> parts =
GrapeUtil.getIvyParts("com.example#foo;[1.0,2.0)");
+ assert "com.example".equals(parts.get("group"));
+ assert "foo".equals(parts.get("module"));
+ assert "[1.0,2.0)".equals(parts.get("version"));
+ }
+
+ public void testNullInput_returnsEmpty(){
+ Map<String, Object> parts = GrapeUtil.getIvyParts(null);
+ assert parts.isEmpty();
+ }
}