讓你如「老」紳士般編寫 Python 命令行工具的開源項目:docopt


做者:HelloGitHub-Prodesire
python

HelloGitHub 的《講解開源項目》系列,項目地址:https://github.com/HelloGitHub-Team/Articlegit

1、前言

在第一篇「初探 docopt」的文章中,咱們初步掌握了使用 docopt 的三個步驟,瞭解了它不一樣於 argparse 的設計思路。
那麼 docopt 的使用模式都有哪些呢?其接口描述中都支持哪些語法規則呢?本文將帶你深刻了解 docoptgithub

本系列文章默認使用 Python 3 做爲解釋器進行講解。
若你仍在使用 Python 2,請注意二者之間語法和庫的使用差別哦~

2、使用模式

在上一篇文章中咱們提到 docopt 是經過定義一個包含特定內容的字符串,也就是接口描述,來達到描述命令行功能的目的。
那麼接口描述的整體規則是這樣的:編程

  • 位於關鍵字 usage:(大小寫不敏感)和一個可見的空行之間的文本內容會被解釋爲一個個使用模式。
  • useage: 後的第一個詞會被解釋爲程序的名稱,好比下面就是一個沒有命令行參數的示例程序:
Usage: cli
  • 接口描述中能夠包含不少有各類元素的模式,以描述命令行用法,好比:
Usage:
  cli command --option <argument>
  cli [<optional-argument>]
  cli --another-option=<with-argument>
  cli (--either-that-option | <or-this-argument>)
  cli <repeating-argument> <repeating-argument>...

2.1 位置參數:

使用 <> 包裹的參數會被解釋爲位置參數。bash

好比,咱們能夠指定兩個位置參數 xy ,先添加的 x 位於第一個位置,後加入的 y 位於第二個位置。那麼在命令行中輸入 1 2的時候,分別對應到的就是 xy函數

"""
Usage: cli <x> <y>
"""
from docopt import docopt

arguments = docopt(__doc__, argv=['1', '2'])
print(arguments)

其輸出爲:ui

{'<x>': '1',
 '<y>': '2'}

2.2 選項參數:-o --option

以單個破折號(-)開頭的的參數爲短選項,以雙破折號(--)開頭的參數爲長選項。this

  • 短選項支持集中表達多個短選項,好比 -abc 等價於 -a-b-c
  • 長選項後可跟參數,經過 空格= 指定,好比 --input ARG 等價於 --input=ARG
  • 短選項後可跟參數,通可選的 空格 指定,好比 -f FILE 等價於 -fFILE

在下面這個例子中,咱們但願經過 -n h 或 --name 來指定名字:命令行

"""
Usage:
  cli [options]

Options:
  -n, --name NAME   Set name.
"""
from docopt import docopt

arguments = docopt(__doc__, argv=['-n', 'Eric'])
print(arguments)

arguments = docopt(__doc__, argv=['-nEric'])
print(arguments)

arguments = docopt(__doc__, argv=['--name', 'Eric'])
print(arguments)

arguments = docopt(__doc__, argv=['--name=Eric'])
print(arguments)

上面的示例中,咱們經過 4 種方式(2 個短選項參數方式和 2 個長選項參數方式)來指定命令行輸入,其輸出均爲:設計

{'--name': 'Eric'}

須要注意的是:

--input ARG(而不是 --input=ARG)的含義是模糊不清的,由於這不能看出 ARG 到底是選項參數,
仍是位置參數。在 docopt 的使用模式中,只有在接口描述中定義了對應選項纔會被解釋爲一個帶參數的選項,
不然就會被解釋爲一個選項和一個獨立的位置參數。

-f FILE-fFILE 這種寫法也有一樣的模糊點。後者沒法說明這到底是一系列短選項的集合,
仍是一個帶參數的選項。只有在接口描述中定義了對應選項纔會被解釋爲一個帶參數的選項。

2.3 命令

這裏的命令也就是 argparse 中嵌套解析器所要完成的事情,準確的說,對整個命令行程序來講,實現的是子命令。

docopt 中,凡是不符合 --options<arguments> 約定的詞,均會被解釋爲子命令。

在下面這個例子中,咱們支持 createdelete 兩個子命令,用來建立或刪除指定路徑。而 delete 命令支持 --recursive 參數來代表是否遞歸刪除指定路徑:

"""
Usage:
  cli create
  cli delete [--recursive]

Options:
  -r, --recursive   Recursively remove the directory.
"""
from docopt import docopt

arguments = docopt(__doc__)
print(arguments)

直接指定 delete -r,輸出以下:

$ python3 cli.py delete -r

{'--recursive': True,
 'create': False,
 'delete': True}

2.4 可選元素:[optional elements]

以中括號「[]」包裹的元素(選項、參數和命令)均會被標記爲可選。多個元素放在一對中括號中或各自放在中括號中是等價的。好比:

Usage: cli [command --option <argument>]

等價於:

Usage: cli [command] [--option] [<argument>]

2.5 必填元素:(required elements)

沒被中括號「[]」包裹的全部元素默認都是必填的。但有時候使用小括號「()」將元素包裹住,用以標記必填是有必要的。
好比,要將多個互斥元素進行分組:

Usage: my_program (--either-this <and-that> | <or-this>)

另外一個例子是,當出現一個參數時,也要求提供另外一個參數,那麼就能夠這麼寫:

Usage: my_program [(<one-argument> <another-argument>)]

這個例子中 <one-argument><another-argument> 要麼都出現,要麼都不出現。

2.6 互斥參數:element|another

argparse 中要想實現互斥參數,還須要先調用 parser.add_mutually_exclusive_group() 添加互斥組,
再在組裏添加參數。而在 docopt 中就特別簡單,直接使用 | 進行分隔:

Usage: my_program go (--up | --down | --left | --right)

在上面的示例中,使用小括號「()」來對四個互斥選項分組,要求必填其中一個選項。
在下面的示例中,使用中括號「()」來對四個互斥選項分組,能夠不填,或填其中一個選項:

Usage: my_program go [--up | --down | --left | --right]

咱們還能夠發散一下思路,子命令自然須要互斥,那麼除了這種寫法:

Usage: my_program run [--fast]
       my_program jump [--high]

使用以下 | 的寫法,也是等價的:

Usage: my_program (run [--fast] | jump [--high])

2.7 可變參數列表:element...

可變參數列表也就是定義參數能夠有多個值。在 argparse 中,咱們經過 parser.add_argument('--foo', nargs='?') 來指定,其中 nargs 能夠是數字、?+*來表示參數個數。

docopt 中,天然也有相同的能力,使用省略號 ... 來實現:

Usage: my_program open <file>...
       my_program move (<from> <to>)...

若要參數提供 N 個,則寫 N 個參數便可,好比下面的示例中要求提供 2 個:

Usage: my_program <file> <file>

若要參數提供 0 個或多個,則配合中括號「[]」進行定義,以下 3 中定義方式等價:

Usage: my_program [<file>...]
       my_program [<file>]...
       my_program [<file> [<file> ...]]

若要參數提供 1 個或多個,則能夠這麼寫:

Usage: my_program <file>...

在下面完整示例中,所得到的 arguments{'<file>': ['f1', 'f2']}

"""
Usage:
  cli <file>...
"""
from docopt import docopt

arguments = docopt(__doc__, argv=['f1', 'f2'])
print(arguments)

2.8 選項簡寫: [options]

「[options]」用於簡寫選項,好比下面的示例中定義了 3 個選項:

Usage: my_program [--all --long --human-readable] <path>

--all             List everything.
--long            Long output.
--human-readable  Display in human-readable format.

能夠簡寫爲:

Usage: my_program [options] <path>

--all             List everything.
--long            Long output.
--human-readable  Display in human-readable format.

若是一個模式中有多個選項,那麼這會頗有用。

另外,若是選項包含長短選項,那麼也能夠用它們中的任意一個寫在模式中,好比下面的示例的模式中均使用短選項:

Usage: my_program [-alh] <path>

-a, --all             List everything.
-l, --long            Long output.
-h, --human-readable  Display in human-readable format.

2.9 [--]

當雙破折號「--」不是選項時,一般用於分隔選項和位置參數,以便處理例如將文件名誤認爲選項的狀況。
爲了支持此約定,須要在位置參數前添加 [--]

Usage: my_program [options] [--] <file>...

2.10 [-]

當單破折號「-」不是選項時,一般用於表示程序應處理 stdin,而非文件。爲了支持此約定,須要在使用模式中加入 [-]

2.11 選項描述

選項描述就是描述一系列選項參數的模式。若是使用模式中的選項定義是清晰的,那麼選項描述就是可選的。

選項描述能夠定義以下內容:

  • 短選項和長選項表明相同含義
  • 帶參數的選項
  • 有默認值的選項參數

選項描述的每一行須要以 --- 開頭(不算空格),好比:

Options:
  --verbose   # 好
  -o FILE     # 好
Other: --bad  # 壞, 沒有以 "-" 開頭

選項描述中,使用空格或「=」來鏈接選項和參數,以定義帶選項的參數。參數可使用 <Arg> 的形式,
或是使用 ARG 大寫字母的形式。可用逗號「,」來分隔長短選項。好比:

-o FILE --output=FILE       # 沒有逗號 長選項使用 "=" 分隔
-i <file>, --input <file>   # 有逗號, 長選項使用空格分隔

選項描述中每一個選項定義和說明之間要有兩個空格,好比:

--verbose MORE text.    # 壞, 會被認爲是帶參數 MORE 的選項
                        # --version 和 MORE text. 之間應該有2個空格
-q        Quit.         # 好
-o FILE   Output file.  # 好
--stdout  Use stdout.   # 好,2個空格

選項描述中在說明中使用 [default: <default-value>] 來給帶參數的選項賦以默認值,好比:

--coefficient=K  The K coefficient [default: 2.95]
--output=FILE    Output file [default: test.txt]
--directory=DIR  Some directory [default: ./]

3、小結

關於 docopt 的方方面面咱們都瞭解的差很少了,回過頭來看。對於命令行元信息的定義,它比 argparse 要來的更加簡潔。

argparse 像是命令式編程,調用一個個的函數逐步將命令行元信息定義清楚;而 docopt 則像是聲明式編程,經過聲明定義命令行元信息。

二者站在的維度不一樣,編程的套路也不盡相同,甚是有趣。

瞭解了這麼多,也該練練手了。在下篇文章中,咱們仍然會以 git 命令做爲實戰項目,看看如何使用 docopt 來實現 git 命令。


『講解開源項目系列』——讓對開源項目感興趣的人再也不畏懼、讓開源項目的發起者再也不孤單。跟着咱們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫咱們、加入咱們,讓更多人愛上開源、貢獻開源~

相關文章
相關標籤/搜索