Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions src/main/groovy/org/codehaus/groovy/tools/GrapeMain.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 19 additions & 2 deletions src/main/java/groovy/grape/Grape.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -226,14 +228,29 @@ private static GrapeEngine createEngineFromProvider(final ServiceLoader.Provider
}

/**
* 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);
}
}
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/org/codehaus/groovy/tools/GrapeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,27 @@
*/
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},
* {@code version}, {@code classifier}, and {@code ext} entries
*/
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("@")) {
Expand Down
48 changes: 48 additions & 0 deletions src/test/groovy/org/codehaus/groovy/tools/GrapeUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,52 @@ public void testGetIvyParts4(){
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();
}
}
Loading