Power UP Your CLI with prompt_toolkit

cli

緣起

之前的工作除了寫 code 之外,另外還得身兼 DevOps。在做 DevOps 的過程當中,不得不查查資料庫,或是看看 kubernetes cluster 的狀況。在過程當中,我發現了 pgcli 以及 kube-shell 這兩套好用的工具,補足了原本 cli 的不足。

下圖是 pgcli 的簡單 demo

cli

我們可以看到在 pgcli 當中,我們可以有 autocomplete, syntax highlighting 等功能,你在做 select 的時候也會自動列出資料庫當中的所有 Table,這樣的功能可以讓你在工作上面省了不少查找的時間,讓你更有效率。

kube-shell 也是有着類似的特性,讓你可以從 kubectl 後面要接怎樣的參數當中解放出來。

在這邊我發現 pgcli 跟 kube-shell 都有使用到 python-prompt-toolkit 這個 Python Package,讓我興起了想要介紹 python-prompt-toolkit 的想法。

讓我們談談 CLI

說起 CLI,寫程式的人應該不陌生。不管是使用 Windows, Mac or Linux 的人,應該都是習慣在 command line 下面討生活的經驗。不知道大家還記不記得自己在剛開始使用 command line 的情況?要記得 cd, ls 等等的指令已經很痛苦了,更不用說後面接的參數到底是要接啥。

當然用習慣的人會說,有 help 可以查啊。不過有時候你連要怎麼看到 help 都不一定記得起來,到底是要 -h 呢?還是 --help 呢?還是只要 help 就好了呢?

下面是 git 的所有指令,你記得多少呢?

另外還有一個很惱人的問題,很多的指令其實也沒有一致性。以 django manage.py 的 commmand 來說,如果要做 i18n 你會需要 make message 這個指令。不過如果沒有常用的人,以下那個指令是對的呢?

  • make_message
  • makemessage
  • makemessages
  • make_messages

這邊可以看到可能要加底線,可能要用單數或是複數,有許多種組合,有時候又不一定有一致性,要記起來還是挺痛苦的。

爲了解決這些問題,很多人會用 alias 或是 bash complete, zsh plugin 等等方式來讓自己更有效率一點,不過還是有點麻煩以及其侷限之處。

CLI 能不能做得更好?

跟 GUI 比起來,CLI 最大的問題就是 CLI 把很多東西都藏起來了。要讓 CLI 可以用起來更方便一些,最要想辦法讓我們可以更容易的找到這些藏起來的功能。

爲了達成讓 CLI 更容易的使用,一般來說會有幾種方式。

Auto Complete

Auto Complete 應該算是最常用的功能,只要按一下 tab 就可以自動補完,可以讓你不用記一堆亂七八糟的指令。

Auto Suggestion

我第一次看到 Auto Suggestion 的功能是在 fish shell 上面。他會自動根據之前你輸入過的指令,來建議可能的內容。

History

只要有用 bash 的人應該都知道用上下鍵就可以找到之前打過的內容吧。

Syntax Coloring

有 syntax coloring 可以把關鍵字 highlight 出來,讓你對你打的指令更一目瞭然。

Python Prompt Toolkit

如果今天想要在你的 CLI 實作以上的幾個功能,看起來好像有點麻煩,不過因為有了 prompt_toolkit,所以只要簡單的輸入幾行程式碼,就可以讓你完成以上的幾個功能。下面讓我們來簡單地實作一個 SQL 的 REPL 吧。

首先,你可以把 prompt_toolkit 當做 readline 來用,另外因為我們想要做一個 REPL,所以外面會再包一層 while loop

from prompt_toolkit import prompt

while 1:
    text = prompt('> ',
    print(text)

接着,我們想要來加上 History 的功能,我們想要把 History 存在檔案當中,所以會使用 FileHistory。在這邊,我們把 history 資料存在一個名叫 history 的檔案當中。

from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory

while 1:
    text = prompt('> ',
                  history=FileHistory('history'),
                  )
    print(text)

有了 History 之後,我們就可以再加上 AutoSuggest 的功能了,透過 AutoSuggestFromHistory 可以輕鬆地達成。

from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory

while 1:
    text = prompt('> ',
                  history=FileHistory('history'),
                  auto_suggest=AutoSuggestFromHistory(),
                  )
    print(text)

然後讓我們來做 auto complete 吧。我們來用最簡單的 WordCompleter,這邊因爲我們想要來做 SQL 的 AutoComplete,所以我們選了四個 SQL 的關鍵字。

完成之後,再執行程式,應該會發現按下 tab 會有 auto complete 的功能。

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

completer = WordCompleter(['SELECT', 'WHERE', 'FROM', 'USE'], ignore_case=True)

while 1:
    text = prompt('> ',
                  history=FileHistory('history'),
                  auto_suggest=AutoSuggestFromHistory(),
                  completer=completer,
                  )
    print(text)

最後,我們來加上 Syntax Highlighting。我們透過 pygements 來幫我們完成這個功能。

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

from pygments.lexers.sql import SqlLexer

completer = WordCompleter(['SELECT', 'WHERE', 'FROM', 'USE'], ignore_case=True)

while 1:
    text = prompt('> ',
                  history=FileHistory('history'),
                  auto_suggest=AutoSuggestFromHistory(),
                  completer=completer,
                  lexer=SqlLexer,
                  )
    print(text)

執行之後就會看到關鍵字已經換了不同的顏色。

不到 20 行 code,我們就加上了 History, AutoSuggest, Auto Complete, Syntax Highlighting 功能。

Summary

如果你有做 CLI 的需求,或是遇到你覺得不好用的 CLI,都可以透過 prompt_toolkit 來做得更好。透過短短的幾行,就可以加上不少讓使用者體驗可以更好的功能。

如果要學習更多關於 prompt_toolkit,可以直接到它豐富的 example,直接看程式碼最快!



Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *