NimのCLIツール作成用ライブラリcligenがとても便利

NimでCLIツールを書くときに使った[cligen](https://github.com/c-blake/cligen)というライブラリが非常に便利でした。
めちゃくちゃ簡単にCLIツールを作れるライブラリだと感じたので使い方と使った感想を記載します。

リポジトリ： [GitHub - c-blake/cligen](https://github.com/c-blake/cligen)

# 検証環境

| 名称 | バージョン |
| --- | --- |
| Linux Mint | 19.1   |
| Nim        | 0.20.2 |
| nimble     | 0.10.2 |

# インストール

以下のコマンドを実行する。
`nimble install cligen`

# 実装する

インストールしたら以下のコードを書いてみます。

```nim
proc sum(debug=false, args: seq[string]): int =
  discard

when isMainModule:
  import cligen
  dispatch(sum)
```

なんと`dispatch`という関数に**関数名を渡すと、その関数の引数定義がそのままコマンドのオプションになる**という驚異的な仕様です。
オプション引数の必須、任意についても、デフォルト引数を定義するか否か、だけで切り替わるので、覚えないといけないことが非常に少ないです。

## 必須オプションと任意オプションの設定

例えば以下のように、引数fooにデフォルト引数を定義すると、それに紐づくオプションは省略できます。
逆にデフォルト引数を指定しなければ、そのオプションは必須になります。

```nim
proc sum(foo=0, bar: int, args: seq[string]): int =
  discard
   
when isMainModule:
   import cligen
   dispatch(sum)
```

引数最後にseqを定義すると、その配列引数へオプション引数に該当しないもの全てが詰められます。


## コマンドの終了コードの指定

関数の戻り値がそのままコマンドの終了コードになるので、
異常系の場合は0以外を返すように実装します。

```a.nim
proc proc1(exitCode: int): int =
  return exitCode

when isMainModule:
  import cligen
  dispatch(proc1)
```

```bash
$ nim c a.nim

$ ./a 0 ; echo $?
0

$ ./a 1 ; echo $?
1
```

## ショートオプションの設定

引数のショートオプションは引数名の先頭１文字が採用されます。
先頭の文字が重複した場合は、あとに定義されたオプションにはショートオプションなしになります。

```b.nim
proc proc1(opt1 = false, opt2 = false): int =
  return 0

when isMainModule:
  import cligen
  dispatch(proc1)
```

```bash
$ nim c b.nim
$ ./b -h
Usage:
  proc1 [optional-params] 
Options(opt-arg sep :|=|spc):
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  -o, --opt1     bool  false  set opt1
  --opt2         bool  false  set opt2
```

## オプション名の変更

オプション名に別名をつけたい場合は、以下のようにすれば別の名前を決められます。

```b2.nim
proc proc1(opt1 = false, opt2 = false): int =
  return 0

when isMainModule:
  import cligen
  dispatch(proc1, short = {"opt2":'O'})
```

```bash
$ nim c b2.nim
$ ./b2 -h
Usage:
  proc1 [optional-params] 
Options(opt-arg sep :|=|spc):
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  -o, --opt1     bool  false  set opt1
  -O, --opt2     bool  false  set opt2
```

2019/09/21 追記:

ケバブケースのオプションで指定できるようにしたかったのですが、ロングオプション名の変更はできないように見えます。
しかし、**特に指定せずともlowerCamelCaseでもkebab-caseでも指定できる**ようです。

> Like long option keys or enum values, any unambiguous prefix is accepted. So, in the above ./cmd f -m1 would also work. This is patterned after, e.g. Mercurial, gdb, or gnuplot. Additionally, long option keys can be spelled flexibly, e.g. --dry-run or --dryRun, much like Nim's style-insensitive identifiers, but with extra insensitivity to so-called "kebab case".

## ヘルプメッセージの設定

ヘルプメッセージも以下のように変更できる。

```b3.nim
proc proc1(opt1 = false, opt2 = false): int =
  return 0

when isMainModule:
  import cligen
  dispatch(proc1, short = {"opt2":'O'}, help = {"opt1":"オプションの説明を変更してみる"})
```

```bash
$ nim c b3.nim
$ ./b3 -h
Usage:
  proc1 [optional-params] 
Options(opt-arg sep :|=|spc):
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  -o, --opt1     bool  false  オプションの説明を変更してみる
  -O, --opt2     bool  false  set opt2
```

## サブコマンドを定義する

サブコマンドの定義も`dispatchMulti`関数で同様に定義できます。
ドキュメンテーションコメントを書くと、コマンドのヘルプとして出力されます。

```c.nim
proc proc1(args: seq[string]): int =
  ## proc1 は練習用のサブコマンドです。
  discard

proc proc2(args: seq[string]): int =
  ## proc2 も練習用のサブコマンドです。
  discard

proc proc3(args: seq[string]): int =
  ## proc3 も練習用のサブコマンドです。
  discard

when isMainModule:
  import cligen
  dispatchMulti([proc1], [proc2], [proc3])
```

```bash
$ nim c c.nim
$ ./c -h
Usage:
  c {SUBCMD}  [sub-command options & parameters]
where {SUBCMD} is one of:
  help   print comprehensive or per-cmd help
  proc1  proc1 は練習用のサブコマンドです。
  proc2  proc2 も練習用のサブコマンドです。
  proc3  proc3 も練習用のサブコマンドです。

c {-h|--help} or with no args at all prints this message.
c --help-syntax gives general cligen syntax help.
Run "c {help SUBCMD|SUBCMD --help}" to see help for just SUBCMD.
Run "c help" to get *comprehensive* help.
```

### サブコマンドのオプション、ヘルプメッセージの変更

サブコマンドも同様にショートオプショにゃヘルプメッセージを変更できます。

```c2.nim
proc proc1(opt1 = false, args: seq[string]): int =
  ## proc1 は練習用のサブコマンドです。
  discard

proc proc2(args: seq[string]): int =
  ## proc2 も練習用のサブコマンドです。
  discard

proc proc3(args: seq[string]): int =
  ## proc3 も練習用のサブコマンドです。
  discard

when isMainModule:
  import cligen
  dispatchMulti([proc1, short = {"opt1":'O'},
                        help = {"opt1":"オプションを変更した"}],
                [proc2],
                [proc3])
```

```bash
$ nim c c2.nim
$ ./c2 proc1 -h
proc1 [optional-params] [args: string...]
proc1 は練習用のサブコマンドです。
Options(opt-arg sep :|=|spc):
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  -O, --opt1     bool  false  オプションを変更した
```

### サブコマンド名の変更

2019/10/11 追記

つけたいサブコマンド名がNimのモジュールの関数名と重複していて、紛らわしくて名前をつけられないケースもあるかと思います。
そういう時は `cmdName` という引数で名前を上書きできます。

```nim
proc subCommandAdd(args: seq[string]): int =
  discard

proc subCommandInsert(args: seq[string]): int =
  discard

proc subCommandDelete(args: seq[string]): int =
  discard

when isMainModule:
  import cligen
  dispatchMulti(
    [subCommandAdd, cmdName = "add"],
    [subCommandInsert, cmdName = "insert"],
    [subCommandDelete, cmdName = "del"],
  )
```

## バージョン情報だけ出力するオプションの定義

2019/07/24 追記

バージョンだけ出力するオプションを追加で定義したい場合は以下のようにします。

```ver.nim
proc proc1(args: seq[string]): int =
  return 0

when isMainModule:
  import cligen
  clCfg.version = "0.1.0" # ここを追加した
  dispatch(proc1)
```

```bash
$ nim c ver.nim
$ ./ver -h
Usage:
  proc1 [optional-params] [args: string...]
Options(opt-arg sep :|=|spc):
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  --version      bool  false  print version
```

# cligenのメリット

- 覚えないといけないことが少ない
    - 関数定義とimport、dispatchしかしなくてよい
    - ライブラリを導入してすぐに使い始められる敷居の低さ
- 記述量が少ない
    - オプション定義に必要なのはたった2行
- サブコマンドの定義が簡単
    - dispatchMultiするだけで良くてとてもお手軽にサブコマンドが定義できて快適すぎる

# cligenのデメリット

- ヘルプメッセージがみづらい
    - 関数にドキュメンテーションコメントを書けば、ヘルプ出力時に表示されるが、表示のされ方が微妙
      前述のコマンドの実行結果を引用

```
$ ./c2 proc1 -h
proc1 [optional-params] [args: string...]
proc1 は練習用のサブコマンドです。
Options(opt-arg sep :|=|spc):
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  -O, --opt1     bool  false  オプションを変更した
```

- `## proc1 は練習用のサブコマンドです。`と関数にドキュメンテーションコメントを書いた結果が反映されているが読みづらい
- ただしデフォルトの書式が読みにくいだけで、`usage`引数にテンプレート文字列を渡せば独自の書式に変更できる
    - [cligen.html#dispatch](http://c-blake.github.io/cligen/cligen.html#dispatch.t%2C%2Cstring%2Cstring%2Ctyped%2Ctyped%2Cseq%5BT%5D%5Bstring%5D%2Cseq%5BT%5D%5Bstring%5D%2Cstring%2Cseq%5BT%5D%5Bstring%5D%2Cseq%5BT%5D%5BClAlias%5D%2Cseq%5BT%5D%5Bstring%5D)

# まとめ

CLIツールを作成するためのライブラリのcligenの使い方について説明しました。
Nimのオプションパーサー系ライブラリの中では一番簡単に使えると感じています。
GitHubスター数もNimのオプションパーサーライブラリの中で一番多いです。
CLIツールを作るときに役立てば幸いです。

以上です。