圖片來源 : Mennonite Church USA Archives. 經Opensource.com. CC BY-SA 4.0修改
這是我的終端應用程式與偉大的命令列介面系列文章中的兩部分。在第一篇中,我討論了使命令列應用程式成為一種純粹為個人喜好的功能。在本文中,我將介紹如何在幾個庫的幫助下,在 Python 中實現這些功能。讀完後,讀者會了解到如何使用 Prompt Toolkit, Click(命令列介面建立工具包)、Pygments 和 Fuzzy Finder 來實現一個易於使用的 REPL。
我計劃在不到 20 行的 Python 程式碼中實現這一點。讓我們開始吧。
Python Prompt 工具
我認為這個庫作為命令列應用的瑞士軍刀——它扮演了readline, curses 和其他更多庫的角色。讓我們從安裝這個庫開始吧:
1 |
pip install prompt_toolkit |
我們將從一個簡單的 REPL 開始。典型的 REPL 能接收使用者的輸入,進行一個操作之後再列印結果。我們這裡建立一個“echo”的 REPL 。使用者輸入什麼它就列印什麼:
REPL
1 2 3 4 |
from prompt_toolkit import prompt while 1: user_input = prompt('>') print(user_input) |
以上便實現了一個 REPL。它可以讀取使用者的輸入並輸出使用者輸入的內容。在這個程式碼片段中的 prompt 函式來自於 prompt_toolkit 庫;它被用來替換 readline 庫。
歷史
為了增強我們的 REPL,我們可以新增命令歷史:
1 2 3 4 5 6 |
from prompt_toolkit import promptfrom prompt_toolkit.history import FileHistory while 1: user_input = prompt('>', history=FileHistory('history.txt'), ) print(user_input) |
我們剛剛向REPL新增了持久的歷史記錄。 現在我們可以使用向上/向下箭頭瀏覽歷史記錄,並使用Ctrl + R搜尋歷史。 這符合命令列的基本禮儀。
自動聯想
我在第一部分中介紹的可發現性技巧之一是自動聯想歷史命令。(我們看到這個功能在fish shell中開創了。)讓我們把這個功能新增到我們的REPL中:
1 2 3 4 5 6 7 |
from prompt_toolkit import promptfrom prompt_toolkit.history import FileHistoryfrom prompt_toolkit.auto_suggest import AutoSuggestFromHistory while 1: user_input = prompt('>', history=FileHistory('history.txt'), auto_suggest=AutoSuggestFromHistory(), ) print(user_input) |
我們需要做的只是在 prompt() API呼叫中新增一個新的引數。現在我們有了一個具有fish風格歷史自動聯想的 REPL。
自動補全
現在,讓我們通過自動補全功能來實現 Tab 補全,它會在使用者輸入過程中彈出參考項。
可是 REPL 怎麼知道給出哪些參考項呢?我們提供了一個可選條目的字典,用來給出提示。
以 SQL 的自動補全為例,來看看怎麼在 REPL 中實現吧。我們可以將 SQL 關鍵字儲存到自動補全字典。具體如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from prompt_toolkit import prompt from prompt_toolkit.history import FileHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.contrib.completers import WordCompleter SQLCompleter = WordCompleter(['select', 'from', 'insert', 'update', 'delete', 'drop'], ignore_case=True) while 1: user_input = prompt('SQL>', history=FileHistory('history.txt'), auto_suggest=AutoSuggestFromHistory(), completer=SQLCompleter, ) print(user_input) |
和上一節類似,我們簡單的使用了 prompt-toolkit 中叫 WordCompleter 的普通補全程式。它會從自動補全字典中匹配使用者的輸入,並且給出一個可選條目列表。
現在我們的 REPL 實現了自動補全、fish 風格歷史自動聯想,以及 up/down 鍵回顧輸入歷史。所有這些功能的實現只用了不到 10 行程式碼。
Click
Click是一個命令列建立工具包,可以方便地解析程式的命令列選項和引數。 本節不介紹如何使用Click作為引數解析器; 相反,我將談一些Click附帶的一些實用程式。
click 安裝很簡單:
1 |
pip install click |
Pager
Pager 是能將長輸出一次顯示在一個頁面上的 Pagers Unix 實用程式。它可以通過調節顯示較少,更多,最多等。通過 pager 顯示命令的輸出不僅僅設計友好,而且相得益彰。
我們進一步來看前面的示例,可以用 click.echo_via_pager() 來取代預設的 print() 語句。它會通過 pager 將輸出傳送到 stdout。這與平臺無關,所以可以在 Unix 或 Windows 中工作。 而且 click.echo_via_pager() 還會通過使用合適的預設值以在必要時顯示彩色程式碼:
1 2 3 4 5 6 7 8 9 10 |
from prompt_toolkit import promptfrom prompt_toolkit.history import FileHistoryfrom prompt_toolkit.auto_suggest import AutoSuggestFromHistoryfrom prompt_toolkit.contrib.completers import WordCompleterimport click SQLCompleter = WordCompleter(['select', 'from', 'insert', 'update', 'delete', 'drop'], ignore_case=True) while 1: user_input = prompt(u'SQL>', history=FileHistory('history.txt'), auto_suggest=AutoSuggestFromHistory(), completer=SQLCompleter, ) click.echo_via_pager(user_input) |
Editor
我上一篇文章中提到的一個細節是,當命令變得太過複雜時,就需要使用編輯器了。而 click 有一個簡單的 API 可以用來啟動一個編輯器,並將在編輯器中輸入的文字返回到應用程式中:
1 2 |
import click message = click.edit() |
Fuzzy Finder
Fuzzy Finder 是一種讓使用者用更少的按鍵減少程式碼提示的方式。然後,有一個類庫實現了 Fuzzy Finder。讓我們來安裝它:
1 |
pip install fuzzyfinder |
Fuzzy Finder 的 API 非常簡單。您只要傳遞部分字串和可能的選擇列表,Fuzzy Finder 就會根據相關性順序排列通過模糊演算法返回與之匹配的新列表。例如:
1 2 |
>>> from fuzzyfinder import fuzzyfinder >>> suggestions = fuzzyfinder('abc', ['abcd', 'defabca', 'aagbec', 'xyz', 'qux']) >>> list(suggestions)['abcd', 'defabca', 'aagbec'] |
現在我們有了 FuzzyFinder,我們將其新增到我們的 SQL REPL 中。 我們這樣做是為了定義一個自定義完成器,而不是 prompt-toolkit 附帶的 WordCompleter。例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
from prompt_toolkit import promptfrom prompt_toolkit.history import FileHistoryfrom prompt_toolkit.auto_suggest import AutoSuggestFromHistoryfrom prompt_toolkit.completion import Completer, Completionimport clickfrom fuzzyfinder import fuzzyfinder SQLKeywords = ['select', 'from', 'insert', 'update', 'delete', 'drop'] class SQLCompleter(Completer): def get_completions(self, document, complete_event): word_before_cursor = document.get_word_before_cursor(WORD=True) matches = fuzzyfinder(word_before_cursor, SQLKeywords) for m in matches: yield Completion(m, start_position=-len(word_before_cursor)) while 1: user_input = prompt(u'SQL>', history=FileHistory('history.txt'), auto_suggest=AutoSuggestFromHistory(), completer=SQLCompleter(), ) click.echo_via_pager(user_input) |
Pygments
現在讓我們為使用者輸入新增語法高亮,當我們在使用 SQL REPL 的時候,擁有彩色的 SQL 輸入將會非常棒。
Pygments 是一個語法高亮庫,它內建支援 300 多種語言。新增語法高亮後可以讓程式更加多彩,而且還能幫助使用者在執行 SQL 之前找到錯誤,例如輸入錯誤、不匹配的引號或者括號。
先安裝 Pygments:
1 |
pip install pygments |
然後使用 Pygments 為我們的 SQL REPL 來新增顏色:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from prompt_toolkit import promptfrom prompt_toolkit.history import FileHistoryfrom prompt_toolkit.auto_suggest import AutoSuggestFromHistoryfrom prompt_toolkit.completion import Completer, Completionimport clickfrom fuzzyfinder import fuzzyfinderfrom pygments.lexers.sql import SqlLexer SQLKeywords = ['select', 'from', 'insert', 'update', 'delete', 'drop'] class SQLCompleter(Completer): def get_completions(self, document, complete_event): word_before_cursor = document.get_word_before_cursor(WORD=True) matches = fuzzyfinder(word_before_cursor, SQLKeywords) for m in matches: yield Completion(m, start_position=-len(word_before_cursor)) while 1: user_input = prompt(u'SQL>', history=FileHistory('history.txt'), auto_suggest=AutoSuggestFromHistory(), completer=SQLCompleter(), lexer=SqlLexer, ) click.echo_via_pager(user_input) |
Pygments 庫能在 Prompt 工具上很好地工作。我們把 Pygments 提供的的 SqlLexer 傳入到 prompt API。現在所有使用者輸入都會被當做 SQL 語句,並能為其新增合適的顏色。
結語
通過建立一個功能強大的 REPL,它包括諸如歷史記錄、鍵盤繫結等全部通用 shell 功能和使用者友好的功能,如自動完成、模糊查詢、pager 支援、編輯器支援和語法高亮。我們用不到 20 個 Python 語句實現了所有這些。
很容易不是嗎?你也應該試著寫一個 stellar 的命令列應用程式。這些資源可能有所幫助:
- Click (命令列介面建立工具包)
- Fuzzy Finder 模糊搜尋
- Prompt Toolkit 提示工具包
- 請參考位於 prompt-toolkit 庫中的 Prompt Toolkit 教程和示例
- Pygments