This library helps to output formatted text into terminal window. It supports colors, typefaces and user-defined formatting functions.
There are already many excellent libraries that is very close. They all offer us different approaches and everyone of us can choose to their liking.
This library provides just one more approach which is seems most convenient to its author.
The library inherits principles of web CSS. We describe a display pattern and give it a name. Then we mark with that name different parts of the text. Example:
<style>
.mytext {font-weight: bold; color: red}
</style>
Some text comes here, <span class="mytext">and part of it is red
and bold</span>, so we can easy read and edit the HTML-code.Also in CSS we can mark up text directly in the line without names. This is so-called «inline styles». Example:
It is not recommended, <span style="font-weight: bold; color: red">but still possible</span>.In the terminal window, a similar effect is realized by embedding escape codes directly in the text. But the readability and usability of markup with this method is very difficult.
For example, if we want to print hyperlink underligned and in blue color, we must write something like this:
print('Download Python: \x1b[1m\x1b[7mhttps://python.org/\x1b[m.')Most of third-party libraries are designed to simplify operations with escape codes. And, Python's built-in string formatting methods are used as usual. After all, both are a way of displaying text. So why should we consider them separately?
In this project, the standard Python features and implicit terminal capabilities are combined as a single solution. We can declare styles using both.
Of course, this is not a literal implementation of CSS. It is not possible and it is not necessary.
The goal of this project is easy to write, easy to read and easy to understand.
To be written.
The styling is organized through chain notation. It's easy to insert them directly into the lines you print.
import css
print('This is the',
css.style('strong').bold,
'text example'.)
print('And this is the',
css.style('strong oblique').bold.italic,
'text example.')You are self-responsible for the way your chain of transformation is organized. Conflicting instructions will lead to unpredictable results.
But it's not hard at all. Just don't assign the same kind of formatting twice.
To apply the same display style to different strings, create an object of class css.style with the desired set of parameters.
import css
b = css.style().bold
i = css.style().italic
bi = css.style().bold.italicThe objects you created are callable. They get a string as a single parameter and apply the specified style to it.
print(b('This text is bold.'))
print(i('And this one is italic.'))
print(bi('This text is bold and italic.'))Note that the string you called with the object will be stored in text property until you assign a new value to it. If you want just print a seldom string using the style but keeping the original text unchanged, use display() method.
Most often in this case we have no need to save the last text value. But this is part of the functionality described in the next section.
If you don't need to use the same text repeatedly, just don't worry about it and use the library as if this feature didn't exist.
print(b('This text is bold.'))
print(b) # The same result as above
# Change stored text without calling:
b.text = 'This text is strong.'
print(b) # This text is strong.
# Print random text just using the style
print(b.display('Am I looking strong?'))
print(b) # Prints "This text is strong." again
print(b('This text is still strong.'))
print(b) # The value has been changed after call.This behavior is a bit of a Zen violation (There should be one-- and preferably only one --obvious way to do it). You can change text property both by calling and by assigning.
But different approaches can be useful depending on the use. Only the same method cannot be equally suitable for fast scripting, prototyping or complex projects. Just keep to some unified way when you design your application.
Sometimes you need the same style for different strings. But sometimes you need to print the same text in different styles. You can reassign the style template as so as you reassign the text.
Before you can assign a new style at the object, you must clear the previous one. To do this, include clear() method in your chain.
>>> text = css.style(' some text ')
' some text '
>>> text.rgbcolors((200,200,0), (0, 33, 128))
# colored text output
# You can clear the template and assign
# a new one here on the same line
>>> text.clear().bg256(200)
# output of recolored text
>>> text.bold
# The modification will be applied to the existing
# ones because `clear()` method was ignored.
# Also you can split the chain of transformations
# into several lines for better readability
>>> text.clear()
' some text '
>>> text.colors(css.YELLOW, css.BG_RED).strike
# colored text with strikethroughYes, as was promised, you can use functions in your styles declarations. It can be both built-in and your own.
To assing a function use apply method.
import css
# Let's begin to organize our style definitions in the class
class CSS():
h1 = css.style().\
colors(css.YELLOW, css.BG_BLUE).\
bold.\
apply(lambda x: x.center(50).upper())
h2 = css.style().bold.apply(str.title)
print(CSS.h1('Program start...'))
# Prints bold yellow text at center of blue
# rectangle in uppercase
print(CSS.h2('this is headline'))
# Prints 'This Is Headline' with bold typeThe apply method can be declared only once. If you combine several apply methods in one chain, only the last one will be executed. If you want to apply more than one function, use lambda or define your own.
One of the useful features is the built-in Python string formatting.
import css
class CSS():
ul_li = css.style().apply(lambda x: f' \N{BULLET} {x}')
print('', 'Unsorted list:', '-'*14, '', sep='\n')
for i in range(5):
print(CSS.ul_li(f'List item number {i+1}'))
# Unsorted list:
# --------------
#
# • List item number 1
# • List item number 2
# • List item number 3
# • List item number 4
# • List item number 5When using templates, remember that you're not working with <str> objects, but with objects of the <css.style> class. This means that if you want to use nested formatting, you'll have to explicitly convert it to a string. The methods of the str class do not work with the string representation of objects returned by the __str__ method. This applies in its entirety to the str.format method.
import css
class CSS():
ol_li = css.style().apply(lambda x: ' {{:>02}} {}'.format(x))
# Will be ' {:>02} text' after applying style,
# so we can use str.format() with it again.
print('', 'Ordered list:', '-'*13, '', sep='\n')
for num, fruit in enumerate(('Apples', 'Peaches', 'Melons', 'Plums')):
print(
str(
CSS.ol_li(fruit)
).format(num + 1)
)
# Ordered list:
# -------------
#
# 01 Apples
# 02 Peaches
# 03 Melons
# 04 PlumsThere are three different ways to use colors in POSIX terminals. First, 16 colors palette. Second, 256 colors palette. Third, full RGB color palette.
Colors could be applied to foreground and to background of string. Both colors may belong to different palettes at the same case.
This library supports all palettes, with two methods for 16-color and three methods for 256 and RGB. Two methods are for foreground and background. They get only one parameter for you can use different palettes at the same style. And third method gets foreground and background parameters together. But both must be in the same palette.
color(color_code: int)colors(*args)
Actually, you can use only colors method, as it takes any number of arguments, but using only first and second (if given). Foreground and background values for 16-colors palette has different numeric codes, so you can pass it in any оrder.
Colors predefined constants keeping in css class, so you can invoke them as arguments.
>>> css.style('red text').color(css.RED)
>>> css.style('green background').color(css.BG_GREEN)
>>> css.style('red text at green background').colors(css.RED, css.BG_GREEN)
>>> css.style('red text at green background').color(css.RED).color(css.BG_GREEN)bg256(color_code: int)fg256(color_code: int)colors256(foreground: int, background: int)
You can look at the color definitions in Wikipedia.
>>> css.style('red text').fg256(196)
>>> css.style('green background').bg256(70)
>>> css.style('red text at green background').colors256(196, 70)bgrgb(r: int, g: int, b: int)fgrgb(r: int, g: int, b: int)rgbcolors(foreground: tuple, background: tuple)
Usage examples:
>>> css.style('red text').fgrgb(255, 0, 0)
>>> css.style('green background').bgrgb(0, 255, 0)
>>> css.style('red text at green background').rgbcolors((255, 0, 0), (0, 255, 0))Typefaces are the simplest members of the style chain. They don't have arguments and are separated by a dot in any order.
The standard typefaces are as follows:
bold, faint, italic, underline, blink, fastblink, invert, conceal, strike.
Not all terminals fully support these parameters.
>>> css.style('Example').bold
>>> css.style('Example').italic
>>> css.style('Example').invert.strike
>>> css.style('Example').color(css.RED).underline.apply(str.lower)Different terminal programs and operating systems support different features and require different implementations. This problem has not been solved at the moment. The current implementation is only being developed for POSIX.
Maybe some of color codes and typefaces will work in Windows terminals. I don't know.
Because of the way the print function works, the following construction cannot be used:
import css
example = css.style('example')
print(example, example.bold)In this case both words will be printed in last formatting (in a bold typeface here).
This is because both objects here are first processed before printing. Since the object saves the text and formatting pattern after each call, in this case all its instances will have the formatting and text value that was made last.
However, the following example will print the lines as it was intended:
import css
example = css.style('example')
print(example, '', end='')
print(example.bold)In the case above each transformation processed independently.
The same problem occurs if we try to use a format method. Such line as print('{} {}'.format(example, example.bold)) will give us as a result two bolded words.
The solution is to use f-strings. A single string is passed to the print function as a single argument. So print processed variables step-by-step in order as it given.
print (f'{example} {example.bold}')This usage works correctly and gives us a result we expected.
When you create or call an object of css.style, any value passed converts to a string. When you operating with an object, you always mean a display representation via __str__() method.
Some functions may not accept string arguments. They will raise an exception if you use them directly in apply method.
The apply method was originally designed to operate with a string formatting, not for numeric expression processing. The function you specify in the apply applies to the string value inside the declared object (which is provided by text property).
When you meet an issue like that, you have to convert value to int or float directly. And only then call the function.
For example we want to define a style which take on numbers and represent them as a currency, according to local settings. Also we want it green, and right-aligned in column.
Look at this step by step:
import locale
from functools import partial
import css
locale.setlocale(locale.LC_ALL, 'RU_ru')
currency = partial(locale.currency, grouping=True, international=True)If you try to apply the style as follows, a ValueError will be raised:
# This is a wrong way
cur = css.style().apply(currency).color(css.GREEN)But this style will work correctly:
cur = css.style().apply(
lambda x: currency(
float(x)
)
).color(css.GREEN)Finally, let's make values aligned to right:
import locale
from functools import partial
import css
locale.setlocale(locale.LC_ALL, 'RU_ru')
currency = partial(locale.currency, grouping=True, international=True)
cur = css.style().apply(lambda x: f'{currency(float(x)):>20}').color(css.GREEN)
for income in (127, 5, 2347.32, 2765789.58675):
print(cur(income))
# 127,00 RUB
# 5,00 RUB
# 2 347,32 RUB
# 2 765 789,59 RUBExcellent.