用于构建优秀命令行的4个Python库
在本文中,我将介绍如何在几个库的帮助下,在 Python 中实现这些功能。读完后,读者会了解到如何使用 Prompt Toolkit, Click(命令行界面创建工具包)、Pygments 和 Fuzzy Finder 来实现一个易于使用的 REPL。
进群进群:700341555可以获取Python各类入门学习资料!
这是我的微信公众号【Python编程之家】各位大佬用空可以关注下,每天更新Python学习方法,感谢!
我计划在不到 20 行的 Python 代码中实现这一点。让我们开始吧。
Python Prompt 工具
我认为这个库作为命令行应用的瑞士军刀——它扮演了readline, **curses **和其他更多库的角色。让我们从安装这个库开始吧:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">pip install prompt_toolkit</pre>
我们将从一个简单的 REPL 开始。典型的 REPL 能接收用户的输入,进行一个操作之后再打印结果。我们这里创建一个“echo”的 REPL 。用户输入什么它就打印什么:
REPL
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from prompt_toolkit import prompt
while 1:
user_input = prompt('>')
print(user_input)</pre>
以上便实现了一个 REPL。它可以读取用户的输入并输出用户输入的内容。在这个代码片段中的 prompt 函数来自于 **prompt_toolkit **库;它被用来替换 readline 库。
历史
为了增强我们的 REPL,我们可以添加命令历史:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from prompt_toolkit import promptfrom prompt_toolkit.history import FileHistory
while 1:
user_input = prompt('>',
history=FileHistory('history.txt'),
)
print(user_input)</pre>
我们刚刚向REPL添加了持久的历史记录。 现在我们可以使用向上/向下箭头浏览历史记录,并使用Ctrl + R搜索历史。 这符合命令行的基本礼仪。
自动联想
我在第一部分中介绍的可发现性技巧之一是自动联想历史命令。(我们看到这个功能在fish shell中开创了。)让我们把这个功能添加到我们的REPL中:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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)</pre>
我们需要做的只是在 **prompt() **API调用中添加一个新的参数。现在我们有了一个具有fish风格历史自动联想的 REPL。
自动补全
现在,让我们通过自动补全功能来实现 Tab 补全,它会在用户输入过程中弹出参考项。
可是 REPL 怎么知道给出哪些参考项呢?我们提供了一个可选条目的字典,用来给出提示。
以 SQL 的自动补全为例,来看看怎么在 REPL 中实现吧。我们可以将 SQL 关键字存储到自动补全字典。具体如下:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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)</pre>
和上一节类似,我们简单的使用了 prompt-toolkit 中叫 **WordCompleter **的普通补全程序。它会从自动补全字典中匹配用户的输入,并且给出一个可选条目列表。
现在我们的 REPL 实现了自动补全、fish 风格历史自动联想,以及 up/down 键回顾输入历史。所有这些功能的实现只用了不到 10 行代码。
Click
Click是一个命令行创建工具包,可以方便地解析程序的命令行选项和参数。 本节不介绍如何使用Click作为参数解析器; 相反,我将谈一些Click附带的一些实用程序。
click 安装很简单:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">pip install click</pre>
Pager
Pager 是能将长输出一次显示在一个页面上的 Pagers Unix 实用程序。它可以通过调节显示较少,更多,最多等。通过 pager 显示命令的输出不仅仅设计友好,而且相得益彰。
我们进一步来看前面的示例,可以用 **click.echo_via_pager() 来取代默认的 print() **语句。它会通过 pager 将输出发送到 stdout。这与平台无关,所以可以在 Unix 或 Windows 中工作。 而且 **click.echo_via_pager() **还会通过使用合适的默认值以在必要时显示彩色代码:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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)</pre>
Editor
我上一篇文章中提到的一个细节是,当命令变得太过复杂时,就需要使用编辑器了。而 **click **有一个简单的 API 可以用来启动一个编辑器,并将在编辑器中输入的文本返回到应用程序中:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import click
message = click.edit()</pre>
Fuzzy Finder
Fuzzy Finder 是一种让用户用更少的按键减少代码提示的方式。然后,有一个类库实现了 Fuzzy Finder。让我们来安装它:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">pip install fuzzyfinder</pre>
Fuzzy Finder 的 API 非常简单。您只要传递部分字符串和可能的选择列表,Fuzzy Finder 就会根据相关性顺序排列通过模糊算法返回与之匹配的新列表。例如:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">>>> from fuzzyfinder import fuzzyfinder
suggestions = fuzzyfinder('abc', ['abcd', 'defabca', 'aagbec', 'xyz', 'qux']) >>> list(suggestions)['abcd', 'defabca', 'aagbec']</pre>
现在我们有了 FuzzyFinder,我们将其添加到我们的 SQL REPL 中。 我们这样做是为了定义一个自定义完成器,而不是 prompt-toolkit 附带的 WordCompleter。例如:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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)</pre>
Pygments
现在让我们为用户输入添加语法高亮,当我们在使用 SQL REPL 的时候,拥有彩色的 SQL 输入将会非常棒。
Pygments 是一个语法高亮库,它内置支持 300 多种语言。添加语法高亮后可以让程序更加多彩,而且还能帮助用户在执行 SQL 之前找到错误,例如输入错误、不匹配的引号或者括号。
先安装 Pygments:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">pip install pygments</pre>
然后使用 Pygments 为我们的 SQL REPL 来添加颜色:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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)</pre>
Pygments 库能在 Prompt 工具上很好地工作。我们把 Pygments 提供的的 SqlLexer 传入到 prompt API。现在所有用户输入都会被当做 SQL 语句,并能为其添加合适的颜色。
结语
通过创建一个功能强大的 REPL,它包括诸如历史记录、键盘绑定等全部通用 shell 功能和用户友好的功能,如自动完成、模糊查找、pager 支持、编辑器支持和语法高亮。我们用不到 20 个 Python 语句实现了所有这些。
很容易不是吗?你也应该试着写一个 stellar 的命令行应用程序。这些资源可能有所帮助:
-
Click (命令行界面创建工具包)
-
Fuzzy Finder 模糊搜索
-
Prompt Toolkit 提示工具包
-
请参考位于 prompt-toolkit 库中的 Prompt Toolkit 教程和示例
-
Pygments