Skip to content

Commit b6169df

Browse files
committed
GROOVY-12004: grape command line tool should accept maven/ivy shorthands
1 parent 06645cd commit b6169df

4 files changed

Lines changed: 104 additions & 9 deletions

File tree

src/main/groovy/org/codehaus/groovy/tools/GrapeMain.groovy

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,19 @@ class GrapeMain implements Runnable {
150150
}
151151

152152
@Command(name = 'install', header = 'Installs a particular grape',
153-
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).')
153+
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).',
154+
'Accepts three coordinate forms: `group module [version] [classifier]` (positional), `group:module:version[:classifier][@ext]` (Maven shorthand), or `group#module;version` (Ivy shorthand).'])
154155
private static class Install implements Runnable {
155156
/**
156-
* Module group to install.
157+
* Module group to install, or a full coordinate in Maven/Ivy shorthand.
157158
*/
158-
@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.')
159+
@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.')
159160
String group
160161

161162
/**
162-
* Module name to install.
163+
* Module name to install. Optional when a shorthand coordinate is supplied as the first parameter.
163164
*/
164-
@Parameters(index = '1', arity = '1', description = 'The name of the module to load. Translated directly to a Maven artifactId or an Ivy artifact.')
165+
@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.')
165166
String module
166167

167168
/**
@@ -203,7 +204,24 @@ class GrapeMain implements Runnable {
203204
throw new CommandLine.ExecutionException(new CommandLine(this), "Grape engine not initialized")
204205
}
205206

206-
def result = engine.grab(autoDownload: true, group: group, module: module, version: version, classifier: classifier, noExceptions: true)
207+
// If the first positional carries Maven/Ivy shorthand separators, parse it and let any
208+
// explicit later positionals override what the shorthand supplied.
209+
String g = group, m = module, v = version, c = classifier
210+
if (g != null && (g.indexOf(':') >= 0 || g.indexOf('#') >= 0)) {
211+
Map<String, Object> parts = GrapeUtil.getIvyParts(g)
212+
if (parts.get('group') && parts.get('module')) {
213+
g = parts.group
214+
if (!m) m = parts.module as String
215+
if (v == '*' && parts.version) v = parts.version as String
216+
if (!c && parts.classifier) c = parts.classifier as String
217+
}
218+
}
219+
if (!m) {
220+
System.err.println "Missing module: pass three positionals or a shorthand like 'g:m:v'"
221+
throw new CommandLine.ExecutionException(new CommandLine(this), "Missing module")
222+
}
223+
224+
def result = engine.grab(autoDownload: true, group: g, module: m, version: v, classifier: c, noExceptions: true)
207225
if (result instanceof Exception) {
208226
System.err.println "Error grabbing Grapes -- ${result.message}"
209227
throw new CommandLine.ExecutionException(new CommandLine(this), "Failed to install grape", result)

src/main/java/groovy/grape/Grape.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
package groovy.grape;
2020

21+
import org.codehaus.groovy.tools.GrapeUtil;
22+
2123
import java.net.URI;
2224
import java.util.Collections;
2325
import java.util.LinkedHashMap;
@@ -226,14 +228,29 @@ private static GrapeEngine createEngineFromProvider(final ServiceLoader.Provider
226228
}
227229

228230
/**
229-
* Grabs a dependency expressed using the endorsed module shorthand.
231+
* Grabs a dependency expressed as a single string.
232+
* <p>
233+
* Recognized forms:
234+
* <ul>
235+
* <li>Maven shorthand: {@code group:module:version[:classifier][@ext]}</li>
236+
* <li>Ivy shorthand: {@code group#module;version}</li>
237+
* <li>Endorsed module: a bare module name (legacy; resolves under the
238+
* {@code groovy.endorsed} group at the current Groovy version)</li>
239+
* </ul>
230240
*
231-
* @param endorsed the endorsed module notation
241+
* @param endorsed the dependency notation
232242
*/
233243
public static void grab(String endorsed) {
234244
if (enableGrapes) {
235245
GrapeEngine instance = getInstance();
236246
if (instance != null) {
247+
if (endorsed != null && (endorsed.indexOf(':') >= 0 || endorsed.indexOf('#') >= 0)) {
248+
Map<String, Object> parts = GrapeUtil.getIvyParts(endorsed);
249+
if (parts.get("group") != null && parts.get("module") != null) {
250+
grab(parts);
251+
return;
252+
}
253+
}
237254
instance.grab(endorsed);
238255
}
239256
}

src/main/java/org/codehaus/groovy/tools/GrapeUtil.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,27 @@
2626
*/
2727
public class GrapeUtil {
2828
/**
29-
* Parses a dependency coordinate in Ivy/Grape shorthand form into its
29+
* Parses a dependency coordinate in Maven or Ivy shorthand form into its
3030
* component parts.
31+
* <p>
32+
* Recognized forms:
33+
* <ul>
34+
* <li>Maven: {@code group:module:version[:classifier][@ext]}</li>
35+
* <li>Ivy: {@code group#module;version} (translated internally to the Maven form before parsing)</li>
36+
* </ul>
3137
*
3238
* @param allstr the dependency coordinate to parse
3339
* @return a map containing any parsed {@code group}, {@code module},
3440
* {@code version}, {@code classifier}, and {@code ext} entries
3541
*/
3642
public static Map<String, Object> getIvyParts(String allstr) {
3743
Map<String, Object> result = new LinkedHashMap<String, Object>();
44+
if (allstr == null) return result;
45+
// Accept the Ivy shorthand "group#module;version" by translating its separators to
46+
// the Maven form before parsing. Neither '#' nor ';' is legal in a groupId, artifactId,
47+
// or version (Ivy ranges use '[]()' not '#;'), so a straight replace cannot collide
48+
// with the Maven form.
49+
allstr = allstr.replace('#', ':').replace(';', ':');
3850
String ext = "";
3951
String[] parts;
4052
if (allstr.contains("@")) {

src/test/groovy/org/codehaus/groovy/tools/GrapeUtilTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,52 @@ public void testGetIvyParts4(){
4747
assert ivyParts.size() == 4;
4848
}
4949

50+
public void testMavenShorthand_groupModuleVersion(){
51+
Map<String, Object> parts = GrapeUtil.getIvyParts("com.example:foo:1.2.3");
52+
assert "com.example".equals(parts.get("group"));
53+
assert "foo".equals(parts.get("module"));
54+
assert "1.2.3".equals(parts.get("version"));
55+
}
56+
57+
public void testMavenShorthand_versionDefaultsToWildcard(){
58+
Map<String, Object> parts = GrapeUtil.getIvyParts("com.example:foo");
59+
assert "com.example".equals(parts.get("group"));
60+
assert "foo".equals(parts.get("module"));
61+
assert "*".equals(parts.get("version"));
62+
}
63+
64+
public void testMavenShorthand_withClassifierAndExt(){
65+
Map<String, Object> parts = GrapeUtil.getIvyParts("com.example:foo:1.2.3:jdk15@zip");
66+
assert "com.example".equals(parts.get("group"));
67+
assert "foo".equals(parts.get("module"));
68+
assert "1.2.3".equals(parts.get("version"));
69+
assert "jdk15".equals(parts.get("classifier"));
70+
assert "zip".equals(parts.get("ext"));
71+
}
72+
73+
public void testIvyShorthand_groupModuleVersion(){
74+
Map<String, Object> parts = GrapeUtil.getIvyParts("com.example#foo;1.2.3");
75+
assert "com.example".equals(parts.get("group"));
76+
assert "foo".equals(parts.get("module"));
77+
assert "1.2.3".equals(parts.get("version"));
78+
}
79+
80+
public void testIvyShorthand_dottedAndHyphenatedNames(){
81+
Map<String, Object> parts = GrapeUtil.getIvyParts("org.apache.commons#commons-lang3;3.9");
82+
assert "org.apache.commons".equals(parts.get("group"));
83+
assert "commons-lang3".equals(parts.get("module"));
84+
assert "3.9".equals(parts.get("version"));
85+
}
86+
87+
public void testIvyShorthand_versionRange(){
88+
Map<String, Object> parts = GrapeUtil.getIvyParts("com.example#foo;[1.0,2.0)");
89+
assert "com.example".equals(parts.get("group"));
90+
assert "foo".equals(parts.get("module"));
91+
assert "[1.0,2.0)".equals(parts.get("version"));
92+
}
93+
94+
public void testNullInput_returnsEmpty(){
95+
Map<String, Object> parts = GrapeUtil.getIvyParts(null);
96+
assert parts.isEmpty();
97+
}
5098
}

0 commit comments

Comments
 (0)