デコレータを使ったコマンドラインパーサ「classopt」を作った
classopt
というdeno用のコマンドラインパーサを作りました。
デコレータを活用し、型安全なパーサが直観的にかけることを目指しました。
サンプル
import {
Arg,
Command,
Flag,
Help,
Opt,
Version,
} from "https://deno.land/x/classopt@v0.1.1/mod.ts";
@Help("Help Text for Command")
@Version("0.0.0")
class Program extends Command {
@Arg({ about: "user to login" })
username!: string;
@Opt({ about: "password for user, if required", short: true })
password = "";
@Flag({ about: "debug mode" })
debug = false;
async execute() {
console.log(`<username> = ${this.username}`);
console.log(`-p, --password = ${this.password}`);
console.log(`--debug = ${this.debug}`);
await void 0;
}
}
await Program.run(Deno.args);
このコードによって、以下のようなコマンドが生成されます。
$ deno run example.ts --help
program - Help Text for Command
USAGE
program [OPTIONS] <username>
OPTIONS
-p, --password <string> password for user, if required
--debug debug mode
-V, --version Prints version information
-h, --help Prints help information
ARGS
<username> user to login
$ deno run example.ts --version
program 0.0.0
$ deno run example.ts -p pass --debug stsysd
<username> = stsysd
-p, --password = pass
--debug = true
Command クラス
コマンドの定義に使う基底クラスとしてCommand
を用意しています。
@Help("Help Text for Command")
@Version("0.0.0")
class Program extends Command {
async execute() {
// コマンドの実行内容をここに書く
}
// 省略
}
await Program.run(Deno.args);
Help
とVersion
というクラスデコレータで修飾することで、それぞれ--help
と--version
がオプションとして指定されます。
これは必須ではありません。
Command
を継承したクラスには、execute
メソッドを必ず実装する必要があります。
execute
の中で同期的な処理しかしない場合であっても、返り値の型は必ずPromise<void>
にしなければいけないことに注意してください。
Command
から継承されるクラスメソッドrun
を実行することで、コマンドライン引数がパースされ、exeute
に書いた処理が実行されます。このとき、run
の引数にはコマンドライン引数が入った文字列の配列を渡してください。
オプションの定義
オプションの定義には、プロパティデコレータを使います。
Arg
位置引数(--key
のようなキーを伴わない引数)の定義には、Arg
を使います。
class Program extends Command {
@Arg({ about: "requied" /* optional: false */ })
foo!: string;
@Arg({ about: "optional", optional: true })
bar = "";
// 省略
}
それぞれのプロパティには、実行時に渡されたコマンドライン引数の文字列がそのまま代入されます。
位置引数の順番はプロパティが定義された順番によって決まります。上の例だとprogram foo bar
の順で引数を与える必要があります。
デフォルトでは位置引数は省略できません。省略可能にしたい場合は、optional
オプションをtrue
にしてください。
定義する際には、省略可能な引数の後に続けて必須な引数は定義できないので注意してください。
Opt
キーワード引数(--key
のようなキーを伴う引数)の定義には、Opt
を使います。
class Program extends Command {
@Opt({
/* type: "string", */ about: "string option",
long: "text",
short: "T",
})
str = "";
@Opt({ type: "number", about: "number option" })
num = 0;
// 省略
}
キーワード引数は必ず省略可能です。 type
オプションによって文字列と数値のどちらを受け取るかを指定できます(デフォルトでは文字列)。
デフォルトではプロパティ名をケバブケース(ex.
foo-bar
)に変換したものがキーとなります。long
オプションによって明示的に指定することも可能です。
また、short
オプションで1文字の短いキーも指定できます。
Flag
フラグ(--key
のようなキー単体で使う引数)の定義には、Flag
を使います。
class Program extends Command {
@Flag({ about: "debug mode" })
debug = false;
// 省略
}
プロパティの値は、フラグが渡されている場合にtrue
、渡されていない場合にfalse
になります。
Opt
と同様に、long
/short
オプションを使ってフラグのキーは明示的に指定できます。 また、短いキーを持つフラグは -abc
のような形式でまとめて渡すことができます。
サブコマンド
サブコマンドの定義には、プロパティデコレータCmd
を使います。
例えば、以下のように書くことで、list
とget
のふたつのサブコマンドを定義できます。
import {
Opt,
Cmd,
Command,
Flag,
Help,
Name,
Version,
} from "https://deno.land/x/classopt@v0.1.1/mod.ts";
interface PathOpt {
path: string;
}
@Help("Help Text for 'list' Command")
class List extends Command<PathOpt> {
@Flag({ about: "Prints full path" })
fullPath = false;
async execute(opt: PathOpt) {
console.log(`List.fullPath = ${this.fullPath}`);
console.log(`PathOpt.path = ${opt.path}`);
await void 0; // avoid `requrie-await`
}
}
@Name("get")
@Help("Help Text for 'get' Command")
class GetCommand extends Command<PathOpt> {
async execute(opt: PathOpt) {
console.log(`PathOpt.path = ${opt.path}`);
await void 0; // avoid `requrie-await`
}
}
@Version("0.0.0")
@Help("Help Text for Top Command")
class Program extends Command implements PathOpt {
@Opt({ about: "Specify path to get" })
path = "";
@Cmd(List, GetCommand)
command?: Command<PathOpt>;
async execute() {
if (this.command == null) {
console.log(this.help());
} else {
await this.command?.execute(this);
}
}
}
await Program.run(Deno.args);
このコードによって、以下のようなコマンドが生成されます。
$ deno run subcommand.ts --help
program - Help Text for Top Command
USAGE
program [OPTIONS]
OPTIONS
--path <string> Specify path to get
-h, --help Prints help information
-V, --version Prints version information
COMMANDS
list Help Text for 'list' Command
get Help Text for 'get' Command
$ deno run example.ts get --help
get - Help Text for 'get' Command
USAGE
program get [OPTIONS]
OPTIONS
-h, --help Prints help information
$ deno run example.ts list --help
list - Help Text for 'list' Command
USAGE
program list [OPTIONS]
OPTIONS
--full-path Prints full path
-h, --help Prints help information
Discussion