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 {
}
}
else if (Commands.class.isAssignableFrom(type)) {
Map<String, Closure<?>> commands = ((Commands) type.newInstance())
.getCommands();
Commands instance = (Commands) type.newInstance();
Map<String, Closure<?>> commands = instance.getCommands();
Map<String, OptionHandler> handlers = instance.getOptions();
for (String command : commands.keySet()) {
this.cli.register(new ScriptCommand(command, commands
.get(command)));
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
.get(command)));
}
}
}
else if (Script.class.isAssignableFrom(type)) {
......@@ -161,6 +172,8 @@ public class InitCommand extends OptionParsingCommand {
public static interface Commands {
Map<String, Closure<?>> getCommands();
Map<String, OptionHandler> getOptions();
}
}
......@@ -51,7 +51,7 @@ public class OptionHandler {
private OptionParser parser;
private Closure<Void> closure;
private Closure<?> closure;
private String help;
......@@ -74,12 +74,9 @@ public class OptionHandler {
}
protected void options() {
if (this.closure != null) {
this.closure.call();
}
}
public void setOptions(Closure<Void> closure) {
public void setClosure(Closure<?> closure) {
this.closure = closure;
}
......@@ -100,6 +97,9 @@ public class OptionHandler {
* @throws Exception
*/
protected void run(OptionSet options) throws Exception {
if (this.closure != null) {
this.closure.call(options);
}
}
public String getHelp() {
......
......@@ -71,10 +71,37 @@ public class InitCommandTests {
@Test
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));
}
@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)
public void initNonExistentScript() throws Exception {
this.command.run("nonexistent.groovy");
......
......@@ -18,7 +18,6 @@ package org.springframework.boot.cli.command;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.OutputCapture;
......@@ -65,15 +64,14 @@ public class ScriptCommandTests {
public void handler() throws Exception {
this.init.run("src/test/resources/commands/handler.groovy");
this.cli.find("foo").run("Foo", "--foo=bar");
assertTrue(executed);
assertTrue(this.output.toString().contains("Hello [Foo]"));
}
@Test
@Ignore
public void options() throws Exception {
this.init.run("src/test/resources/commands/options.groovy");
this.cli.find("foo").run("Foo", "--foo=bar");
assertTrue(executed);
assertTrue(this.output.toString().contains("Hello Foo"));
}
}
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package cli.command;
package org.springframework.boot.cli.command;
import groovy.lang.Closure;
......@@ -29,8 +29,6 @@ import org.junit.Test;
import org.springframework.boot.OutputCapture;
import org.springframework.boot.cli.Command;
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.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
......@@ -75,24 +73,55 @@ public class ScriptCompilationCustomizerTests {
@Test
public void addsCommands() throws Exception {
Class<?>[] types = this.compiler.compile(new File(
"src/test/resources/scripts/options.groovy"));
"src/test/resources/scripts/commands.groovy"));
Class<?> main = types[0];
assertTrue(Commands.class.isAssignableFrom(main));
}
@Test
public void commandsExecutable() throws Exception {
public void closureWithStringArgs() throws Exception {
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];
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(); // what about args?
closure.call();
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
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 @@
package org.test.command
import java.util.Collection;
class TestCommand implements Command {
String name = "foo"
......
......@@ -31,7 +31,6 @@ class TestCommand extends OptionHandler {
void run(OptionSet options) {
// Demonstrate use of Grape.grab to load dependencies before running
println "Clean: " + Git.open(".." as File).status().call().isClean()
org.springframework.boot.cli.command.ScriptCommandTests.executed = true
println "Hello ${options.nonOptionArguments()}: ${options.has('foo')}"
}
......
......@@ -14,8 +14,19 @@
* 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 @@
* limitations under the License.
*/
command("foo") { args ->
println "Hello Command"
command("foo") {
options {
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