Python 命令列之旅:使用 click 實現 git 命令
作者:HelloGitHub-Prodesire
HelloGitHub 的《講解開源專案》系列,專案地址:https://github.com/HelloGitHub-Team/Article
一、前言
在前面五篇介紹 click
的文章中,我們全面瞭解了 click
的強大能力。按照慣例,我們要像使用 argparse
和 docopt
一樣使用 click
來實現 git 命令。
本文的關注點並不在 git
的各種命令是如何實現的,而是怎麼使用 click
去打造一個實用命令列程式,程式碼結構是怎樣的。因此,和 git
相關的操作,將會使用 gitpython
庫來簡單實現。
為了讓沒讀過 使用 xxx 實現 git 命令
xxx
指 argparse
和 docopt
) 的小夥伴也能讀明白本文,我們仍會對 git
常用命令和 gitpython
做一個簡單介紹。
本系列文章預設使用 Python 3 作為直譯器進行講解。
若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~
二、git 常用命令
當你寫好一段程式碼或增刪一些檔案後,會用如下命令檢視檔案狀態:
git status
確認檔案狀態後,會用如下命令將的一個或多個檔案(夾)新增到暫存區:
git add [pathspec [pathspec ...]]
然後使用如下命令提交資訊:
git commit -m "your commit message"
最後使用如下命令將提交推送到遠端倉庫:
git push
我們將使用 click
和 gitpython
庫來實現這 4 個子命令。
三、關於 gitpython
gitpython 是一個和 git
倉庫互動的 Python 第三方庫。
我們將借用它的能力來實現真正的 git
邏輯。
安裝:
pip install gitpython
四、思考
在實現前,我們不妨先思考下會用到 click
的哪些功能?整個程式的結構是怎樣的?
click
git
的 4 個子命令的實現其實對應於四個函式,每個函式使用 click
的 command
來裝飾。
而對於 git add
和 git commit
click.argument
和表示選項的 click.option
來裝飾。
程式結構
程式結構上:
- 例項化
Git
物件,供全域性使用 - 定義
cli
函式作為命令組,也就是整個命令程式的入口 - 定義四個命令對應的實現函式
status
、add
、commit
、push
則基本結構如下:
import os
import click
from git.cmd import Git
git = Git(os.getcwd())
@click.group()
def cli():
"""
git 命令列
"""
pass
@cli.command()
def status():
"""
處理 status 命令
"""
pass
@cli.command()
@click.argument('pathspec', nargs=-1)
def add(pathspec):
"""
處理 add 命令
"""
pass
@cli.command()
@click.option('-m', 'msg')
def commit(msg):
"""
處理 -m <msg> 命令
"""
pass
@cli.command()
def push():
"""
處理 push 命令
"""
pass
if __name__ == '__main__':
cli()
下面我們將一步步地實現我們的 git
程式。
五、實現
假定我們在 click-git.py 檔案中實現我們的 git
程式。
5.1 status 子命令
status
子命令不接受任何引數和選項,因此其實現函式只需 cli.command()
裝飾。
@cli.command()
def status():
"""
處理 status 命令
"""
cmd = ['git', 'status']
output = git.execute(cmd)
click.echo(output)
不難看出,我們最後呼叫了真正的 git status
來實現,並列印了輸出。
5.2 add 子命令
add
子命令相對於 status
子命令,需要接受任意個 pathspec 引數,因此增加一個 click.argument
裝飾器,並且在 add
函式中需要增加同名的 pathspec
入參。
經 click
處理後的 pathspec
其實是個元組,和列表相加前,需要先轉換為列表。
@cli.command()
@click.argument('pathspec', nargs=-1)
def add(pathspec):
"""
處理 add 命令
"""
cmd = ['git', 'add'] + list(pathspec)
output = git.execute(cmd)
click.echo(output)
當我們執行 python3 click-git.py add --help
時,結果如下:
Usage: click-git.py add [OPTIONS] [PATHSPEC]...
處理 add 命令
Options:
--help Show this message and exit.
既然 git add
能接受任意多個 pathspec
,那麼 add(pathspec)
的引數其實改為複數形式更為合適,但我們又希望幫助資訊中是單數形式,這就需要額外指定 metavar
,則有:
@cli.command()
@click.argument('pathspecs', nargs=-1, metavar='[PATHSPEC]...')
def add(pathspecs):
"""
處理 add 命令
"""
cmd = ['git', 'add'] + list(pathspecs)
output = git.execute(cmd)
click.echo(output)
5.3 commit 子命令
add
子命令相對於 status
子命令,需要接受 -m
選項,因此增加一個 click.option
裝飾器,指定選項名稱 msg
,並且在 commit
函式中增加同名入參。
@cli.command()
@click.option('-m', 'msg')
def commit(msg):
"""
處理 -m <msg> 命令
"""
cmd = ['git', 'commit', '-m', msg]
output = git.execute(cmd)
click.echo(output)
5.4 push 子命令
push
子命令同 status
子命令一樣,不接受任何引數和選項,因此其實現函式只需 cli.command()
裝飾。
@cli.command()
def push():
"""
處理 push 命令
"""
cmd = ['git', 'push']
output = git.execute(cmd)
click.echo(output)
至此,我們就實現了一個簡單的 git
命令列,使用 python click-git.py status
便可查詢專案狀態。
非常方便的是,每個命令函式的 docstring
都將作為這個命令的幫助資訊,因此,當我們執行 python3 click-git.py --help
會自動生成如下幫助內容:
Usage: click-git.py [OPTIONS] COMMAND [ARGS]...
git 命令列
Options:
--help Show this message and exit.
Commands:
add 處理 add 命令
commit 處理 -m <msg> 命令
push 處理 push 命令
status 處理 status 命令
想看整個原始碼,請戳 click-git.py 。
六、小結
本文簡單介紹了日常工作中常用的 git
命令,然後提出實現它的思路,最終一步步地使用 click
和 gitpython
實現了 git
程式。
對比 argparse
和 click
的實現版本,你會發現使用 click
來實現變得特定簡單:
- 相較於
argparse
,子解析器、引數型別什麼的統統不需要關心 - 相較於
docopt
,引數解析和命令呼叫處理也不需要關心
這無疑是 click
最大的優勢了。
關於 click
的講解將告一段落,回顧下 click
的至簡之道,你會愛上它。
現在,你已學會了三個命令列解析庫的使用了。但你以為這就夠了嗎?click
已經夠簡單了吧,夠直接了吧?但它仍然不是最簡單的。
在下篇文章中,將為大家介紹一個由谷歌出品的在 Python 界很火的命令列庫 —— fire
。
『講解開源專案系列』——讓對開源專案感興趣的人不再畏懼、讓開源專案的發起者不再孤單。跟著我們的文章,你會發現程式設計的樂趣、使用和發現參與開源專案如此簡單。歡迎留言聯絡我們、加入我們,讓更多人愛上開源、貢獻開源