diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CleanCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CleanCommand.java deleted file mode 100644 index 37eedc3fce..0000000000 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CleanCommand.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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. - */ -package org.springframework.bootstrap.cli; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; - -import joptsimple.OptionParser; -import joptsimple.OptionSet; -import joptsimple.OptionSpec; - -import org.apache.ivy.util.FileUtil; - -/** - * {@link Command} to 'clean' up grapes, removing cached dependencies and forcing a - * download on the next attempt to resolve. - * - * @author Dave Syer - * - */ -public class CleanCommand extends OptionParsingCommand { - - private static enum Layout { - IVY, MAVEN; - } - - private OptionSpec allOption; - - private OptionSpec ivyOption; - - private OptionSpec mvnOption; - - public CleanCommand() { - super("clean", - "Clean up groovy grapes (useful if snapshots are needed and you need an update)"); - } - - @Override - public String getUsageHelp() { - return "[options] "; - } - - @Override - protected OptionParser createOptionParser() { - OptionParser parser = new OptionParser(); - this.allOption = parser.accepts("all", "Clean all files (not just snapshots)"); - this.ivyOption = parser.accepts("ivy", - "Clean just ivy (grapes) cache. Default is on unless --maven is used."); - this.mvnOption = parser.accepts("maven", - "Clean just maven cache. Default is off."); - return parser; - } - - @Override - protected void run(OptionSet options) throws Exception { - if (!options.has(this.ivyOption)) { - clean(options, getGrapesHome(options), Layout.IVY); - } - if (options.has(this.mvnOption)) { - if (options.has(this.ivyOption)) { - clean(options, getGrapesHome(options), Layout.IVY); - } - clean(options, getMavenHome(options), Layout.MAVEN); - } - } - - private void clean(OptionSet options, File root, Layout layout) { - - if (root == null || !root.exists()) { - return; - } - - ArrayList specs = new ArrayList(options.nonOptionArguments()); - if (!specs.contains("org.springframework.bootstrap") && layout == Layout.IVY) { - specs.add(0, "org.springframework.bootstrap"); - } - for (String spec : specs) { - String group = spec; - String module = null; - if (spec.contains(":")) { - group = spec.substring(0, spec.indexOf(":")); - module = spec.substring(spec.indexOf(":") + 1); - } - File file = getModulePath(root, group, module, layout); - if (file.exists()) { - if (options.has(this.allOption) - || group.equals("org.springframework.bootstrap")) { - System.out.println("Deleting: " + file); - FileUtil.forceDelete(file); - } else { - for (Object obj : FileUtil.listAll(file, Collections.emptyList())) { - File candidate = (File) obj; - if (candidate.getName().contains("SNAPSHOT")) { - System.out.println("Deleting: " + candidate); - FileUtil.forceDelete(candidate); - } - } - } - } - } - } - - private File getModulePath(File root, String group, String module, Layout layout) { - File parent = root; - if (layout == Layout.IVY) { - parent = new File(parent, group); - } else { - for (String path : group.split("\\.")) { - parent = new File(parent, path); - } - } - - if (module == null) { - return parent; - } - return new File(parent, module); - } - - private File getGrapesHome(OptionSet options) { - - String dir = System.getenv("GROOVY_HOME"); - String userdir = System.getProperty("user.home"); - - File home; - if (dir == null || !new File(dir).exists()) { - dir = userdir; - home = new File(dir, ".groovy"); - } else { - home = new File(dir); - } - if (dir == null || !new File(dir).exists()) { - return null; - } - - File grapes = new File(home, "grapes"); - return grapes; - } - - private File getMavenHome(OptionSet options) { - String dir = System.getProperty("user.home"); - - if (dir == null || !new File(dir).exists()) { - return null; - } - File home = new File(dir); - File grapes = new File(new File(home, ".m2"), "repository"); - return grapes; - } -} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/NoArgumentsException.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CommandFactory.java similarity index 78% rename from spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/NoArgumentsException.java rename to spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CommandFactory.java index bcd5716096..1202311b2d 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/NoArgumentsException.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CommandFactory.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.bootstrap.cli; +import java.util.Collection; + /** - * Exception thrown when no CLI options are specified. + * @author Dave Syer * - * @author Phillip Webb */ -class NoArgumentsException extends BootstrapCliException { +public interface CommandFactory { - private static final long serialVersionUID = 1L; + Collection getCommands(); } diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java deleted file mode 100644 index b38bdde565..0000000000 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/RunCommand.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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. - */ - -package org.springframework.bootstrap.cli; - -import java.awt.Desktop; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - -import joptsimple.OptionParser; -import joptsimple.OptionSet; -import joptsimple.OptionSpec; - -import org.springframework.bootstrap.cli.runner.BootstrapRunner; -import org.springframework.bootstrap.cli.runner.BootstrapRunnerConfiguration; - -import static java.util.Arrays.asList; - -/** - * {@link Command} to 'run' a groovy script or scripts. - * - * @author Phillip Webb - * @author Dave Syer - * - * @see BootstrapRunner - */ -public class RunCommand extends OptionParsingCommand { - - private OptionSpec watchOption; - - private OptionSpec editOption; - - private OptionSpec noGuessImportsOption; - - private OptionSpec noGuessDependenciesOption; - - private OptionSpec verboseOption; - - private OptionSpec quietOption; - - private OptionSpec localOption; - - private BootstrapRunner runner; - - public RunCommand() { - super("run", "Run a spring groovy script"); - } - - @Override - public String getUsageHelp() { - return "[options] [--] [args]"; - } - - public void stop() { - if (this.runner != null) { - this.runner.stop(); - } - } - - @Override - protected OptionParser createOptionParser() { - OptionParser parser = new OptionParser(); - this.watchOption = parser - .accepts("watch", "Watch the specified file for changes"); - this.localOption = parser.accepts("local", - "Accumulate the dependencies in a local folder (./grapes)"); - this.editOption = parser.acceptsAll(asList("edit", "e"), - "Open the file with the default system editor"); - this.noGuessImportsOption = parser.accepts("no-guess-imports", - "Do not attempt to guess imports"); - this.noGuessDependenciesOption = parser.accepts("no-guess-dependencies", - "Do not attempt to guess dependencies"); - this.verboseOption = parser.acceptsAll(asList("verbose", "v"), "Verbose logging"); - this.quietOption = parser.acceptsAll(asList("quiet", "q"), "Quiet logging"); - return parser; - } - - @Override - protected void run(OptionSet options) throws Exception { - List nonOptionArguments = options.nonOptionArguments(); - File[] files = getFileArguments(nonOptionArguments); - List args = nonOptionArguments.subList(files.length, - nonOptionArguments.size()); - - if (options.has(this.editOption)) { - Desktop.getDesktop().edit(files[0]); - } - - BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter( - options); - if (configuration.isLocal() && System.getProperty("grape.root") == null) { - System.setProperty("grape.root", "."); - } - this.runner = new BootstrapRunner(configuration, files, - args.toArray(new String[args.size()])); - this.runner.compileAndRun(); - } - - private File[] getFileArguments(List nonOptionArguments) { - List files = new ArrayList(); - for (String filename : nonOptionArguments) { - if ("--".equals(filename)) { - break; - } - // TODO: add support for strict Java compilation - // TODO: add support for recursive search in directory - if (filename.endsWith(".groovy") || filename.endsWith(".java")) { - File file = new File(filename); - if (file.isFile() && file.canRead()) { - files.add(file); - } - } - } - if (files.size() == 0) { - throw new RuntimeException("Please specify a file to run"); - } - return files.toArray(new File[files.size()]); - } - - /** - * Simple adapter class to present the {@link OptionSet} as a - * {@link BootstrapRunnerConfiguration}. - */ - private class BootstrapRunnerConfigurationAdapter implements - BootstrapRunnerConfiguration { - - private OptionSet options; - - public BootstrapRunnerConfigurationAdapter(OptionSet options) { - this.options = options; - } - - @Override - public boolean isWatchForFileChanges() { - return this.options.has(RunCommand.this.watchOption); - } - - @Override - public boolean isGuessImports() { - return !this.options.has(RunCommand.this.noGuessImportsOption); - } - - @Override - public boolean isGuessDependencies() { - return !this.options.has(RunCommand.this.noGuessDependenciesOption); - } - - @Override - public boolean isLocal() { - return this.options.has(RunCommand.this.localOption); - } - - @Override - public Level getLogLevel() { - if (this.options.has(RunCommand.this.verboseOption)) { - return Level.FINEST; - } - if (this.options.has(RunCommand.this.quietOption)) { - return Level.OFF; - } - return Level.INFO; - } - - } -} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/SpringBootstrapCli.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/SpringBootstrapCli.java index c4721becb6..f5841e0d31 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/SpringBootstrapCli.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/SpringBootstrapCli.java @@ -16,10 +16,13 @@ package org.springframework.bootstrap.cli; +import java.io.IOException; +import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.List; +import java.util.ServiceLoader; import java.util.Set; /** @@ -29,14 +32,12 @@ import java.util.Set; * *

* The '-d' and '--debug' switches are handled by this class, however, most argument - * parsing is left to the {@link Command} implementation. The {@link OptionParsingCommand} - * class provides a convenient base for command that need to parse arguments. + * parsing is left to the {@link Command} implementation. * * @author Phillip Webb * @see #main(String...) * @see BootstrapCliException * @see Command - * @see OptionParsingCommand */ public class SpringBootstrapCli { @@ -52,8 +53,17 @@ public class SpringBootstrapCli { * commands. */ public SpringBootstrapCli() { - setCommands(Arrays.asList(new VersionCommand(), new RunCommand(), - new CreateCommand(), new CleanCommand())); + setCommands(ServiceLoader.load(CommandFactory.class, getClass().getClassLoader())); + } + + private void setCommands(Iterable iterable) { + this.commands = new ArrayList(); + for (CommandFactory factory : iterable) { + for (Command command : factory.getCommands()) { + this.commands.add(command); + } + } + this.commands.add(0, new HelpCommand()); } /** @@ -61,7 +71,7 @@ public class SpringBootstrapCli { * 'help' command will be automatically provided in addition to this list. * @param commands the commands to add */ - protected void setCommands(List commands) { + public void setCommands(List commands) { this.commands = new ArrayList(commands); this.commands.add(0, new HelpCommand()); } @@ -172,11 +182,7 @@ public class SpringBootstrapCli { /** * Internal {@link Command} used for 'help' and '--help' requests. */ - private class HelpCommand extends AbstractCommand { - - public HelpCommand() { - super("help", "Show command help", true); - } + private class HelpCommand implements Command { @Override public void run(String... args) throws Exception { @@ -201,6 +207,46 @@ public class SpringBootstrapCli { throw new NoSuchCommandException(commandName); } + @Override + public String getName() { + return "help"; + } + + @Override + public boolean isOptionCommand() { + return false; + } + + @Override + public String getDescription() { + return "Get help on commands"; + } + + @Override + public String getUsageHelp() { + return null; + } + + @Override + public void printHelp(PrintStream out) throws IOException { + } + + } + + static class NoHelpCommandArgumentsException extends BootstrapCliException { + + private static final long serialVersionUID = 1L; + + public NoHelpCommandArgumentsException() { + super(Option.SHOW_USAGE); + } + + } + + static class NoArgumentsException extends BootstrapCliException { + + private static final long serialVersionUID = 1L; + } /** diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/AbstractCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/AbstractCommand.java similarity index 95% rename from spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/AbstractCommand.java rename to spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/AbstractCommand.java index 945b01aff5..10c1f24880 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/AbstractCommand.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/AbstractCommand.java @@ -14,11 +14,13 @@ * limitations under the License. */ -package org.springframework.bootstrap.cli; +package org.springframework.bootstrap.cli.command; import java.io.IOException; import java.io.PrintStream; +import org.springframework.bootstrap.cli.Command; + /** * Abstract {@link Command} implementation. * diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/CleanCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/CleanCommand.java new file mode 100644 index 0000000000..62649cf3cc --- /dev/null +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/CleanCommand.java @@ -0,0 +1,167 @@ +/* + * 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. + */ +package org.springframework.bootstrap.cli.command; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +import org.apache.ivy.util.FileUtil; +import org.springframework.bootstrap.cli.Command; + +/** + * {@link Command} to 'clean' up grapes, removing cached dependencies and forcing a + * download on the next attempt to resolve. + * + * @author Dave Syer + * + */ +public class CleanCommand extends OptionParsingCommand { + + public CleanCommand() { + super( + "clean", + "Clean up groovy grapes (useful if snapshots are needed and you need an update)", + new CleanOptionHandler()); + } + + @Override + public String getUsageHelp() { + return "[options] "; + } + + private static class CleanOptionHandler extends OptionHandler { + + private static enum Layout { + IVY, MAVEN; + } + + private OptionSpec allOption; + + private OptionSpec ivyOption; + + private OptionSpec mvnOption; + + @Override + protected void options() { + this.allOption = option("all", "Clean all files (not just snapshots)"); + this.ivyOption = option("ivy", + "Clean just ivy (grapes) cache. Default is on unless --maven is used."); + this.mvnOption = option("maven", "Clean just maven cache. Default is off."); + } + + @Override + protected void run(OptionSet options) throws Exception { + if (!options.has(this.ivyOption)) { + clean(options, getGrapesHome(options), Layout.IVY); + } + if (options.has(this.mvnOption)) { + if (options.has(this.ivyOption)) { + clean(options, getGrapesHome(options), Layout.IVY); + } + clean(options, getMavenHome(options), Layout.MAVEN); + } + } + + private void clean(OptionSet options, File root, Layout layout) { + + if (root == null || !root.exists()) { + return; + } + + ArrayList specs = new ArrayList(options.nonOptionArguments()); + if (!specs.contains("org.springframework.bootstrap") && layout == Layout.IVY) { + specs.add(0, "org.springframework.bootstrap"); + } + for (String spec : specs) { + String group = spec; + String module = null; + if (spec.contains(":")) { + group = spec.substring(0, spec.indexOf(":")); + module = spec.substring(spec.indexOf(":") + 1); + } + File file = getModulePath(root, group, module, layout); + if (file.exists()) { + if (options.has(this.allOption) + || group.equals("org.springframework.bootstrap")) { + System.out.println("Deleting: " + file); + FileUtil.forceDelete(file); + } else { + for (Object obj : FileUtil.listAll(file, Collections.emptyList())) { + File candidate = (File) obj; + if (candidate.getName().contains("SNAPSHOT")) { + System.out.println("Deleting: " + candidate); + FileUtil.forceDelete(candidate); + } + } + } + } + } + } + + private File getModulePath(File root, String group, String module, Layout layout) { + File parent = root; + if (layout == Layout.IVY) { + parent = new File(parent, group); + } else { + for (String path : group.split("\\.")) { + parent = new File(parent, path); + } + } + + if (module == null) { + return parent; + } + return new File(parent, module); + } + + private File getGrapesHome(OptionSet options) { + + String dir = System.getenv("GROOVY_HOME"); + String userdir = System.getProperty("user.home"); + + File home; + if (dir == null || !new File(dir).exists()) { + dir = userdir; + home = new File(dir, ".groovy"); + } else { + home = new File(dir); + } + if (dir == null || !new File(dir).exists()) { + return null; + } + + File grapes = new File(home, "grapes"); + return grapes; + } + + private File getMavenHome(OptionSet options) { + String dir = System.getProperty("user.home"); + + if (dir == null || !new File(dir).exists()) { + return null; + } + File home = new File(dir); + File grapes = new File(new File(home, ".m2"), "repository"); + return grapes; + } + + } + +} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CreateCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/CreateCommand.java similarity index 57% rename from spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CreateCommand.java rename to spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/CreateCommand.java index 186697b093..f375f83d0b 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/CreateCommand.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/CreateCommand.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package org.springframework.bootstrap.cli; +package org.springframework.bootstrap.cli.command; -import joptsimple.OptionParser; import joptsimple.OptionSet; -import static java.util.Arrays.*; +import org.springframework.bootstrap.cli.Command; + +import static java.util.Arrays.asList; /** * {@link Command} to 'create' a new spring groovy script. @@ -29,7 +30,7 @@ import static java.util.Arrays.*; public class CreateCommand extends OptionParsingCommand { public CreateCommand() { - super("create", "Create an new spring groovy script"); + super("create", "Create an new spring groovy script", new CreateOptionHandler()); } @Override @@ -37,18 +38,20 @@ public class CreateCommand extends OptionParsingCommand { return "[options] "; } - @Override - protected OptionParser createOptionParser() { - OptionParser parser = new OptionParser(); - parser.acceptsAll(asList("overwite", "f"), "Overwrite any existing file"); - parser.accepts("type", "Create a specific application type").withOptionalArg() - .ofType(String.class).describedAs("web, batch, integration"); - return parser; - } + private static class CreateOptionHandler extends OptionHandler { + + @Override + protected void options() { + option(asList("overwite", "f"), "Overwrite any existing file"); + option("type", "Create a specific application type").withOptionalArg() + .ofType(String.class).describedAs("web, batch, integration"); + } + + @Override + protected void run(OptionSet options) { + throw new IllegalStateException("Not implemented"); // FIXME + } - @Override - protected void run(OptionSet options) { - throw new IllegalStateException("Not implemented"); // FIXME } } diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/NoHelpCommandArgumentsException.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/DefaultCommandFactory.java similarity index 54% rename from spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/NoHelpCommandArgumentsException.java rename to spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/DefaultCommandFactory.java index 88b6a5a1b8..f7d4de8f25 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/NoHelpCommandArgumentsException.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/DefaultCommandFactory.java @@ -13,20 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.springframework.bootstrap.cli.command; -package org.springframework.bootstrap.cli; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.bootstrap.cli.Command; +import org.springframework.bootstrap.cli.CommandFactory; /** - * Exception thrown when the 'help' command is issued without any arguments. + * @author Dave Syer * - * @author Phillip Webb */ -class NoHelpCommandArgumentsException extends BootstrapCliException { +public class DefaultCommandFactory implements CommandFactory { - private static final long serialVersionUID = 1L; + private static final List DEFAULT_COMMANDS = Arrays. asList( + new VersionCommand(), new RunCommand(), new CleanCommand()); - public NoHelpCommandArgumentsException() { - super(Option.SHOW_USAGE); + @Override + public Collection getCommands() { + return DEFAULT_COMMANDS; } } diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/OptionHandler.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/OptionHandler.java new file mode 100644 index 0000000000..ccab872681 --- /dev/null +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/OptionHandler.java @@ -0,0 +1,63 @@ +/* + * 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. + */ +package org.springframework.bootstrap.cli.command; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; + +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpecBuilder; + +/** + * @author Dave Syer + * + */ +public abstract class OptionHandler { + + private OptionParser parser; + + public OptionSpecBuilder option(String name, String description) { + return getParser().accepts(name, description); + } + + public OptionSpecBuilder option(Collection aliases, String description) { + return getParser().acceptsAll(aliases, description); + } + + private OptionParser getParser() { + if (this.parser == null) { + this.parser = new OptionParser(); + options(); + } + return this.parser; + } + + protected abstract void options(); + + public final void run(String... args) throws Exception { + OptionSet options = getParser().parse(args); + run(options); + } + + protected abstract void run(OptionSet options) throws Exception; + + public void printHelp(PrintStream out) throws IOException { + getParser().printHelpOn(out); + } + +} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/OptionParsingCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/OptionParsingCommand.java similarity index 72% rename from spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/OptionParsingCommand.java rename to spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/OptionParsingCommand.java index 948498f400..39b7b1493a 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/OptionParsingCommand.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/OptionParsingCommand.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package org.springframework.bootstrap.cli; +package org.springframework.bootstrap.cli.command; import java.io.IOException; import java.io.PrintStream; import joptsimple.OptionParser; -import joptsimple.OptionSet; + +import org.springframework.bootstrap.cli.Command; /** * Base class for any {@link Command}s that use an {@link OptionParser}. @@ -29,26 +30,25 @@ import joptsimple.OptionSet; */ public abstract class OptionParsingCommand extends AbstractCommand { - private OptionParser parser; + private OptionHandler handler; - public OptionParsingCommand(String name, String description) { + public OptionParsingCommand(String name, String description, OptionHandler handler) { super(name, description); - this.parser = createOptionParser(); + this.handler = handler; } - protected abstract OptionParser createOptionParser(); - @Override public void printHelp(PrintStream out) throws IOException { - this.parser.printHelpOn(out); + this.handler.printHelp(out); } @Override public final void run(String... args) throws Exception { - OptionSet options = parser.parse(args); - run(options); + this.handler.run(args); } - protected abstract void run(OptionSet options) throws Exception; + protected OptionHandler getHandler() { + return this.handler; + } } diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/RunCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/RunCommand.java new file mode 100644 index 0000000000..8edaa73e49 --- /dev/null +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/RunCommand.java @@ -0,0 +1,181 @@ +/* + * 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. + */ + +package org.springframework.bootstrap.cli.command; + +import java.awt.Desktop; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +import org.springframework.bootstrap.cli.Command; +import org.springframework.bootstrap.cli.runner.BootstrapRunner; +import org.springframework.bootstrap.cli.runner.BootstrapRunnerConfiguration; + +import static java.util.Arrays.asList; + +/** + * {@link Command} to 'run' a groovy script or scripts. + * + * @author Phillip Webb + * @author Dave Syer + * + * @see BootstrapRunner + */ +public class RunCommand extends OptionParsingCommand { + + public RunCommand() { + super("run", "Run a spring groovy script", new RunOptionHandler()); + } + + @Override + public String getUsageHelp() { + return "[options] [--] [args]"; + } + + public void stop() { + if (this.getHandler() != null) { + ((RunOptionHandler) this.getHandler()).runner.stop(); + } + } + + private static class RunOptionHandler extends OptionHandler { + + private OptionSpec watchOption; + + private OptionSpec editOption; + + private OptionSpec noGuessImportsOption; + + private OptionSpec noGuessDependenciesOption; + + private OptionSpec verboseOption; + + private OptionSpec quietOption; + + private OptionSpec localOption; + + private BootstrapRunner runner; + + @Override + protected void options() { + this.watchOption = option("watch", "Watch the specified file for changes"); + this.localOption = option("local", + "Accumulate the dependencies in a local folder (./grapes)"); + this.editOption = option(asList("edit", "e"), + "Open the file with the default system editor"); + this.noGuessImportsOption = option("no-guess-imports", + "Do not attempt to guess imports"); + this.noGuessDependenciesOption = option("no-guess-dependencies", + "Do not attempt to guess dependencies"); + this.verboseOption = option(asList("verbose", "v"), "Verbose logging"); + this.quietOption = option(asList("quiet", "q"), "Quiet logging"); + } + + @Override + protected void run(OptionSet options) throws Exception { + List nonOptionArguments = options.nonOptionArguments(); + File[] files = getFileArguments(nonOptionArguments); + List args = nonOptionArguments.subList(files.length, + nonOptionArguments.size()); + + if (options.has(this.editOption)) { + Desktop.getDesktop().edit(files[0]); + } + + BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter( + options); + if (configuration.isLocal() && System.getProperty("grape.root") == null) { + System.setProperty("grape.root", "."); + } + this.runner = new BootstrapRunner(configuration, files, + args.toArray(new String[args.size()])); + this.runner.compileAndRun(); + } + + private File[] getFileArguments(List nonOptionArguments) { + List files = new ArrayList(); + for (String filename : nonOptionArguments) { + if ("--".equals(filename)) { + break; + } + // TODO: add support for strict Java compilation + // TODO: add support for recursive search in directory + if (filename.endsWith(".groovy") || filename.endsWith(".java")) { + File file = new File(filename); + if (file.isFile() && file.canRead()) { + files.add(file); + } + } + } + if (files.size() == 0) { + throw new RuntimeException("Please specify a file to run"); + } + return files.toArray(new File[files.size()]); + } + + /** + * Simple adapter class to present the {@link OptionSet} as a + * {@link BootstrapRunnerConfiguration}. + */ + private class BootstrapRunnerConfigurationAdapter implements + BootstrapRunnerConfiguration { + + private OptionSet options; + + public BootstrapRunnerConfigurationAdapter(OptionSet options) { + this.options = options; + } + + @Override + public boolean isWatchForFileChanges() { + return this.options.has(RunOptionHandler.this.watchOption); + } + + @Override + public boolean isGuessImports() { + return !this.options.has(RunOptionHandler.this.noGuessImportsOption); + } + + @Override + public boolean isGuessDependencies() { + return !this.options.has(RunOptionHandler.this.noGuessDependenciesOption); + } + + @Override + public boolean isLocal() { + return this.options.has(RunOptionHandler.this.localOption); + } + + @Override + public Level getLogLevel() { + if (this.options.has(RunOptionHandler.this.verboseOption)) { + return Level.FINEST; + } + if (this.options.has(RunOptionHandler.this.quietOption)) { + return Level.OFF; + } + return Level.INFO; + } + + } + } + +} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/ScriptCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/ScriptCommand.java new file mode 100644 index 0000000000..26653026ad --- /dev/null +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/ScriptCommand.java @@ -0,0 +1,166 @@ +/* + * 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. + */ +package org.springframework.bootstrap.cli.command; + +import groovy.lang.Script; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URL; + +import org.apache.ivy.util.FileUtil; +import org.codehaus.groovy.control.CompilationFailedException; +import org.springframework.bootstrap.cli.Command; +import org.springframework.bootstrap.cli.compiler.GroovyCompiler; +import org.springframework.bootstrap.cli.compiler.GroovyCompilerConfiguration; + +/** + * {@link Command} to run a script. + * + * @author Dave Syer + * + */ +public class ScriptCommand extends AbstractCommand { + + private static String[] DEFAULT_PATHS = new String[] { "${SPRING_HOME}/ext", + "${SPRING_HOME}/bin" }; + + private String[] paths = DEFAULT_PATHS; + + private Class mainClass; + + private Object main; + + public ScriptCommand(String name, String description) { + super(name, description); + } + + @Override + public void printHelp(PrintStream out) throws IOException { + if (getMain() instanceof OptionHandler) { + ((OptionHandler) getMain()).printHelp(out); + } + } + + @Override + public void run(String... args) throws Exception { + if (getMain() instanceof OptionHandler) { + ((OptionHandler) getMain()).run(args); + } else if (this.main instanceof Runnable) { + ((Runnable) this.main).run(); + } else if (this.main instanceof Script) { + Script script = (Script) this.main; + script.setProperty("args", args); + script.run(); + } + } + + /** + * Paths to search for script files. + * + * @param paths the paths to set + */ + public void setPaths(String[] paths) { + this.paths = paths; + } + + @Override + public String getUsageHelp() { + return "[options] "; + } + + protected Object getMain() { + if (this.main == null) { + try { + this.main = getMainClass().newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Cannot create main class: " + getName(), + e); + } + } + return this.main; + } + + private void compile() { + GroovyCompiler compiler = new GroovyCompiler(new ScriptConfiguration()); + compiler.addCompilationCustomizers(new ScriptCompilationCustomizer()); + File source = locateSource(getName()); + Class[] classes; + try { + classes = compiler.compile(source); + } catch (CompilationFailedException e) { + throw new IllegalStateException("Could not compile script", e); + } catch (IOException e) { + throw new IllegalStateException("Could not compile script", e); + } + this.mainClass = classes[0]; + } + + private Class getMainClass() { + if (this.mainClass == null) { + compile(); + } + return this.mainClass; + } + + private File locateSource(String name) { + String resource = "commands/" + name + ".groovy"; + URL url = getClass().getClassLoader().getResource(resource); + File file = null; + if (url != null) { + try { + file = File.createTempFile(name, ".groovy"); + FileUtil.copy(url, file, null); + } catch (IOException e) { + throw new IllegalStateException("Could not create temp file for source: " + + name); + } + } else { + String home = System.getProperty("SPRING_HOME", System.getenv("SPRING_HOME")); + if (home == null) { + home = "."; + } + for (String path : this.paths) { + String subbed = path.replace("${SPRING_HOME}", home); + File test = new File(subbed, resource); + if (test.exists()) { + file = test; + break; + } + } + } + if (file == null) { + throw new IllegalStateException("No script found for : " + name); + } + return file; + } + + private static class ScriptConfiguration implements GroovyCompilerConfiguration { + + @Override + public boolean isGuessImports() { + return true; + } + + @Override + public boolean isGuessDependencies() { + return true; + } + + } + +} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/ScriptCompilationCustomizer.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/ScriptCompilationCustomizer.java new file mode 100644 index 0000000000..bfbda0d782 --- /dev/null +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/ScriptCompilationCustomizer.java @@ -0,0 +1,50 @@ +/* + * 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. + */ +package org.springframework.bootstrap.cli.command; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.customizers.CompilationCustomizer; +import org.codehaus.groovy.control.customizers.ImportCustomizer; + +/** + * @author Dave Syer + * + */ +public class ScriptCompilationCustomizer extends CompilationCustomizer { + + public ScriptCompilationCustomizer() { + super(CompilePhase.CONVERSION); + } + + @Override + public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) + throws CompilationFailedException { + // AnnotationNode mixin = new AnnotationNode(ClassHelper.make(Mixin.class)); + // mixin.addMember("value", + // new ClassExpression(ClassHelper.make(OptionHandler.class))); + // classNode.addAnnotation(mixin); + ImportCustomizer importCustomizer = new ImportCustomizer(); + importCustomizer.addImports("joptsimple.OptionParser", "joptsimple.OptionSet", + OptionParsingCommand.class.getCanonicalName(), + OptionHandler.class.getCanonicalName()); + importCustomizer.call(source, context, classNode); + } + +} diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/VersionCommand.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/VersionCommand.java similarity index 90% rename from spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/VersionCommand.java rename to spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/VersionCommand.java index 3d9201cf82..821d8edc72 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/VersionCommand.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/command/VersionCommand.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package org.springframework.bootstrap.cli; +package org.springframework.bootstrap.cli.command; + +import org.springframework.bootstrap.cli.Command; /** * {@link Command} to display the 'version' number. diff --git a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java index 60182b350f..611e0842e9 100644 --- a/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java +++ b/spring-bootstrap-cli/src/main/java/org/springframework/bootstrap/cli/compiler/GroovyCompiler.java @@ -72,15 +72,17 @@ public class GroovyCompiler { CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); this.loader = new ExtendedGroovyClassLoader(getClass().getClassLoader(), compilerConfiguration); - // FIXME: allow the extra resolvers to be switched on (off by default) addExtraResolvers(); - compilerConfiguration .addCompilationCustomizers(new CompilerAutoConfigureCustomizer()); } - public Object[] sources(File[] files) throws CompilationFailedException, IOException { + public void addCompilationCustomizers(CompilationCustomizer... customizers) { + this.loader.getConfiguration().addCompilationCustomizers(customizers); + } + + public Object[] sources(File... files) throws CompilationFailedException, IOException { List compilables = new ArrayList(); List others = new ArrayList(); for (File file : files) { @@ -125,6 +127,18 @@ public class GroovyCompiler { for (Object loadedClass : collector.getLoadedClasses()) { classes.add((Class) loadedClass); } + ClassNode mainClassNode = (ClassNode) compilationUnit.getAST().getClasses() + .get(0); + Class mainClass = null; + for (Class loadedClass : classes) { + if (mainClassNode.getName().equals(loadedClass.getName())) { + mainClass = loadedClass; + } + } + if (mainClass != null) { + classes.remove(mainClass); + classes.add(0, mainClass); + } return classes.toArray(new Class[classes.size()]); diff --git a/spring-bootstrap-cli/src/main/resources/META-INF/services/org.springframework.bootstrap.cli.CommandFactory b/spring-bootstrap-cli/src/main/resources/META-INF/services/org.springframework.bootstrap.cli.CommandFactory new file mode 100644 index 0000000000..08f8bcb548 --- /dev/null +++ b/spring-bootstrap-cli/src/main/resources/META-INF/services/org.springframework.bootstrap.cli.CommandFactory @@ -0,0 +1 @@ +org.springframework.bootstrap.cli.command.DefaultCommandFactory \ No newline at end of file diff --git a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java index d59a3b1807..c3638ca871 100644 --- a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java +++ b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SampleIntegrationTests.java @@ -28,6 +28,7 @@ import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.springframework.bootstrap.cli.command.RunCommand; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SpringBootstrapCliTests.java b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SpringBootstrapCliTests.java index ce21456b4b..5551dba98c 100644 --- a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SpringBootstrapCliTests.java +++ b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/SpringBootstrapCliTests.java @@ -11,6 +11,8 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.bootstrap.cli.SpringBootstrapCli.NoArgumentsException; +import org.springframework.bootstrap.cli.SpringBootstrapCli.NoHelpCommandArgumentsException; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; diff --git a/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/command/ScriptCommandTests.java b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/command/ScriptCommandTests.java new file mode 100644 index 0000000000..9ed2b5d983 --- /dev/null +++ b/spring-bootstrap-cli/src/test/java/org/springframework/bootstrap/cli/command/ScriptCommandTests.java @@ -0,0 +1,54 @@ +/* + * 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. + */ +package org.springframework.bootstrap.cli.command; + +import groovy.lang.Script; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + * + */ +public class ScriptCommandTests { + + public static boolean executed = false; + + @Test + public void testScript() throws Exception { + ScriptCommand command = new ScriptCommand("script", "Run a test command"); + command.run("World"); + assertEquals("World", + ((String[]) ((Script) command.getMain()).getProperty("args"))[0]); + } + + @Test + public void testOptions() throws Exception { + ScriptCommand command = new ScriptCommand("test", "Run a test command"); + command.run("World", "--foo"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ((OptionHandler) command.getMain()).printHelp(new PrintStream(out)); + assertTrue("Wrong output: " + out, out.toString().contains("--foo")); + assertTrue(executed); + } + +} diff --git a/spring-bootstrap-cli/src/test/resources/commands/script.groovy b/spring-bootstrap-cli/src/test/resources/commands/script.groovy new file mode 100644 index 0000000000..1346ff5ec5 --- /dev/null +++ b/spring-bootstrap-cli/src/test/resources/commands/script.groovy @@ -0,0 +1 @@ +println "Hello ${args[0]}" diff --git a/spring-bootstrap-cli/src/test/resources/commands/test.groovy b/spring-bootstrap-cli/src/test/resources/commands/test.groovy new file mode 100644 index 0000000000..4f9131ff5d --- /dev/null +++ b/spring-bootstrap-cli/src/test/resources/commands/test.groovy @@ -0,0 +1,19 @@ +package org.test.command + +@Grab("org.eclipse.jgit:org.eclipse.jgit:2.3.1.201302201838-r") +import org.eclipse.jgit.api.Git + +class TestCommand extends OptionHandler { + + void options() { + option "foo", "Foo set" + } + + 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.bootstrap.cli.command.ScriptCommandTests.executed = true + println "Hello ${options.nonOptionArguments()}: ${options.has('foo')}" + } + +}