やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

docoptがクソすぎた💩

 コマンドラインパーサ。Usageのテキストからコマンド引数を解析してくれる。だが、思ったように受け取れなかった。

成果物

情報源

docoptとはコマンドパーサである

 docoptPython製のコマンドパーサである。CLIによくあるヘルプのテキストを解析してコマンド引数を取得してくれる。

 すごい! と思うじゃん? けれど思ったとおりに解析してくれなかった。それをこれから実践してみよう。

インストール

pip3 install docopt

 バージョンは0.6.2

$ pip3 freeze | grep docopt
docopt==0.6.2

コード

a.py

#!/usr/bin/env python3
# coding: utf8
"""
Usage:
  a.py [-t <title>] [-f <folder>] <PATH>
"""
import docopt
args = docopt.docopt(__doc__)
print(args)

実行

 chmodで実行権限を付与すれば./a.pyのように短縮名で実行できる。実行アプリはシェバング#!/usr/bin/env python3で決まる。

chmod +x ./a.py
./a.py

 そうでない場合は以下で実行する。

python3 ./a.py

最初に書いたdocopt

Usage:
  a.py [-t <title>] [-f <folder>] <PATH>

 期待するコマンドパターンは以下のとおり。

a.py PPP
a.py -t TTT PPP
a.py -f FFF PPP
a.py -t TTT -f FFF PPP
a.py -f FFF -t TTT PPP

 すべての引数をセットした場合は成功した。

a.py -t TTT -f FFF PPP
a.py -f FFF -t TTT PPP
{'-f': True,
 '-t': True,
 '<PATH>': 'PPP',
 '<folder>': 'FFF',
 '<title>': 'TTT'}

 だが、それ以外は失敗した。

 つまりオプションにしたいヤツらが必須になってるってことかよ……。

a.py PPP
a.py -t TTT PPP
a.py -f FFF PPP
Usage:
  a.py [-t <title>] [-f <folder>] <PATH>

 これはひどい。まさか全パターン網羅しなきゃいけない、なんてことはないよね? そんなんだったらこのテキストを書くのが苦痛すぎて何も嬉しくないからね?

 きっと私の書き方が間違っているのだろう。ググって調べたらこれでいいはずなんだけど、きっと違うに違いない。そうに決まっている。というわけで別の書き方を試してみよう。

2. 引数値を[]で囲んでみた

Usage:
  a.py [-t [title]] [-f [folder]] <PATH>

 オプションがひとつもないときは成功した。

a.py PPP

 でもね? オプションをひとつつけたらダメさ。

a.py -t TTT PPP
a.py -f FFF PPP
Usage:
  a.sh [-t [title]] [-f [folder]] <PATH>

 フルオプションでもやっぱりダメ。

a.py -f FFF -t TTT PPP
a.py -t TTT -f FFF PPP
Usage:
  a.sh [-t [title]] [-f [folder]] <PATH>

 それどころか、オプションの値がなくても通ってしまう始末だよ。

a.py -f FFF -t PPP
a.py -t TTT -f PPP
{'-f': False,
 '-t': True,
 '<PATH>': 'PPP',
 'folder': False,
 'title': False}
{'-f': True,
 '-t': False,
 '<PATH>': 'PPP',
 'folder': False,
 'title': False}

 バカなのかい? そんな使い方、するわけないだろ? なんのために引数値を書いたと思っているんだい?

 はいそうだね、こんな書き方した私が間違っているんだね。知ってた。

3. 引数値を()で囲んでみた

 ついにオプション引数はその存在を許されなくなってしまった。

$ a.py PPP
{'-f': False,
 '-t': False,
 '<PATH>': 'PPP',
 'folder': False,
 'title': False}
$ a.py 
Usage:
  a.sh [-t (title)] [-f (folder)] <PATH>
$ a.py -t TTT PPP
Usage:
  a.sh [-t (title)] [-f (folder)] <PATH>
$ a.py -f FFF PPP
Usage:
  a.sh [-t (title)] [-f (folder)] <PATH>
$ a.py -t TTT -f FFF PPP
Usage:
  a.sh [-t (title)] [-f (folder)] <PATH>
$ a.py -f FFF -t TTT PPP
Usage:
  a.sh [-t (title)] [-f (folder)] <PATH>

 位置引数の<PATH>以外は全滅さ。

 もうね、記法が間違っているならそうと言ってほしいんだよね。

4. 全パターンを網羅した

 書き方を最初のやつにした上で、全パターン網羅した。

Usage:
  a.sh <PATH>
  a.sh [-t <title>] <PATH>
  a.sh [-f <folder>] <PATH>
  a.sh [-t <title>] [-f <folder>] <PATH>

 これで成功した。いや、そりゃそうだろうさ。でもね? これとんでもない数になるよ? オプション引数の数だけ組み合わせがあって、それを網羅しろって? 今2個しかないからこれだけで済んでいるけどさぁ。

オプション引数の数 組み合わせパターン数
1 1 a
2 3 a,b,ab
3 7 a,b,c,ab,ac,bc,abc
4 14 a,b,c,d,ab,ac,ad,bc,bd,abc,abd,acd,bcd,abcd
5 30 a,b,c,d,e,ab,ac,ad,ae,bc,bd,be,cd,ce,de,abc,abd,abe,acd,ace,ade,bcd,bce,bde,cde,abcd,abce,abde,acde,abcde

 これ、頭のいい人なら計算式がわかるのでは? 私はバカだから手で数えた。

 とにかく大変なことになる。こんなにパターン網羅して書くのって辛くない? 私は絶対に嫌だ。

 ためしにオプション引数が3つある場合を書いてみた。オプションなしを合わせて全8パターン。

Usage:
  a.sh <PATH>
  a.sh [-t <title>] <PATH>
  a.sh [-f <folder>] <PATH>
  a.sh [-g <generator>] <PATH>
  a.sh [-t <title>] [-f <folder>] <PATH>
  a.sh [-t <title>] [-g <generator>] <PATH>
  a.sh [-f <folder>] [-g <generator>] <PATH>
  a.sh [-t <title>] [-f <folder>] [-g <generator>] <PATH>

 バカじゃないの? ふつうは以下のように書くことで表現しているのに。

Usage:
  a.sh [-t title] [-f folder] [-g generator] <PATH>

 docopt、全然かんたんじゃない。むしろDRYに書くことを阻んでいる害悪。

 だめだこりゃ。

所感

 docoptには失望した。

 コマンドのパースって、なんでこんなにむずかしいんだろうね。

 以下のように簡単にできないの?

[-t title] [-f folder] [-g generator] <PATH>
subcmd1 -[-f] [-o option]
subcmd2 -[-f] [-o option]
subcmd2  subsub1 -[-f] [-o option]

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux