This is the second part of my two-part series on Terminal applications with a great command-line user interface. In the first post, I discussed the features that make command-line applications a pure joy to use. In the second part, I will see how to implement these functions in Python with the help of some libraries. By the end of this article, the reader should have a solid understanding of how to use the Prompt Toolkit , Click (Command Line Interface Creation Kit), Pygments , and Fuzzy Finder to implement an easy-to-use REPL .

I plan to achieve this in less than 20 lines of Python code. let's start.

Python Prompt Toolkit

I like to think of this library as a swiss army knife for command line applications -- it's a replacement for readline , curses , and more. Let's install this library and get started.

 pip install prompt_toolkit

We'll start with a simple REPL. In general, REPLs take user input, perform operations, and print the results. In our example, we will build a REPL that "echo". It just prints back what the user entered.

REPL

 from prompt_toolkit import prompt

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

That's all there is to implementing a REPL. It can read the user's input and print out what they typed. The prompt function used in this code snippet is from the prompt_toolkit library; it is a replacement for the readline library.

history

To strengthen our REPL, we can add command history.

 from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory

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

We just added persistent history to the REPL. Now we can use the up/down arrows to browse the history and Ctrl + R to search the history. This satisfies the basic etiquette of the command line.

auto-suggest

One of the discoverability tricks I talked about in the first part is auto-suggestion of historical commands. (We saw this functionality in fish shell.) Let's add this functionality to our 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)

All we have to do is add a new parameter to the API call to prompt() . Now we have a REPL with fish -style auto-suggestions from history.

automatic completion

Now let's implement an enhancement to the Tab key with autocomplete, which will pop up possible suggestions when the user starts typing.

How does our REPL know what to suggest? We provide a dictionary of possible suggested items.

Let's say we are implementing a SQL REPL. We can store SQL keywords in our autocomplete dictionary. Let's see how to do this.

 from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion 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)

Again, we can simply use prompt-toolkit's built-in completer, called WordCompleter , which matches user input against a dictionary of possible suggestions and provides a list.

We now have a REPL that does autocompletion, takes fish suggestions from history, and traverses up/down history. All this is done in less than 10 lines of actual code.

Click

Click is a command line creation toolkit that makes it easy to parse command line option arguments and program arguments. This section doesn't talk about using Click as a parameter parser; instead, I'll take a look at some of the utilities that come with Click.

Installing Click is simple.

 pip install click

pager

Pagers are Unix utilities that display one page of output at a time. Examples of pagers are: less , more , most , etc. Displaying the output of a command via a pager is not only friendly design, but also decent practice.

Let's take a closer look at the previous example. Instead of using the default print() statement, we can use click.echo_via_pager() . This will take care of sending the output to stdout via a pager. It is platform independent, so it will work in Unix or Windows. click.echo_via_pager() will try to use a suitable default to be able to display the color code if necessary.

 from prompt_toolkit import prompt
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import WordCompleter
import click

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,
    )
    click.echo_via_pager(user_input)

edit

One of the benefits I mentioned in the last post is the ability to go back to the editor when the command gets too complicated. click again provides a simple API to start an editor and return the text entered in the editor to the application.

 输入click

message = click.edit()

Fuzzy Finder

Fuzzy Finder is a way for users to narrow down suggestions with minimal typing. Once again, there is a library that implements Fuzzy Finder. Let's install this library.

 pip install fuzzyfinder

The API of Fuzzy Finder is simple. You pass in a partial string and a list of possible choices, and Fuzzy Finder will return a new list that matches the partial string, sorted by relevance using a fuzzy algorithm. for example

 >>> from fuzzyfinder.main import fuzzyfinder
>> suggestions = fuzzyfinder('abc', ['abcd', 'defabca', 'aagbec', 'xyz', 'qux'] )
>> list(sustips)
['abcd', 'defabca', 'aagbec']

Now that we have our fuzzyfinder , let's add it to our SQL REPL. The way we do this is to define a custom completer instead of the **WordCompleter that comes with prompt-toolkit . for example

 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.main 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

Now let's add syntax highlighting to the user's input. We're building an SQL REPL and it would be nice to have colored SQL statements.

Pygments is a syntax highlighting library with built-in support for over 300 languages. Adding syntax highlighting makes the application colorful, which helps users spot errors -- such as typos, mismatched quotes, or parentheses -- before SQL is executed.

First install Pygments.

 pip install pygments

Let's color our SQL REPL using Pygments.

 from prompt_toolkit import prompt

from prompt_toolkit.history import FileHistory

从 prompt_toolkit.auto_suggest 导入 AutoSuggestFromHistory

从 prompt_toolkit.completion 导入 Completer, Completion

导入点击

从 fuzzyfinder 导入 fuzzyfinder

from pygments.lexers.sql import SqlLexer



SQLKeywords = ['选择', '来自', '插入', '更新', '删除', '放弃']



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:

            产量完成(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)

The Prompt Toolkit works well with the Pygments library. We choose SqlLexer provided by Pygments and pass it into the prompt** API of prompt-toolkit. All user input is now treated as SQL statements and colored appropriately.

Epilogue

This concludes our journey to create a robust REPL with all the features of a normal shell, such as history, keybindings, and user-friendly features such as autocompletion, fuzzy lookup, pager support, editor support, and Syntax highlighting. We implemented all of this in less than 20 Python statements.

Isn't it easy? Now you have no excuse not to write a great command line application. These resources may be helpful.

*Learn more at Amjith Ramanujam's PyCon US 2017 talk, Awesome Commandline Tools , May 20 in Portland, OR.


universe_king
3.4k 声望680 粉丝