docopt
ドキュメント文字列からコマンドライン引数パーサーを自動生成する革新的なライブラリ。宣言的なアプローチを採用。
GitHub概要
docopt/docopt
Create *beautiful* command-line interfaces with Python
スター7,984
ウォッチ161
フォーク563
作成日:2012年4月7日
言語:Python
ライセンス:MIT License
トピックス
なし
スター履歴
データ取得日時: 2025/7/25 02:04
フレームワーク
docopt
概要
docoptは、Pythonスクリプトのdocstringから自動的にコマンドラインインターフェース(CLI)を生成する革新的なライブラリです。「あなたのプログラムのヘルプメッセージがそのまま仕様になる」という哲学に基づいて設計されており、宣言的なアプローチでCLIを定義できます。オリジナル版は開発が停滞しており、現在はdocopt-ngへの移行が推奨されています。
詳細
docoptは、従来のCLIライブラリとは全く異なるアプローチを採用しています。プログラムの使用方法を記述したdocstringをパースし、その記述に基づいて自動的に引数解析を行います。この方法により、ドキュメントとコードの乖離を防ぎ、常に最新の使用方法が保証されます。
メンテナンス状況(2024年)
- オリジナルdocopt: 2013年のバージョン0.6.2以降更新されておらず、28のプルリクエストが未処理のまま放置されている状況です。
- docopt-ng: Jazzbandプロジェクトによって維持されているフォークで、型ヒント、完全なテストカバレッジ、そして継続的なメンテナンスが提供されています。最新リリースは2023年5月です。
- 将来の展望: docopt-ngも長期的なメンテナンス継続に不安があり、より現代的なCLIフレームワークへの移行が検討されています。新規プロジェクトでは、型ヒントや入力検証などの機能を持つより現代的なフレームワークの使用が推奨されています。
主な特徴
- Docstring駆動開発: プログラムのdocstringに
Usage:
セクションを記述するだけでCLIを定義 - 自己文書化: ヘルプメッセージとパーサーが同じ場所に存在
- パターンベース構文: 慣習的なコマンドラインヘルプメッセージの記法を使用
- 最小限のコード: わずか数行でCLIを実装可能
- 辞書形式の出力: パース結果は扱いやすい辞書として返される
- POSIX準拠: 標準的なコマンドライン規約に従う
使用パターン構文
- 位置引数:
<file>
,<host>
のように山括弧で囲む - オプション:
-h
,--help
, または-h, --help
の形式 - コマンド:
add
,ship
のような単純な単語 - 必須要素:
()
で囲む(例:(add|rm)
) - オプション要素:
[]
で囲む(例:[--timeout=<seconds>]
) - 排他的要素:
|
で区切る(例:(-a|-b)
) - 繰り返し要素:
...
を付ける(例:NAME...
)
メリット・デメリット
メリット
- ドキュメントとコードの一体化により保守性が向上
- 直感的で読みやすい構文
- 学習コストが低い
- コードが簡潔で美しい
- 標準的なコマンドライン規約に準拠
デメリット
- 複雑なCLIには向かない場合がある
- データ検証機能が組み込まれていない(別途実装が必要)
- エラーメッセージのカスタマイズが困難
- 型ヒントが本質的に制限される(docstring操作のため)
- オリジナル版は完全に開発停止(2013年以降更新なし)
- docopt-ngも長期的なメンテナンス継続に不安がある
- デバッグが難しい場合がある
- 新規プロジェクトには他の現代的なCLIライブラリが推奨される
主要リンク
書き方の例
基本的な使用例
"""シンプルな挨拶プログラム
Usage:
hello.py <name> [--times=<n>]
hello.py -h | --help
hello.py --version
Options:
-h --help このヘルプメッセージを表示
--version バージョンを表示
--times=<n> 挨拶を繰り返す回数 [default: 1]
"""
from docopt import docopt
def main():
arguments = docopt(__doc__, version='Hello 1.0')
name = arguments['<name>']
times = int(arguments['--times'])
for _ in range(times):
print(f'こんにちは、{name}さん!')
if __name__ == '__main__':
main()
複数コマンドの例
"""ファイル管理ツール
Usage:
file_manager.py list [<directory>]
file_manager.py copy <source> <destination> [--force]
file_manager.py delete <file>... [--confirm]
file_manager.py (-h | --help)
file_manager.py --version
Commands:
list ディレクトリの内容を表示
copy ファイルをコピー
delete ファイルを削除
Options:
-h --help このヘルプメッセージを表示
--version バージョンを表示
--force 既存ファイルを上書き
--confirm 削除前に確認
"""
from docopt import docopt
import os
import shutil
def main():
args = docopt(__doc__, version='File Manager 1.0')
if args['list']:
directory = args['<directory>'] or '.'
files = os.listdir(directory)
for f in files:
print(f)
elif args['copy']:
source = args['<source>']
destination = args['<destination>']
if os.path.exists(destination) and not args['--force']:
print(f'エラー: {destination} は既に存在します。--forceを使用してください。')
else:
shutil.copy2(source, destination)
print(f'コピー完了: {source} -> {destination}')
elif args['delete']:
files = args['<file>']
if args['--confirm']:
response = input(f'{len(files)}個のファイルを削除しますか? (y/n): ')
if response.lower() != 'y':
print('削除をキャンセルしました。')
return
for file in files:
os.remove(file)
print(f'削除: {file}')
if __name__ == '__main__':
main()
Pydanticを使用した検証付きの例
"""座標計算ツール
Usage:
calculator.py distance <x1> <y1> <x2> <y2>
calculator.py midpoint <x1> <y1> <x2> <y2>
calculator.py (-h | --help)
calculator.py --version
Options:
-h --help このヘルプメッセージを表示
--version バージョンを表示
"""
from docopt import docopt
from pydantic import BaseModel, Field, ValidationError
import math
class CoordinateArgs(BaseModel):
x1: float = Field(..., alias='<x1>')
y1: float = Field(..., alias='<y1>')
x2: float = Field(..., alias='<x2>')
y2: float = Field(..., alias='<y2>')
distance: bool
midpoint: bool
def calculate_distance(x1, y1, x2, y2):
return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
def calculate_midpoint(x1, y1, x2, y2):
return ((x1 + x2) / 2, (y1 + y2) / 2)
def main():
raw_args = docopt(__doc__, version='Calculator 1.0')
try:
args = CoordinateArgs(**raw_args)
if args.distance:
result = calculate_distance(args.x1, args.y1, args.x2, args.y2)
print(f'距離: {result:.2f}')
elif args.midpoint:
mx, my = calculate_midpoint(args.x1, args.y1, args.x2, args.y2)
print(f'中点: ({mx:.2f}, {my:.2f})')
except ValidationError as e:
print('エラー: 無効な引数です。')
print(e)
exit(1)
if __name__ == '__main__':
main()
高度なパターンの例
"""Git風のサブコマンドを持つツール
Usage:
mytool init [<name>] [--bare]
mytool clone <url> [<directory>]
mytool commit -m <message> [--amend]
mytool push [<remote>] [<branch>]
mytool pull [<remote>] [<branch>] [--rebase]
mytool status [--short]
mytool log [--oneline] [--graph] [-n <number>]
mytool (-h | --help)
mytool --version
Options:
-h --help このヘルプメッセージを表示
--version バージョンを表示
--bare ベアリポジトリを作成
-m <message> コミットメッセージ
--amend 前回のコミットを修正
--rebase リベースを使用してプル
--short 短い形式で表示
--oneline 各コミットを1行で表示
--graph グラフ表示
-n <number> 表示するコミット数 [default: 10]
"""
from docopt import docopt
def main():
args = docopt(__doc__, version='MyTool 1.0')
# コマンドの判定と処理
if args['init']:
name = args['<name>'] or 'myproject'
bare = args['--bare']
print(f'プロジェクト "{name}" を初期化しました。')
if bare:
print('(ベアリポジトリ)')
elif args['clone']:
url = args['<url>']
directory = args['<directory>'] or url.split('/')[-1]
print(f'クローン中: {url} -> {directory}')
elif args['commit']:
message = args['<message>']
amend = args['--amend']
if amend:
print(f'前回のコミットを修正: "{message}"')
else:
print(f'コミット: "{message}"')
elif args['status']:
if args['--short']:
print('M file1.txt\nA file2.txt')
else:
print('変更されたファイル:\n file1.txt\n新規ファイル:\n file2.txt')
if __name__ == '__main__':
main()