Amjith Ramanujam 发布的文章

在这个分为两篇的关于具有绝佳命令行界面的终端程序的系列文章的第二篇教程中,我们将讨论 Prompt、Toolkit、Click、Pygments 和 Fuzzy Finder 。

 title=

这是我的一个分为两篇的关于具有绝佳命令行界面的终端程序的系列文章的第二篇教程。在第一篇文章中,我们讨论了一些能够使命令行应用用起来令人感到愉悦的特性。在第二篇文章中,我们来看看如何用 Python 的一些库来实现这些特性。

我打算用少于 20 行 Python 代码来实现。让我们开始吧。

Python Prompt Toolkit

我习惯于把这个库称为命令行应用的瑞士军刀,它可以作为 readlinecurses 等的替代品。让我们首先安装这个库,然后开始该教程:

pip install prompt_toolkit

我们以一个简单的 REPL (LCTT 译注:REPL —— Read-Eval-Print Loop,交互式开发环境)开始。一个典型的 REPL 会接收用户的输入,进行一个操作,然后输出结果。比如在我们的例子中,我们将要实现一个具有 “回显” 功能的 REPL 。它仅仅是原样打印出用户的输入:

REPL

from prompt_toolkit import prompt

while 1:
    user_input = prompt('>')
    print(user_input)

这就是实现 REPL 的全部代码。它可以读取用户的输入,然后打印出用户的输入内容。在这段代码中使用的 prompt 函数来自 prompt_toolkit 库,它是 readline 库的一个替代品。

命令历史

为了增强我们的 REPL 的功能,我们可以添加命令历史:

from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory

while 1:
    user_input = prompt('>', 
                        history=FileHistory('history.txt'),
                       )
    print(user_input)

我们刚刚给 REPL 添加了持久的命令历史。现在,我们可以使用上/下箭头来浏览命令历史,并使用 Ctrl-R 来搜索命令历史。它满足了命令行的基本准则。

自动推荐

在第一篇教程中,我讲到的一个可发现性技巧是自动推荐历史命令。(我是首先在 fish shell 中看到的这一特性)让我们把这一特性加入到我们的 REPL 中:

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

while 1:
    user_input = prompt('>', 
                        history=FileHistory('history.txt'),
                        auto_suggest=AutoSuggestFromHistory(),
                       )
    print(user_input)

我们只需要给 prompt() API 调用添加一个新的参数。现在,我们有了一个具有 fish shell 风格的 REPL,它可以自动推荐历史命令

自动补全

现在,让我们通过自动补全来加强 Tab 补全。它能够在用户开始输入的时候弹出可能的命令推荐。

REPL 如何来进行推荐呢?我们使用一个字典来进行可能项的推荐。

比如说我们实现一个针对 SQL 的 REPL 。我们可以把 SQL 关键字存到自动补全字典里面。让我们看一看这是如何实现的:

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 的补全特性,它能够把用户输入和可能推荐的字典进行匹配,然后提供一个列表。

现在,我们有了一个能够自动补全、fish shell 风格的历史命令推荐以及上/下浏览历史的 REPL 。实现这些特性只用了不到 10 行的实际代码。

Click

Click 是一个命令行创建工具包,使用它能够更容易的为程序解析命令行选项的参数和常量。在这儿我们不讨论如何使用 Click 来作为参数解析器。相反,我们将会看看 Click 带有的一些功能。

安装 Click

pip install click

分页器

分页器是 Unix 系统上的实用工具,它们能够一次一页地显示很长的输出。分页器的一些例子包括 lessmoremost 等。通过分页器来显示一个命令的输出不仅仅是一个友好的设计,同时也是必要的。

让我们进一步改进前面的例子。我们不再使用默认的 print() 语句,取而代之的是 click.echo_via_pager() 。它将会把输出通过分页器发送到标准输出。这是平台无关的,因此在 Unix 系统或 Windows 系统上均能工作。如果必要的话,click_via_pager 会尝试使用一个合适的默认分页器来输出,从而能够显示代码高亮。

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
import 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)

编辑器

在我前面的文章中一个值得一提的细节是,当命令过于复杂的时候进入编辑器来编辑。Click 有一个简单的 API 能够打开编辑器,然后把在编辑器中输入的文本返回给应用。

import click
message = click.edit()

Fuzzy Finder

Fuzzy Finder 是一种通过少量输入来为用户减少推荐的方法。幸运的是,有一个库可以实现 Fuzzy Finder 。让我们首先安装这个库:

pip install fuzzyfinder

Fuzzy Finder 的 API 很简单。用户向它传递部分字符串和一系列可能的选择,然后,Fuzzy Finder 将会返回一个与部分字符串匹配的列表,这一列表是通过模糊算法根据相关性排序得出的。比如:

>>> from fuzzyfinder import fuzzyfinder

>>> suggestions = fuzzyfinder('abc', ['abcd', 'defabca', 'aagbec', 'xyz', 'qux'])

>>> list(suggestions)
['abcd', 'defabca', 'aagbec']

现在我们有了 fuzzyfinder,让我们把它加入到我们的 SQL REPL 中。方法是我们自定义一个 completer 而不是使用来自 prompt-toolkit 库的 WordCompleter 。比如:

from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import Completer, Completion
import click
from 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

pip install pygments

让我们使用 Pygments 来为 SQL REPL 添加颜色:

from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import Completer, Completion
import click
from fuzzyfinder import fuzzyfinder
from 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)

Prompt Toolkit 能够和 Pygments 一同很好的工作。我们把 Pygments 提供的 SqlLexer 加入到来自 prompt-toolkitprompt 中。现在,所有的用户输入都会被当作 SQL 语句,并进行适当着色。

结论

我们的“旅途”通过创建一个强大的 REPL 结束,这个 REPL 具有常见的 shell 的全部特性,比如历史命令,键位绑定,用户友好性比如自动补全、模糊查找、分页器支持、编辑器支持和语法高亮。我们仅用少于 20 行 Python 代码就实现了这个 REPL 。

不是很简单吗?现在,你没有理由不会写一个自己的命令行应用了。下面这些资源可能有帮助:

你也可以在我在 PyCon US 2017 的演讲优秀的命令行工具中学到更多东西,该会议是 5 月 20 日在波特兰,俄勒冈举行的。

(题图 : 美国 Mennonite 教堂档案 。 Opensource.com. CC BY-SA 4.0


作者简介:

Amjith Ramanujam - Amjith Ramanujam 是 pgclimycli 的创始人。人们认为它们很酷,他表示笑纳赞誉。他喜欢用 Python、JavaScript 和 C 编程。他喜欢写一些简单、易于理解的代码,有时候这样做是成功的。


via: https://opensource.com/article/17/5/4-practical-python-libraries

作者:Amjith Ramanujam 译者:ucasFL 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

让我们来看几个精心设计的 CLI 程序,以及如何解决一些可发现性问题。

在本文中,我会指出命令行界面的 可发现性 discoverability 缺点以及克服这些问题的几种方法。

我喜欢命令行。我第一次接触命令行是在 1997 的 DOS 6.2 上。我学习了各种命令的语法,并展示了如何在目录中列出隐藏的文件(attrib)。我会每次仔细检查命令中的每个字符。 当我犯了一个错误,我会从头开始重新输入命令。直到有一天,有人向我展示了如何使用向上和向下箭头按键遍历命令行历史,我被震惊了。

后来当我接触到 Linux 时,让我感到惊喜的是,上下箭头保留了它们遍历历史记录的能力。我仍然很仔细地打字,但是现在,我了解如何盲打,并且我能打的很快,每分钟可以达到 55 个单词的速度。接着有人向我展示了 tab 补完,再一次改变了我的生活。

在 GUI 应用程序中,菜单、工具提示和图标用于向用户展示功能。而命令行缺乏这种能力,但是有办法克服这个问题。在深入解决方案之前,我会来看看几个有问题的 CLI 程序:

1、 MySQL

首先让我们看看我们所钟爱的 MySQL REPL。我经常发现自己在输入 SELECT * FROM 然后按 Tab 的习惯。MySQL 会询问我是否想看到所有的 871 种可能性。我的数据库中绝对没有 871 张表。如果我选择 yes,它会显示一堆 SQL 关键字、表、函数等。(LCTT 译注:REPL —— Read-Eval-Print Loop,交互式开发环境)

MySQL gif

2、 Python

我们来看另一个例子,标准的 Python REPL。我开始输入命令,然后习惯按 Tab 键。瞧,插入了一个 Tab 字符,考虑到 Tab 在 Python 源代码中没有特定作用,这是一个问题。

 title=

好的用户体验

让我看下设计良好的 CLI 程序以及它们是如何克服这些可发现性问题的。

自动补全: bpython

Bpython 是对 Python REPL 的一个很好的替代。当我运行 bpython 并开始输入时,建议会立即出现。我没用通过特殊的键盘绑定触发它,甚至没有按下 Tab 键。

 title=

当我出于习惯按下 Tab 键时,它会用列表中的第一个建议补全。这是给 CLI 设计带来可发现性性的一个很好的例子。

bpython 的另一个方面是可以展示模块和函数的文档。当我输入一个函数的名字时,它会显示这个函数附带的签名以及文档字符串。这是一个多么令人难以置信的周到设计啊。

上下文感知补全:mycli

mycli 是默认的 MySQL 客户端的现代替代品。这个工具对 MySQL 来说就像 bpython 之于标准 Python REPL 一样。mycli 将在你输入时自动补全关键字、表名、列和函数。

补全建议是上下文相关的。例如,在 SELECT * FROM 之后,只有来自当前数据库的表才会列出,而不是所有可能的关键字。

 title=

模糊搜索和在线帮助: pgcli

如果您正在寻找 PostgreSQL 版本的 mycli,请看看 pgcli。 与 mycli 一样,它提供了上下文感知的自动补全。菜单中的项目使用模糊搜索缩小范围。模糊搜索允许用户输入整体字符串中的任意子字符串来尝试找到正确的匹配项。

 title=

pgcli 和 mycli 在其 CLI 中都实现了这个功能。斜杠命令的文档也作为补全菜单的一部分展示。

可发现性: fish

在传统的 Unix shell(Bash、zsh 等)中,有一种搜索历史记录的方法。此搜索模式由 Ctrl-R 触发。当再次调用你上周运行过的命令时,例如 sshdocker,这是一个令人难以置信的有用的工具。 一旦你知道这个功能,你会发现自己经常会使用它。

如果这个功能是如此有用,那为什么不每次都搜索呢?这正是 fish shell 所做的。一旦你开始输入命令,fish 将开始建议与历史记录类似的命令。然后,你可以按右箭头键接受该建议。

命令行规矩

我已经回顾了一些解决可发现性的问题的创新方法,但也有一些基本的命令行功能应该作为每个 REPL 所实现基础功能的一部分:

  • 确保 REPL 有可通过箭头键调用的历史记录。确保会话之间的历史持续存在。
  • 提供在编辑器中编辑命令的方法。不管你的补全是多么棒,有时用户只需要一个编辑器来制作完美的命令来删除生产环境中所有的表。
  • 使用分页器(pager)来管道输出。不要让用户滚动他们的终端。哦,要为分页器设置个合理的默认值。(记得添加选项来处理颜色代码。)
  • 提供一种通过 Ctrl-R 界面或者 fish 式的自动搜索来搜索历史记录的方法。

总结

在第 2 节中,我将来看看 Python 中使你能够实现这些技术的特定库。同时,请查看其中一些精心设计的命令行应用程序:

  • bpythonptpython:具有自动补全支持的 Python REPL。
  • http-prompt:交互式 HTTP 客户端。
  • mycli:MySQL、MariaDB 和 Percona 的命令行界面,具有自动补全和语法高亮。
  • pgcli:具有自动补全和语法高亮,是对 psql 的替代工具。
  • wharfee:用于管理 Docker 容器的 shell。

了解更多: Amjith Ramanujam 在 5 月 20 日在波特兰俄勒冈州举办的 PyCon US 2017 上的谈话“神奇的命令行工具”。


作者简介:

Amjith Ramanujam - Amjith Ramanujam 是 pgcli 和 mycli 的创始人。人们认为它们很酷,他表示笑纳赞誉。他喜欢用 Python、Javascript 和 C 编程。他喜欢编写简单易懂的代码,它们有时甚至会成功。


via: https://opensource.com/article/17/5/4-terminal-apps

作者:Amjith Ramanujam 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出