💾 Archived View for dmerej.info › blog › 0036-introducing-python-cli-ui.gmi captured on 2021-12-06 at 14:29:53. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
2017, Apr 10 - Dimitri Merejkowsky License: CC By 4.0
For quite some time I've been adding a file called `ui.py` in some of the Python projects I was working on.
Since I believe in the rule of three[1] and I already have three different projects using it, I've decided to share it to the world.
1: https://blog.codinghorror.com/rule-of-three/
Feel free to take a look at the github page[2].
2: https://github.com/dmerejkowsky/python-cli-ui
Let's start with a screen shot:
It's a module to write colorful command line interfaces in Python.
It's a tiny wrapper on top of `colorama`, with a (IMHO) nicer API than crayons[3] or lazyme[4].
3: https://pypi.python.org/pypi/crayons
4: https://pypi.python.org/pypi/lazyme
Here's an example:
ui.info(ui.red, "Error", ui.reset, ui.bold, file_path, ui.reset, "not found")
This will print the word 'Error' in red, the file path in bold, and the 'not found' normally.
The API follows the behavior of the `print()` function: by default, tokens are separated by spaces, `\n` is added at the end, and you can specify your own `sep` and `end` keywords if you need to.
It also allows to display items of a list, taking care of "off-by-one" errors and aligning the numbers nicely (note the leading space for `1/12`)
>>> months = ["January", "February", ..., "December"] >>> for i, month in enumerate(months): >>> ui.info_count(i, 12, month)
>>> first_name = "John" >>> last_name = "Doe" >>> adress = """\ ACME Inc. 795 E Dragram Tucson AZ 85705 USA """ >>> ui.info("People") >>> ui.info(ui.tabs(1), first_name, last_name) >>> ui.info(ui.tabs(1), "Adress:") >>> ui.info(ui.indent(2, adress)) People: John Doe Adress: 795 E Dragram Tucson AZ 85705 USA
You can define your own suite of characters, which will get a color, a unicode and an ASCII representation (so that it works on Windows too.)
For instance:
check = UnicodeSequence(green, "✓", "ok") ui.info("Success!", ui.check)
On Linux:
On Windows:
The module also contains "high-level" methods such as `info_1`, `info_2`, `info_3`, so that you can write:
ui.info_1("Making some tea") ... ui.info_2("Boiling water") ... ui.info_3("Turning kettle on") ... ui.info_1("Done")
Which will be rendered as:
This allows you to group your messages in a coherent way. Here the main message is 'Making some tea'. 'Boiling water' is a sub-task of making the tea, and 'Turning the kettle on' is a sub-task of the boiling water process.
In the same vein, `warning`, `error` and `fatal` methods are provided for when things go wrong. (The last one calls `sys.exit()`, hence the name)
There's also a `debug` function, for messages you are only interested when debugging: you can control the verbosity using the `CONFIG` global dictionary.
ui.CONFIG['quiet'] = True ui.info("this is some info") # won't get printed
ui.CONFIG['verbose'] = True ui.debug("A debug message") # will get printed
>>> domain = ui.ask_string("Please enter your domain name", default="example.com") >>> print("You chose:", domain) > Please enter your domain name (example.com) (nothing) > You chose example.com >>> domain = ui.ask_string("Please enter your domain name", default="example.com") >>> print("You chose:", domain) > Please enter your domain name (example.com) foobar.com > You chose foobar.com
Note how the prompt goes from `Y/n` (y uppercase), to `y/N` (n uppercase) depending on the default value:
>>> with_sugar = ui.ask_yes_no("With sugar?", default=True) > "With sugar ? (Y/n)" n > False >>> with_cream = ui.ask_yes_no("With cream?", default=False) > "With cream? (y/N)" (nothing) > False
Note how the user is stuck in a loop until he enters a valid answer, and how the first item is selected by default:
>>> choices = ["apple", "orange", "banana"] >>> answer = ui.ask_choice("Select a fruit:", choices) > Select a fruit: 1. apple (default) 2. orange 3. banana > foobar Please enter a number between 1 and 3 > 4 Please enter a number between 1 and 3 > 2 >>> print(answer) oranges
@ui.timer("Doing foo") def foo(): # something that takes time >>> foo() ... # output of the foo method Doing foo took 3min 13s
Works also in a `with` statement:
with ui.timer("making foobar"): foo() bar()
>>> commands = ["install", "remove"] >>> user_input = input() intall >>> ui.did_you_mean("No such command", user_input, choices) No such command. Did you mean: install?
You can also write tests to assert that a certain message matching a regexp was emitted.
def say_hello(name): ui.info("Hello", name) def test_say_hello(messages): say_hello("John") assert messages.find(r"Hello\w+John")
Well, I hope you'll find this module useful.
It is available as `python-cli-ui` on pypi
Cheers!
----