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();
+    }
 }

Reply via email to