package com.netflix.governator.commons_cli.modules;
import java.lang.annotation.Annotation;
import java.util.List;
import javax.inject.Inject;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.name.Names;
import com.netflix.governator.annotations.binding.Main;
import com.netflix.governator.commons_cli.providers.StringOptionProvider;
/**
* Guicify Apache Commons CLI.
*
* Usages
*
* <pre>
* {code
*
* // When creating Guice
*
* install(new OptionsModule() {
* protected void configure() {
* option("f")
* .hasArg()
* .withLongOpt("filename")
* .annotatedWith(Filename.class); // no need to call create()
*
* }
* })
*
* // Inject into any class
*
* @Singleton
* public class MyService {
* @Inject
* public MyService(@Filename String filename) {
* }
* }
*
* // You can also inject CommandLine directly
*
* @Singleton
* public class MyService {
* @Inject
* public MyService(CommandLine commandLine) {
* }
* }
*
* }
* </pre>
*
* @author elandau
*
*/
public abstract class OptionsModule extends AbstractModule {
private static final Logger LOG = LoggerFactory.getLogger(OptionsModule.class);
private List<OptionBuilder> builders = Lists.newArrayList();
private boolean parserIsBound = false;
/**
* Non-static version of commons CLI OptionBuilder
*
* @author elandau
*/
protected class OptionBuilder {
private String longopt;
private String description;
private String argName;
private boolean required;
private int numberOfArgs = Option.UNINITIALIZED;
private Object type;
private boolean optionalArg;
private char valuesep;
private String shortopt;
private String defaultValue;
private Class<? extends Annotation> annot;
public OptionBuilder annotatedWith(Class<? extends Annotation> annot) {
this.annot = annot;
return this;
}
public OptionBuilder withLongOpt(String longopt) {
this.longopt = longopt;
return this;
}
public OptionBuilder withShortOpt(char shortopt) {
this.shortopt = Character.toString(shortopt);
return this;
}
public OptionBuilder hasArg() {
this.numberOfArgs = 1;
return this;
}
public OptionBuilder hasArg(boolean hasArg) {
this.numberOfArgs = hasArg ? 1 : Option.UNINITIALIZED;
return this;
}
public OptionBuilder withArgName(String name) {
this.argName = name;
return this;
}
public OptionBuilder isRequired() {
this.required = true;
return this;
}
public OptionBuilder withValueSeparator(char sep) {
this.valuesep = sep;
return this;
}
public OptionBuilder withValueSeparator() {
this.valuesep = '=';
return this;
}
public OptionBuilder isRequired(boolean newRequired) {
this.required = newRequired;
return this;
}
public OptionBuilder hasArgs() {
this.numberOfArgs = Option.UNLIMITED_VALUES;
return this;
}
public OptionBuilder hasArgs(int num) {
this.numberOfArgs = num;
return this;
}
public OptionBuilder hasOptionalArg() {
this.numberOfArgs = 1;
this.optionalArg = true;
return this;
}
public OptionBuilder hasOptionalArgs() {
this.numberOfArgs = Option.UNLIMITED_VALUES;
this.optionalArg = true;
return this;
}
public OptionBuilder hasOptionalArgs(int numArgs) {
this.numberOfArgs = numArgs;
this.optionalArg = true;
return this;
}
public OptionBuilder withType(Object newType) {
this.type = newType;
return this;
}
public OptionBuilder withDescription(String newDescription) {
this.description = newDescription;
return this;
}
public OptionBuilder withDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
return this;
}
Option create() throws IllegalArgumentException
{
Preconditions.checkNotNull(shortopt);
Option option = null;
// create the option
option = new Option(shortopt, description);
// set the option properties
option.setLongOpt(longopt);
option.setRequired(required);
option.setOptionalArg(optionalArg);
option.setArgs(numberOfArgs);
option.setType(type);
option.setValueSeparator(valuesep);
option.setArgName(argName);
// return the Option instance
return option;
}
}
/**
* On injection of CommandLine execute the BasicParser
* @author elandau
*/
@Singleton
public static class CommandLineProvider implements Provider<CommandLine> {
private final Options options;
private final String[] arguments;
private final Parser parser;
@Inject
public CommandLineProvider(Options options, @Main String[] arguments, Parser parser) {
this.options = options;
this.arguments = arguments;
this.parser = parser;
}
@Override
public CommandLine get() {
try {
return parser.parse(options, arguments);
} catch (ParseException e) {
throw new ProvisionException("Error parsing command line arguments", e);
}
}
}
@Override
protected final void configure() {
configureOptions();
Options options = new Options();
for (OptionBuilder builder : builders) {
Option option = builder.create();
if (builder.annot != null) {
bind(String.class)
.annotatedWith(builder.annot)
.toProvider(new StringOptionProvider(option, builder.defaultValue))
.asEagerSingleton();
LOG.info("Binding option to annotation : " + builder.annot.getName());
}
else {
bind(String.class)
.annotatedWith(Names.named(option.getOpt()))
.toProvider(new StringOptionProvider(option, builder.defaultValue))
.asEagerSingleton();
LOG.info("Binding option to String : " + option.getOpt());
}
options.addOption(option);
}
bind(Options.class).toInstance(options);
bind(CommandLine.class).toProvider(CommandLineProvider.class);
if (!parserIsBound) {
bindParser().to(BasicParser.class);
}
}
protected abstract void configureOptions();
/**
* @param shortopt
* @return Return a builder through which a single option may be configured
*/
protected OptionBuilder option(char shortopt) {
OptionBuilder builder = new OptionBuilder().withShortOpt(shortopt);
builders.add(builder);
return builder;
}
/**
* Bind any parser. BasicParser is used by default if no other parser is provided.
*/
protected AnnotatedBindingBuilder<Parser> bindParser() {
parserIsBound = true;
return bind(Parser.class);
}
}