ページ

2012-11-11

Javaでコマンドライン引数を解析するやつを書いてる


数日前からjavaでコマンドライン引数を解析するやつを書いています。



Javaにはcommons cliというコマンドライン引数を解析するライブラリがあるのですが、自前で作ってみる方向で行きます。

どんなやつ?

ターゲットとしてはgitとかrailsみたいに、最初の引数にコマンドを取って、それ以降にオプションが続く形式のコマンドです。
git status -s
みたいなやつです。

まともに使えるようになったらGlacierToolsに使うつもりです。

なんでつくったの?

GlacierToolsでコマンドライン引数を解析する部分の処理がめちゃくちゃになって、可読性が悪くなってきたからです。

下のリンクのコードを見てもらうとわかるのですが、色々とダメな所が盛り沢山です。
https://github.com/mironal/GlacierTools/blob/master/src/jp/mironal/java/aws/app/glacier/cmd/VaultControllerCmd.java
  • コマンドとコマンドの処理の記述箇所が離れていて、可読性が悪い
  • コマンドライン引数を登録してる所は、もはや無いに等しい
  • どのオプションがどのコマンドに紐付いているのが分からない
  • helpもどれがどれなのか分かりにくい
  • エラー処理も分かりにくい
  • そもそもバグってる


これらを解決出来るように色々考えてみました。

どんな風に書けるの?

下のように書けます。


それぞれコマンドはのCommandクラスのインスタンスで閉じた形になるので、オプションの種類が増えてもなんとかなるような気がします。

また、オプションの解析そのものは裏側に隠して、実行すべき処理に集中できるようにしました。



public class SampleApp {
// @formatter:off
// コマンド毎にenumを宣言
enum Kind {
Create,
List,
Delete
}
// @formatter:on
public static void main(String[] args) {
/*
* gitのような形式のコマンド向けのライブラリ.
* git commit -aみたいなやつ.
*
* 各コマンド毎にオプションの宣言位置と、実際に行われる処理、エラー処理、helpの
* 宣言位置が離れないところが特徴.
* 例外を使用することで、コマンド実行処理中に何かエラーが発生した場合に
* 即座に処理を中断し、エラー処理に映ることが出来る.
*/
// createコマンドの定義
Command<Kind> create = new Command<SampleApp.Kind>(Kind.Create)
// @formatter:off
// createコマンドが取るオプションを登録
.addOption("--name", "object name.")
.addOption("--desc", "object description")
// @formatter:on
.withProcedure(new CommandProcedure() {
@Override
// コマンドが実行される時に呼ばれる
public void onExecute(Options options) throws CommandProcedureException {
// まずはオプションのチェック
// 何かオプションに問題があったらメッセージを含めて例外を投げる.
if (!options.hasOption("--name")) {
// ex. 必須のオプションが指定されていないとき.
throw new CommandProcedureException("--name is required.");
}
// 問題なければコマンドの実行を続ける
StringBuilder builder = new StringBuilder();
builder.append("create ").append(options.getOptionValue("--name"));
builder.append("\n");
builder.append("success!.");
System.out.println(builder.toString());
}
@Override
// onExecuteで例外を投げた時はここに飛ぶ.
public void onCatchError(CommandProcedureException e) {
// 表示するメッセージはonExecuteで生成済みなので、printするだけ
System.err.println(e.getMessage());
}
@Override
// --helpなどでhelpが要求されたときはここに飛ぶ
public void onAskHelp(Options options) {
// @formatter:off
String help = "Something help.\n +" +
"hogehogehogehoge.";
// @formatter:on
System.out.println(help);
}
});
// listコマンドの定義
Command<Kind> list = new Command<SampleApp.Kind>(Kind.List)
.withProcedure(new CommandProcedure() {
@Override
public void onAskHelp(Options options) {
// do something
}
@Override
public void onExecute(Options options) throws CommandProcedureException {
// do something
}
@Override
public void onCatchError(CommandProcedureException e) {
// do something
}
});
// deleteコマンドの定義
Command<Kind> delete = new Command<Kind>(Kind.Delete).addOption("--neme", "object name.")
.withProcedure(new CommandProcedure() {
@Override
public void onCatchError(CommandProcedureException e) {
// do something
}
@Override
public void onAskHelp(Options options) {
// do something
}
@Override
public void onExecute(Options options) throws CommandProcedureException {
// do something
}
}).addOption("--name", "object name.");
CommandLineExecutor<Kind> executor = new CommandLineExecutor<SampleApp.Kind>(Kind.class) {
@Override
// コマンドそのもののhelp.
// 何も引数が指定されたかった時などに呼ばれる
public void onAskHelp(Map<Kind, Command<Kind>> options) {
System.out.println("create | list | delete");
}
};
// 上で作ったコマンドを登録.
executor.addCommand(create).addCommand(list).addCommand(delete);
// 実行
executor.execute(args);
}
}
view raw SampleApp.java hosted with ❤ by GitHub

読みづれぇ!!


ごめんなさい。僕的には読みやすいと思ったんです。

まだ動く状態まで行ってないので、色々変更するかもしれないです。

動く状態になったらgithubに公開してみる予定です。

なかなか奥が深くて楽しいです。

つかScalaで書けよ・・・

0 件のコメント:

コメントを投稿