Commit ac34f9c9 authored by Dave Syer's avatar Dave Syer

First proper draft of DSL for Groovy Commands

Users can declare or Command, OptionHandler classes in an init script
or they can use a DSL, e.g.

command("foo") { args -> println "Do stuff with ${args} array" }

or

command("foo") {
  options { option "bar", "Help text for bar option" ithOptionArg() ofType Integer }
  run { options -> println "Do stuff with ${options.valueOf('bar')}" }
}
parent 1e75c0a5
...@@ -117,11 +117,22 @@ public class InitCommand extends OptionParsingCommand { ...@@ -117,11 +117,22 @@ public class InitCommand extends OptionParsingCommand {
} }
} }
else if (Commands.class.isAssignableFrom(type)) { else if (Commands.class.isAssignableFrom(type)) {
Map<String, Closure<?>> commands = ((Commands) type.newInstance()) Commands instance = (Commands) type.newInstance();
.getCommands(); Map<String, Closure<?>> commands = instance.getCommands();
Map<String, OptionHandler> handlers = instance.getOptions();
for (String command : commands.keySet()) { for (String command : commands.keySet()) {
if (handlers.containsKey(command)) {
// An OptionHandler is available
OptionHandler handler = handlers.get(command);
handler.setClosure(commands.get(command));
this.cli.register(new ScriptCommand(command, handler));
}
else {
// Otherwise just a plain Closure
this.cli.register(new ScriptCommand(command, commands this.cli.register(new ScriptCommand(command, commands
.get(command))); .get(command)));
}
} }
} }
else if (Script.class.isAssignableFrom(type)) { else if (Script.class.isAssignableFrom(type)) {
...@@ -161,6 +172,8 @@ public class InitCommand extends OptionParsingCommand { ...@@ -161,6 +172,8 @@ public class InitCommand extends OptionParsingCommand {
public static interface Commands { public static interface Commands {
Map<String, Closure<?>> getCommands(); Map<String, Closure<?>> getCommands();
Map<String, OptionHandler> getOptions();
} }
} }
...@@ -51,7 +51,7 @@ public class OptionHandler { ...@@ -51,7 +51,7 @@ public class OptionHandler {
private OptionParser parser; private OptionParser parser;
private Closure<Void> closure; private Closure<?> closure;
private String help; private String help;
...@@ -74,12 +74,9 @@ public class OptionHandler { ...@@ -74,12 +74,9 @@ public class OptionHandler {
} }
protected void options() { protected void options() {
if (this.closure != null) {
this.closure.call();
}
} }
public void setOptions(Closure<Void> closure) { public void setClosure(Closure<?> closure) {
this.closure = closure; this.closure = closure;
} }
...@@ -100,6 +97,9 @@ public class OptionHandler { ...@@ -100,6 +97,9 @@ public class OptionHandler {
* @throws Exception * @throws Exception
*/ */
protected void run(OptionSet options) throws Exception { protected void run(OptionSet options) throws Exception {
if (this.closure != null) {
this.closure.call(options);
}
} }
public String getHelp() { public String getHelp() {
......
...@@ -71,10 +71,37 @@ public class InitCommandTests { ...@@ -71,10 +71,37 @@ public class InitCommandTests {
@Test @Test
public void initCommand() throws Exception { public void initCommand() throws Exception {
this.command.run("src/test/resources/command.groovy"); this.command.run("src/test/resources/commands/command.groovy");
verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class)); verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class));
} }
@Test
public void initHandler() throws Exception {
this.command.run("src/test/resources/commands/handler.groovy");
verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class));
}
@Test
public void initClosure() throws Exception {
this.command.run("src/test/resources/commands/closure.groovy");
verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class));
}
@Test
public void initOptions() throws Exception {
this.command.run("src/test/resources/commands/options.groovy");
verify(this.cli, times(this.defaultCount + 1)).register(any(Command.class));
}
@Test
public void runOptions() throws Exception {
SpringCli cli = new SpringCli();
InitCommand command = cli.getInitCommand();
command.run("src/test/resources/commands/options.groovy");
cli.find("foo").run("--foo=bar", "--bar=123");
assertTrue(this.output.toString().contains("Hello Foo: bar=123"));
}
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void initNonExistentScript() throws Exception { public void initNonExistentScript() throws Exception {
this.command.run("nonexistent.groovy"); this.command.run("nonexistent.groovy");
......
...@@ -18,7 +18,6 @@ package org.springframework.boot.cli.command; ...@@ -18,7 +18,6 @@ package org.springframework.boot.cli.command;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.OutputCapture; import org.springframework.boot.OutputCapture;
...@@ -65,15 +64,14 @@ public class ScriptCommandTests { ...@@ -65,15 +64,14 @@ public class ScriptCommandTests {
public void handler() throws Exception { public void handler() throws Exception {
this.init.run("src/test/resources/commands/handler.groovy"); this.init.run("src/test/resources/commands/handler.groovy");
this.cli.find("foo").run("Foo", "--foo=bar"); this.cli.find("foo").run("Foo", "--foo=bar");
assertTrue(executed); assertTrue(this.output.toString().contains("Hello [Foo]"));
} }
@Test @Test
@Ignore
public void options() throws Exception { public void options() throws Exception {
this.init.run("src/test/resources/commands/options.groovy"); this.init.run("src/test/resources/commands/options.groovy");
this.cli.find("foo").run("Foo", "--foo=bar"); this.cli.find("foo").run("Foo", "--foo=bar");
assertTrue(executed); assertTrue(this.output.toString().contains("Hello Foo"));
} }
} }
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package cli.command; package org.springframework.boot.cli.command;
import groovy.lang.Closure; import groovy.lang.Closure;
...@@ -29,8 +29,6 @@ import org.junit.Test; ...@@ -29,8 +29,6 @@ import org.junit.Test;
import org.springframework.boot.OutputCapture; import org.springframework.boot.OutputCapture;
import org.springframework.boot.cli.Command; import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.command.InitCommand.Commands; import org.springframework.boot.cli.command.InitCommand.Commands;
import org.springframework.boot.cli.command.OptionHandler;
import org.springframework.boot.cli.command.ScriptCompilationCustomizer;
import org.springframework.boot.cli.compiler.GroovyCompiler; import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerScope; import org.springframework.boot.cli.compiler.GroovyCompilerScope;
...@@ -75,24 +73,55 @@ public class ScriptCompilationCustomizerTests { ...@@ -75,24 +73,55 @@ public class ScriptCompilationCustomizerTests {
@Test @Test
public void addsCommands() throws Exception { public void addsCommands() throws Exception {
Class<?>[] types = this.compiler.compile(new File( Class<?>[] types = this.compiler.compile(new File(
"src/test/resources/scripts/options.groovy")); "src/test/resources/scripts/commands.groovy"));
Class<?> main = types[0]; Class<?> main = types[0];
assertTrue(Commands.class.isAssignableFrom(main)); assertTrue(Commands.class.isAssignableFrom(main));
} }
@Test @Test
public void commandsExecutable() throws Exception { public void closureWithStringArgs() throws Exception {
Class<?>[] types = this.compiler.compile(new File( Class<?>[] types = this.compiler.compile(new File(
"src/test/resources/scripts/options.groovy")); "src/test/resources/scripts/commands.groovy"));
Class<?> main = types[0];
Map<String, Closure<?>> commands = ((Commands) main.newInstance()).getCommands();
assertEquals(1, commands.size());
assertEquals("foo", commands.keySet().iterator().next());
Closure<?> closure = commands.values().iterator().next();
closure.call("foo", "bar");
assertTrue(this.output.toString().contains("Hello Command"));
}
@Test
public void closureWithEmptyArgs() throws Exception {
Class<?>[] types = this.compiler.compile(new File(
"src/test/resources/scripts/commands.groovy"));
Class<?> main = types[0]; Class<?> main = types[0];
Map<String, Closure<?>> commands = ((Commands) main.newInstance()).getCommands(); Map<String, Closure<?>> commands = ((Commands) main.newInstance()).getCommands();
assertEquals(1, commands.size()); assertEquals(1, commands.size());
assertEquals("foo", commands.keySet().iterator().next()); assertEquals("foo", commands.keySet().iterator().next());
Closure<?> closure = commands.values().iterator().next(); Closure<?> closure = commands.values().iterator().next();
closure.call(); // what about args? closure.call();
assertTrue(this.output.toString().contains("Hello Command")); assertTrue(this.output.toString().contains("Hello Command"));
} }
@Test
public void closureAndOptionsDefined() throws Exception {
Class<?>[] types = this.compiler.compile(new File(
"src/test/resources/scripts/options.groovy"));
Class<?> main = types[0];
Commands commands = (Commands) main.newInstance();
Map<String, Closure<?>> closures = commands.getCommands();
assertEquals(1, closures.size());
assertEquals("foo", closures.keySet().iterator().next());
final Closure<?> closure = closures.values().iterator().next();
Map<String, OptionHandler> options = commands.getOptions();
assertEquals(1, options.size());
OptionHandler handler = options.get("foo");
handler.setClosure(closure);
handler.run("--foo=bar", "--bar=blah", "spam");
assertTrue(this.output.toString().contains("Hello [spam]: true blah"));
}
private static class TestGroovyCompilerConfiguration implements private static class TestGroovyCompilerConfiguration implements
GroovyCompilerConfiguration { GroovyCompilerConfiguration {
......
class MyCommand implements Command {
String name = "foo"
String description = "My script command"
String help = "No options"
String usageHelp = "Not very useful"
Collection<String> optionsHelp = ["No options"]
boolean optionCommand = false
void run(String... args) {
println "Hello ${args[0]}"
}
}
\ No newline at end of file
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
command("foo") { args ->
println "Hello Foo"
}
...@@ -16,8 +16,6 @@ ...@@ -16,8 +16,6 @@
package org.test.command package org.test.command
import java.util.Collection;
class TestCommand implements Command { class TestCommand implements Command {
String name = "foo" String name = "foo"
......
...@@ -31,7 +31,6 @@ class TestCommand extends OptionHandler { ...@@ -31,7 +31,6 @@ class TestCommand extends OptionHandler {
void run(OptionSet options) { void run(OptionSet options) {
// Demonstrate use of Grape.grab to load dependencies before running // Demonstrate use of Grape.grab to load dependencies before running
println "Clean: " + Git.open(".." as File).status().call().isClean() println "Clean: " + Git.open(".." as File).status().call().isClean()
org.springframework.boot.cli.command.ScriptCommandTests.executed = true
println "Hello ${options.nonOptionArguments()}: ${options.has('foo')}" println "Hello ${options.nonOptionArguments()}: ${options.has('foo')}"
} }
......
...@@ -14,8 +14,19 @@ ...@@ -14,8 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
command("foo") { args -> def foo() {
"Foo"
}
command("foo") {
options {
option "foo", "A foo of type String"
option "bar", "Bar has a value" withOptionalArg() ofType Integer
}
run { options ->
println "Hello ${foo()}: bar=${options.valueOf('bar')}"
}
org.springframework.boot.cli.command.ScriptCommandTests.executed = true
println "Hello ${options.nonOptionArguments()}: ${options.has('foo')} ${options.valueOf('bar')}"
} }
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
def foo() {
"Foo"
}
command("foo") { options ->
def foo = foo()
println "Hello ${foo} ${options.nonOptionArguments()}: ${options.has('foo')} ${options.valueOf('bar')}"
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
command("foo") { args ->
println "Hello Command"
}
...@@ -14,8 +14,10 @@ ...@@ -14,8 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
command("foo") { args -> command("foo") {
options {
println "Hello Command" option "foo", "Some foo description" withOptionalArg()
option "bar", "Some bar" withOptionalArg()
}
run { options -> println "Hello ${options.nonOptionArguments()}: ${options.has('foo')} ${options.valueOf('bar')}" }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment