那個極爽的命令行糾錯軟件 The Fuck 是如何工作的
改好的命令
這裏最重要的部分就是匹配規則了,規則是一個特殊模塊集,它有兩個方法:
-
match(command: Command)
→ bool – 匹配上規則則返回 True; -
get_new_command(command: Command)
→ str|list[str] – 否則返回修改後的命令或命令列表(當有多個可能匹配項)
我想這個應用隻是因為它的規則才這麼有趣,編寫自己的規則也很簡單。目前有 75 條可用的規則,大都是有第三方貢獻者寫的。命令是一個類似命名元組namedtuple這樣的數據結構:
Command(script: str, stdout: str, stderr: str)
其中 script 是與 shell 類型無關的錯誤命令。
處理不同 Shell 類型
在不同的 shell 中,描述 alias 的方式不同、語法不同(比如在 fish 中 && 表示為 and)、曆史命令的處理方法也不同,且 shell 還依賴特定的配置文件(.bashre
,.zshrc
等)。為了避免這些麻煩,在程序中有一個 shells 的模塊把這些與特定 shell 相關的命令轉化為與 sh 兼容的類型,並展開別名和環境變量。 所以我們使用shells.from_shell
方法來獲得 Command(前麵的章節提到過的)的實例,在 sh 裏運行並且獲得 stdout 和 stderr。
出錯的命令 → from_shell 模塊 → 與 shell 類型無關的命令 → (可以)在 sh 內運行 –> Command 實例
對修改好的命令也做了相似地處理,即把與特定 shell 無關的命令通過 shells.to_shell 模塊轉化為與 shell 相關的命令。
配置
The Fuck 是一個高可配置的應用,用戶可以開啟或關閉規則、配置 UI、設置規則選項還有進行其他的操作。用戶可以通過修改 ~/.thefuck/settring.py
文件以及環境變量來配置應用:
默認配置 → 通過 setting.py 文件更新 → 通過環境變量更新
之前版本中,配置對象以參數的形式傳遞到所有需要的場合,雖然那樣還不錯並且能夠測試,但存在過多的重複代碼。而現在是一個單例(thefuck.conf.settings
),類似 Django 中的 django.conf.settings
。
UI
The Fuck 的 UI 很簡單,它允許用戶通過(上下)箭頭的方式在修正過的命令列表中進行選擇,使用 Enter 來確認選擇,Ctrl+C 來跳出程序。 美中不足的是在 Python 標準庫中沒有辦法在非 Windows 下不通過 curses 來讀取鍵盤輸入,由於別名alias的特性我們又不能在這裏使用 curses。但容易寫出針對 Windows 的 msvrt.getch
:
import tty
import termios
def getch():
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
if ch == '\x03': # For compatibility with msvcrt.getch
raise KeyboardInterrupt
return ch
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
另外 UI 也需要修複好的程序命令組成的有序列表,且規則匹配耗時應該盡量較短。而加入簡單的啟發式算法後效果還不錯,首先我們按照優先級來匹配規則,第一個返回的修複過的命令是有最大優先級的命令。當用戶按下箭頭按鍵時再選擇其他的命令。所以在大多數的使用場景中都能很快完成任務。
整體來看
如果從整體來看一下這個應用,會發現它很簡單:
其中 controller(控製器)是當用戶使用 The Fuck 來修複錯誤命令時的程序入口,它初始化設置、準備 shells 的交互環境、從 Corrector (修正器)來獲取修正過的命令並在 UI 中選擇。Corrector 使用所有可用的規則來匹配當前命令並且返回所有可用的修複過的命令。關於UI、設置和規則就說到這裏。
測試
測試是所有軟件項目的最重要的部分之一。沒有測試,軟件可能會由於任一個改變而崩潰。我們使用 pytest 來進行單元測試。由於應用中存在規則,所以需要做很多測試來匹配和確認修正過的命令。所以,參數化的測試用例是很有用的,典型的測試是這樣的:
import pytest
from thefuck.rules.cd_mkdir import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='cd foo', stderr='cd: foo: No such file or directory'),
Command(script='cd foo/bar/baz',
stderr='cd: foo: No such file or directory'),
Command(script='cd foo/bar/baz', stderr='cd: can\'t cd to foo/bar/baz')])
def test_match(command):
assert match(command)
The Fuck 可以與許多種類的 shell 共同工作,而每個 shell 又需要特定的別名。為了保證所有別名可用,需要用到功能測試,其中用到了我寫的 pytest-docker-pexpect 模塊,在一個 docker 容器內設置一個場景來測試所有支持的命令。
發布
The Fuck 應用的最麻煩的部分是它的安裝,應用通過 pip 來發布,由此產生了一些問題:
- 有些平台上依賴 python 的頭文件(python-dev),所以我們需要告訴用戶手動地安裝;
- pip 不支持安裝後自動完成一些自定義操作,所以用戶需要手動配置一個別名;
- 有些用戶使用不支持的 python 版本,應用隻支持 2.7 或者 3.3+ 的版本;
- 有些老版本的 pip 根本就不安裝依賴項;
- 有些版本的 pip 忽視 Python 版本的依賴關係,所以需要為早於 3.4 的版本安裝 pathlib;
- 有趣的是有人對這個名字感到很憤怒並且嚐試從 pypi 中移除這個包;
原文發布時間為:2017-05-05
本文來自雲棲社區合作夥伴“Linux中國”
最後更新:2017-05-19 14:04:42