JCommander
一. 使用例子
非常小的java框架,用於解析命令行參數
注解描述符例子
import com.beust.jcommander.Parameter;
public class JCommanderExample {
@Parameter
private List<String> parameters = new ArrayList<>();
@Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity")
private Integer verbose = 1;
@Parameter(names = "-groups", description = "Comma-separated list of group names to be run")
private String groups;
@Parameter(names = "-debug", description = "Debug mode")
private boolean debug = false;
}
使用JCommander解析
JCommanderExample jct = new JCommanderExample();
String[] argv = { "-log", "2", "-groups", "unit" };
new JCommander(jct, argv);
Assert.assertEquals(jct.verbose.intValue(), 2);
實際觀察到的結果入下
class Main {
@Parameter(names={"--length", "-l"})
int length;
@Parameter(names={"--pattern", "-p"})
int pattern;
public static void main(String ... args) {
Main main = new Main();
new JCommander(main, args);
main.run();
}
public void run() {
System.out.printf("%d %d", length, pattern);
}
}
二.支持的類型
Boolean
可以設置默認值,不傳這個參數使用默認值
@Parameter(names = "-debug", description = "Debug mode")
private boolean debug = false;
String, Integer, Long
這三種類型,JCommander會將它們轉化為相應的類型
Lists
list類型,可以使用很多次標記傳入
@Parameter(names = "-host", description = "The host")
private List<String> hosts = new ArrayList<>();
使用
java Main -host host1 -verbose -host host2
Password
密碼之類不做記錄的參數,設置為password類型,JCommander會讓你在終端中填入
public class ArgsPassword {
@Parameter(names = "-password", description = "Connection password", password = true)
private String password;
}
使用例子
Value for -password (Connection password):
顯示輸入
設置echoInput
為true
public class ArgsPassword {
@Parameter(names = "-password", description = "Connection password", password = true, echoInput = true)
private String password;
}
三. 定制類型
注解
需要文件、主機名、等參數時需要定制
需要實現IStringConverter
接口寫一個類型轉換器
public interface IStringConverter<T> {
T convert(String value);
}
string轉文件
public class FileConverter implements IStringConverter<File> {
@Override
public File convert(String value) {
return new File(value);
}
}
然后定義轉換的類型即可
@Parameter(names = "-file", converter = FileConverter.class)
File file;
工廠方法
實現接口
public interface IStringConverterFactory {
<T> Class<? extends IStringConverter<T>> getConverter(Class<T> forType);
}
比如轉換host和port:java App -target example.com:8080
定義holder類
public class HostPort {
private String host;
private Integer port;
}
定義string converter
在類中創建實例
class HostPortConverter implements IStringConverter<HostPort> {
@Override
public HostPort convert(String value) {
HostPort result = new HostPort();
String[] s = value.split(":");
result.host = s[0];
result.port = Integer.parseInt(s[1]);
return result;
}
}
工廠如下
public class Factory implements IStringConverterFactory {
public Class<? extends IStringConverter<?>> getConverter(Class forType) {
if (forType.equals(HostPort.class)) return HostPortConverter.class;
else return null;
}
可以直接使用,不增加額外的參數
public class ArgsConverterFactory {
@Parameter(names = "-hostport")
private HostPort hostPort;
}
只需要在JCommander對象中添加工廠即可
ArgsConverterFactory a = new ArgsConverterFactory();
JCommander jc = new JCommander(a);
jc.addConverterFactory(new Factory());
jc.parse("-hostport", "example.com:8080");
Assert.assertEquals(a.hostPort.host, "example.com");
Assert.assertEquals(a.hostPort.port.intValue(), 8080);
四. 參數校驗
兩種方式:
- 全局
- 局部
局部校驗
實現接口IParameterValidator可以做早期校驗
public interface IParameterValidator {
void validate(String name, String value) throws ParameterException;
}
驗證數為正數的例子
public class PositiveInteger implements IParameterValidator {
public void validate(String name, String value)
throws ParameterException {
int n = Integer.parseInt(value);
if (n < 0) {
throw new ParameterException("Parameter " + name + " should be positive (found " + value +")");
}
}
}
在注解中,在validateWith中指定實現接口的類名
@Parameter(names = "-age", validateWith = PositiveInteger.class)
private Integer age;
如果校驗不成功會拋異常
全局校驗
JCommander解析后,需要額外的校驗,比如校驗兩個互斥參數;
java實現非常局限,JCommander不提供解決方案
五. Main參數
目前參數都通過names屬性定義,可以定義一個沒有這個屬性的參數,這樣的參數需要List類型,它會包含所有沒有包含的參數,比如
@Parameter(description = "Files")
private List<String> files = new ArrayList<>();
@Parameter(names = "-debug", description = "Debugging level")
private Integer debug = 1;
傳入參數
java Main -debug file1 file2
由於file1不能解析為Integer,因此files會存進file1和file2
六. private參數
參數可以設置為私有,這樣提供get方法
public class ArgsPrivate {
@Parameter(names = "-verbose")
private Integer verbose = 1;
public Integer getVerbose() {
return verbose;
}
}
使用
ArgsPrivate args = new ArgsPrivate();
new JCommander(args, "-verbose", "3");
Assert.assertEquals(args.getVerbose().intValue(), 3);
七. 參數分隔符
默認的分隔符為空格
可設置分隔符,類似這樣
java Main -log:3
java Main -level=42
在參數里配置分隔符
@Parameters(separators = "=")
public class SeparatorEqual {
@Parameter(names = "-level")
private Integer level = 2;
}
八. 多種描述
可以把描述參數分配到多個類中,比如
public class ArgsMaster {
@Parameter(names = "-master")
private String master;
}
public class ArgsSlave {
@Parameter(names = "-slave")
private String slave;
}
然后把這兩個對象傳入JCommander中
ArgsMaster m = new ArgsMaster();
ArgsSlave s = new ArgsSlave();
String[] argv = { "-master", "master", "-slave", "slave" };
new JCommander(new Object[] { m , s }, argv);
Assert.assertEquals(m.master, "master");
Assert.assertEquals(s.slave, "slave");
九. @語法
@語法可以把參數放到文件中,然后通過參數把文件傳遞過去
/tmp/parameters
文件如下
-verbose
file1
file2
file3
傳遞參數
java Main @/tmp/parameters
十. 多值參數
固定
如果參數為多個值
java Main -pairs slave master foo.xml
這時,參數需要存入list,並且制定arity
@Parameter(names = "-pairs", arity = 2, description = "Pairs")
private List<String> pairs;
只有list支持多值
可變
參數的數量不確定
program -foo a1 a2 a3 -bar
program -foo a1 -bar
這樣的參數,類型為list,並且設置variableArity為true
@Parameter(names = "-foo", variableArity = true)
public List<String> foo = new ArrayList<>();
十一. 多個參數名
@Parameter(names = { "-d", "--outputDirectory" }, description = "Directory")
private String outputDirectory;
使用定義中的參數名都可以
java Main -d /tmp
java Main --outputDirectory /tmp
十二. 其他參數配置
參數的查找方式
JCommander.setCaseSensitiveOptions(boolean) //是否大小寫敏感
JCommander.setAllowAbbreviatedOptions(boolean)//參數是否可以簡寫
簡寫參數時,比如-param
可用參數-par
替代,如果出錯會拋異常
十三. 是否必填
required=true,為必填,如果空,拋異常
@Parameter(names = "-host", required = true)
private String host;
十四. 默認值
定義時,可以設置默認值
也可以使用IDefaultProvider,把配置放在xml或.properties中
public interface IDefaultProvider {
String getDefaultValueFor(String optionName);
}
下面是一個默認的IDefaultProvider,將所有的-debug
設置為42
private static final IDefaultProvider DEFAULT_PROVIDER = new IDefaultProvider() {
@Override
public String getDefaultValueFor(String optionName) {
return "-debug".equals(optionName) ? "false" : "42";
}
};
// ...
JCommander jc = new JCommander(new Args());
jc.setDefaultProvider(DEFAULT_PROVIDER);
十五. help參數
顯示用法和幫助信息
@Parameter(names = "--help", help = true)
private boolean help;
十六. 更復雜的語法
復雜的工具比如git,svn包含一系列的命令,具有自己的語法
比如
git commit --amend -m "Bug fix"
上述commit關鍵字在JCommander中叫做commands
,可以為每個命令設置一個arg對象
@Parameters(separators = "=", commandDescription = "Record changes to the repository")
private class CommandCommit {
@Parameter(description = "The list of files to commit")
private List<String> files;
@Parameter(names = "--amend", description = "Amend")
private Boolean amend = false;
@Parameter(names = "--author")
private String author;
}
@Parameters(commandDescription = "Add file contents to the index")
public class CommandAdd {
@Parameter(description = "File patterns to add to the index")
private List<String> patterns;
@Parameter(names = "-i")
private Boolean interactive = false;
}
然后注冊進JCommander,在JCommander對象上調用getParsedCommand()
CommandMain cm = new CommandMain();
JCommander jc = new JCommander(cm);
CommandAdd add = new CommandAdd();
jc.addCommand("add", add);
CommandCommit commit = new CommandCommit();
jc.addCommand("commit", commit);
jc.parse("-v", "commit", "--amend", "--author=cbeust", "A.java", "B.java");
Assert.assertTrue(cm.verbose);
Assert.assertEquals(jc.getParsedCommand(), "commit");
Assert.assertTrue(commit.amend);
Assert.assertEquals(commit.author, "cbeust");
Assert.assertEquals(commit.files, Arrays.asList("A.java", "B.java"));
十七. 異常
捕獲到任何錯誤,都會拋ParameterException,都是運行時異常
十八. 用法
在JCommander實例上調用,usage()方法,顯示所有的使用方法
Usage: <main class> [options]
Options:
-debug Debug mode (default: false)
-groups Comma-separated list of group names to be run
* -log, -verbose Level of verbosity (default: 1)
-long A long number (default: 0)
十九. 隱藏參數
不想讓參數在help中顯示
@Parameter(names = "-debug", description = "Debug mode", hidden = true)
private boolean debug = false;
二十. 國際化
可以國家化描述
使用@Parameters
注解,定義名字,然后使用descriptionKey 屬性,而不是description
@Parameters(resourceBundle = "MessageBundle")
private class ArgsI18N2 {
@Parameter(names = "-host", description = "Host", descriptionKey = "host")
String hostName;
}
JCommander會使用默認的locale解析描述
二一. 參數delegates
如果在同一project中寫了很多工具,工具之間可以共享配置,可以繼承對象,避免重復代碼;
單繼承可能會限制靈活性,JCommander支持參數delegates
@ParameterDelegate
注解
class Delegate {
@Parameter(names = "-port")
private int port;
}
class MainParams {
@Parameter(names = "-v")
private boolean verbose;
@ParametersDelegate
private Delegate delegate = new Delegate();
}
只需要添加MainParams對象到JCommander配置中,就能夠使用這個delegate
MainParams p = new MainParams();
new JCommander(p).parse("-v", "-port", "1234");
Assert.assertTrue(p.isVerbose);
Assert.assertEquals(p.delegate.port, 1234);
二二. 動態參數
允許使用編譯時不知道的參數,比如-Da=b -Dc=d
,必須是Map<String, String>類型,可以在命令行中出現多次
@DynamicParameter(names = "-D", description = "Dynamic parameters go here")
private Map<String, String> params = new HashMap<>();
也可以使用除=的分配符
二三. scala中使用
import java.io.File
import com.beust.jcommander.{JCommander, Parameter}
import collection.JavaConversions._
object Main {
object Args {
// Declared as var because JCommander assigns a new collection declared
// as java.util.List because that's what JCommander will replace it with.
// It'd be nice if JCommander would just use the provided List so this
// could be a val and a Scala LinkedList.
@Parameter(
names = Array("-f", "--file"),
description = "File to load. Can be specified multiple times.")
var file: java.util.List[String] = null
}
def main(args: Array[String]): Unit = {
new JCommander(Args, args.toArray: _*)
for (filename <- Args.file) {
val f = new File(filename)
printf("file: %s\n", f.getName)
}
}
}
二四. groovy中使用
import com.beust.jcommander.*
class Args {
@Parameter(names = ["-f", "--file"], description = "File to load. Can be specified multiple times.")
List<String> file
}
new Args().with {
new JCommander(it, args)
file.each { println "file: ${new File(it).name}" }
}