The humanfriendly package started out as a single humanfriendly
module. Eventually this module grew to a size that necessitated splitting up
the code into multiple modules (see e.g. tables,
terminal, text and
usage). Most of the functionality that remains in the
humanfriendly module will eventually be moved to submodules as well (as
time permits and a logical subdivision of functionality presents itself to me).
While moving functionality around like this my goal is to always preserve
backwards compatibility. For example if a function is moved to a submodule an
import of that function is added in the main module so that backwards
compatibility with previously written import statements is preserved.
If backwards compatibility of documented functionality has to be broken then
the major version number will be bumped. So if you’re using the humanfriendly
package in your project, make sure to at least pin the major version number in
order to avoid unexpected surprises.
The following aliases exist to preserve backwards compatibility, however a DeprecationWarning is triggered when they are accessed, because these aliases will be removed in a future release.
seconds – The number of seconds to sleep (an
integer or floating point number).
This method sleeps for the given number of seconds minus the
elapsed_time. If the resulting duration is negative
time.sleep() will still be called, but the argument
given to it will be the number 0 (negative numbers cause
time.sleep() to raise an exception).
The use case for this is to initialize a Timer inside
the body of a for or while loop and call
Timer.sleep() at the end of the loop body to rate limit
whatever it is that is being done inside the loop body.
For posterity: Although the implementation of sleep() only
requires a single line of code I’ve added it to humanfriendly
anyway because now that I’ve thought about how to tackle this once I
never want to have to think about it again :-P (unless I find ways to
improve this).
Format a number as a string including thousands separators.
Parameters:
number – The number to format (a number like an int,
long or float).
num_decimals – The number of decimals to render (2 by default). If no
decimal places are required to represent the number
they will be omitted regardless of this argument.
Returns:
The formatted number (a string).
This function is intended to make it easier to recognize the order of size
of the number being formatted.
Shorten a pathname to make it more human friendly.
Parameters:
pathname – An absolute pathname (a string).
Returns:
The pathname with the user’s home directory abbreviated.
Given an absolute pathname, this function abbreviates the user’s home
directory to ~/ in order to shorten the pathname without losing
information. It is not an error if the pathname is not relative to the
current user’s home directory.
If you want to parse date/time strings with a fixed, known
format and parse_date() isn’t useful to you, consider
time.strptime() or datetime.datetime.strptime(),
both of which are included in the Python standard library.
Alternatively for more complex tasks consider using the date/time
parsing module in the dateutil package.
Convert a human friendly pathname to an absolute pathname.
Expands leading tildes using os.path.expanduser() and
environment variables using os.path.expandvars() and makes the
resulting pathname absolute using os.path.abspath().
Note that the parse_timespan() function is not meant to be the
“mirror image” of the format_timespan() function. Instead it’s
meant to allow humans to easily and succinctly specify a timespan with a
minimal amount of typing. It’s very useful to accept easy to write time
spans as e.g. command line arguments to programs.
The time units (and abbreviations) supported by this function are:
Trims leading and trailing whitespace, replaces runs of whitespace
characters with a single space and interpolates any arguments using
format().
Parameters:
text – The text to compact (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The compacted text (a string).
Here’s an example of how I like to use the compact() function, this
is an example from a random unrelated project I’m working on at the moment:
raisePortDiscoveryError(compact(""" Failed to discover port(s) that Apache is listening on! Maybe I'm parsing the wrong configuration file? ({filename})""",filename=self.ports_config))
The combination of compact() and Python’s multi line strings allows
me to write long text fragments with interpolated variables that are easy
to write, easy to read and work well with Python’s whitespace
sensitivity.
Dedent a string (remove common leading whitespace from all lines).
Removes common leading whitespace from all lines in the string using
textwrap.dedent(), removes leading and trailing empty lines using
trim_empty_lines() and interpolates any arguments using
format().
Parameters:
text – The text to dedent (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The dedented text (a string).
The compact() function’s documentation contains an example of how I
like to use the compact() and dedent() functions. The main
difference is that I use compact() for text that will be presented
to the user (where whitespace is not so significant) and dedent()
for data file and code generation tasks (where newlines and indentation are
very significant).
Update a module with backwards compatible aliases.
Parameters:
module_name – The __name__ of the module (a string).
aliases – Each keyword argument defines an alias. The values
are expected to be “dotted paths” (strings).
The behavior of this function depends on whether the Sphinx documentation
generator is active, because the use of DeprecationProxy to shadow the
real module in sys.modules has the unintended side effect of
breaking autodoc support for :data: members (module variables).
To avoid breaking Sphinx the proxy object is omitted and instead the
aliased names are injected into the original module namespace, to make sure
that imports can be satisfied when the documentation is being rendered.
If you run into cyclic dependencies caused by define_aliases() when
running Sphinx, you can try moving the call to define_aliases() to
the bottom of the Python module you’re working on.
Format a string using the string formatting operator and/or str.format().
Parameters:
text – The text to format (a string).
args – Any positional arguments are interpolated into the text using
the string formatting operator (%). If no positional
arguments are given no interpolation is done.
kw – Any keyword arguments are interpolated into the text using the
str.format() function. If no keyword arguments are given
no interpolation is done.
Returns:
The text with any positional and/or keyword arguments
interpolated (a string).
The implementation of this function is so trivial that it seems silly to
even bother writing and documenting it. Justifying this requires some
context :-).
Why format() instead of the string formatting operator?
For really simple string interpolation Python’s string formatting operator
is ideal, but it does have some strange quirks:
When you switch from interpolating a single value to interpolating
multiple values you have to wrap them in tuple syntax. Because
format() takes a variable number of arguments it always
receives a tuple (which saves me a context switch :-). Here’s an
example:
>>> fromhumanfriendly.textimportformat>>> # The string formatting operator.>>> print('the magic number is %s'%42)the magic number is 42>>> print('the magic numbers are %s and %s'%(12,42))the magic numbers are 12 and 42>>> # The format() function.>>> print(format('the magic number is %s',42))the magic number is 42>>> print(format('the magic numbers are %s and %s',12,42))the magic numbers are 12 and 42
When you interpolate a single value and someone accidentally passes in a
tuple your code raises a TypeError. Because
format() takes a variable number of arguments it always
receives a tuple so this can never happen. Here’s an example:
>>> # How expecting to interpolate a single value can fail.>>> value=(12,42)>>> print('the magic value is %s'%value)Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting>>> # The following line works as intended, no surprises here!>>> print(format('the magic value is %s',value))the magic value is (12, 42)
Why format() instead of the str.format() method?
When you’re doing complex string interpolation the str.format()
function results in more readable code, however I frequently find myself
adding parentheses to force evaluation order. The format() function
avoids this because of the relative priority between the comma and dot
operators. Here’s an example:
>>> "{adjective} example"+" "+"(can't think of anything less {adjective})".format(adjective='silly')"{adjective} example (can't think of anything less silly)">>> ("{adjective} example"+" "+"(can't think of anything less {adjective})").format(adjective='silly')"silly example (can't think of anything less silly)">>> format("{adjective} example"+" "+"(can't think of anything less {adjective})",adjective='silly')"silly example (can't think of anything less silly)"
The compact() and dedent() functions are wrappers that
combine format() with whitespace manipulation to make it easy to
write nice to read Python code.
Render a table using characters like dashes and vertical bars to emulate borders.
Parameters:
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
horizontal_bar – The character used to represent a horizontal bar (a
string).
vertical_bar – The character used to represent a vertical bar (a
string).
If a column contains numeric data (integer and/or floating point
numbers) in all rows (ignoring column names of course) then the content
of that column is right-aligned, as can be seen in the example above. The
idea here is to make it easier to compare the numbers in different
columns to each other.
The column names are highlighted in color so they stand out a bit more
(see also HIGHLIGHT_COLOR). The following screen shot shows what
that looks like (my terminals are always set to white text on a black
background):
>>> prompt_for_choice([])Traceback (most recent call last):
File "humanfriendly/prompts.py", line 148, in prompt_for_choiceraiseValueError("Can't prompt for choice without any options!")ValueError: Can't prompt for choice without any options!
If a single option is given the user isn’t prompted:
>>> prompt_for_choice(['only one choice'])'only one choice'
Here’s what the actual prompt looks like by default:
>>> prompt_for_choice(['first option','second option']) 1. first option 2. second option Enter your choice as a number or unique substring (Control-C aborts): second'second option'
If you don’t like the whitespace (empty lines and indentation):
>>> prompt_for_choice(['first option','second option'],padding=False) 1. first option 2. second optionEnter your choice as a number or unique substring (Control-C aborts): first'first option'
This function is used to implement robust tokenization of user input in
functions like parse_size() and parse_timespan(). It
automatically coerces integer and floating point numbers, ignores
whitespace and knows how to separate numbers from strings even without
whitespace. Some examples to make this more concrete:
The CaseInsensitiveDict class is a dictionary whose string keys
are case insensitive. It works by automatically coercing string keys to
CaseInsensitiveKey objects. Keys that are not strings are
supported as well, just without case insensitivity.
At its core this module works by normalizing strings to lowercase before
comparing or hashing them. It doesn’t support proper case folding nor
does it support Unicode normalization, hence the word “simple”.
Simple case insensitive dictionary implementation (that remembers insertion order).
This class works by overriding methods that deal with dictionary keys to
coerce string keys to CaseInsensitiveKey objects before calling
down to the regular dictionary handling methods. While intended to be
complete this class has not been extensively tested yet.
Simple case insensitive dictionary key implementation.
The CaseInsensitiveKey class provides an intentionally simple
implementation of case insensitive strings to be used as dictionary keys.
If you need features like Unicode normalization or proper case folding
please consider using a more advanced implementation like the istr
package instead.
Human friendly input/output (text formatting) on the command
line based on the Python package with the same name.
Supported options:
Option
Description
-c, --run-command
Execute an external command (given as the positional arguments) and render
a spinner and timer while the command is running. The exit status of the
command is propagated.
--format-table
Read tabular data from standard input (each line is a row and each
whitespace separated field is a column), format the data as a table and
print the resulting table to standard output. See also the --delimiter
option.
-d, --delimiter=VALUE
Change the delimiter used by --format-table to VALUE (a string). By default
all whitespace is treated as a delimiter.
-l, --format-length=LENGTH
Convert a length count (given as the integer or float LENGTH) into a human
readable string and print that string to standard output.
-n, --format-number=VALUE
Format a number (given as the integer or floating point number VALUE) with
thousands separators and two decimal places (if needed) and print the
formatted number to standard output.
-s, --format-size=BYTES
Convert a byte count (given as the integer BYTES) into a human readable
string and print that string to standard output.
-b, --binary
Change the output of -s, --format-size to use binary multiples of bytes
(base-2) instead of the default decimal multiples of bytes (base-10).
-t, --format-timespan=SECONDS
Convert a number of seconds (given as the floating point number SECONDS)
into a human readable timespan and print that string to standard output.
--parse-length=VALUE
Parse a human readable length (given as the string VALUE) and print the
number of metres to standard output.
--parse-size=VALUE
Parse a human readable data size (given as the string VALUE) and print the
number of bytes to standard output.
--demo
Demonstrate changing the style and color of the terminal font using ANSI
escape sequences.
Wrap text in ANSI escape sequences for the given color and/or style(s).
Parameters:
text – The text to wrap (a string).
kw – Any keyword arguments are passed to ansi_style().
Returns:
The result of this function depends on the keyword arguments:
If ansi_style() generates an ANSI escape sequence based
on the keyword arguments, the given text is prefixed with the
generated ANSI escape sequence and suffixed with
ANSI_RESET.
If ansi_style() returns an empty string then the text
given by the caller is returned unchanged.
Try to enable support for ANSI escape sequences (required on Windows).
Returns:
True if ANSI is supported, False otherwise.
This functions checks for the following supported configurations, in the
given order:
On Windows, if have_windows_native_ansi_support() confirms
native support for ANSI escape sequences ctypes will be used to
enable this support.
On Windows, if the environment variable $ANSICON is set nothing is
done because it is assumed that support for ANSI escape sequences has
already been enabled via ansicon.
On Windows, an attempt is made to import and initialize the Python
package colorama instead (of course for this to work
colorama has to be installed).
On other platforms this function calls connected_to_terminal()
to determine whether ANSI escape sequences are supported (that is to
say all platforms that are not Windows are assumed to support ANSI
escape sequences natively, without weird contortions like above).
This makes it possible to call enable_ansi_support()
unconditionally without checking the current platform.
The cached() decorator is used to ensure
that this function is only executed once, but its return value remains
available on later calls.
Determine the number of lines and columns visible in the terminal.
Returns:
A tuple of two integers with the line and column count.
The result of this function is based on the first of the following three
methods that works:
First find_terminal_size_using_ioctl() is tried,
then find_terminal_size_using_stty() is tried,
finally DEFAULT_LINES and DEFAULT_COLUMNS are returned.
Note
The find_terminal_size() function performs the steps
above every time it is called, the result is not cached. This is
because the size of a virtual terminal can change at any time and
the result of find_terminal_size() should be correct.
Pre-emptive snarky comment: It’s possible to cache the result
of this function and use signal.SIGWINCH to
refresh the cached values!
Response: As a library I don’t consider it the role of the
humanfriendly.terminal module to install a process wide
signal handler …
Format a number as a string including thousands separators.
Parameters:
number – The number to format (a number like an int,
long or float).
num_decimals – The number of decimals to render (2 by default). If no
decimal places are required to represent the number
they will be omitted regardless of this argument.
Returns:
The formatted number (a string).
This function is intended to make it easier to recognize the order of size
of the number being formatted.
Render a table using characters like dashes and vertical bars to emulate borders.
Parameters:
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
horizontal_bar – The character used to represent a horizontal bar (a
string).
vertical_bar – The character used to represent a vertical bar (a
string).
If a column contains numeric data (integer and/or floating point
numbers) in all rows (ignoring column names of course) then the content
of that column is right-aligned, as can be seen in the example above. The
idea here is to make it easier to compare the numbers in different
columns to each other.
The column names are highlighted in color so they stand out a bit more
(see also HIGHLIGHT_COLOR). The following screen shot shows what
that looks like (my terminals are always set to white text on a black
background):
Render tabular data using the most appropriate representation.
Parameters:
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
Returns:
The rendered table (a string).
If you want an easy way to render tabular data on a terminal in a human
friendly format then this function is for you! It works as follows:
If the input data doesn’t contain any line breaks the function
format_pretty_table() is used to render a pretty table. If the
resulting table fits in the terminal without wrapping the rendered pretty
table is returned.
If the input data does contain line breaks or if a pretty table would
wrap (given the width of the terminal) then the function
format_robust_table() is used to render a more robust table that
can deal with data containing line breaks and long text.
For details about argument handling please refer to
format().
Renders the message using format() and writes
the resulting string (followed by a newline) to sys.stderr using
auto_encode().
If sys.stderr is connected to a terminal that supports colors,
ansi_wrap() is used to color the message in a red font (to make
the warning stand out from surrounding text).
function – The function whose return value should be cached.
Returns:
The decorated function.
The given function will only be called once, the first time the wrapper
function is called. The return value is cached by the wrapper function as
an attribute of the given function and returned on each subsequent call.
Note
Currently no function arguments are supported because only a
single return value can be cached. Accepting any function
arguments at all would imply that the cache is parametrized on
function arguments, which is not currently the case.
Support for deprecation warnings when importing names from old locations.
When software evolves, things tend to move around. This is usually detrimental
to backwards compatibility (in Python this primarily manifests itself as
ImportError exceptions).
While backwards compatibility is very important, it should not get in the way
of progress. It would be great to have the agility to move things around
without breaking backwards compatibility.
This is where the humanfriendly.deprecation module comes in: It enables
the definition of backwards compatible aliases that emit a deprecation warning
when they are accessed.
The way it works is that it wraps the original module in an DeprecationProxy
object that defines a __getattr__() special method to
override attribute access of the module.
Update a module with backwards compatible aliases.
Parameters:
module_name – The __name__ of the module (a string).
aliases – Each keyword argument defines an alias. The values
are expected to be “dotted paths” (strings).
The behavior of this function depends on whether the Sphinx documentation
generator is active, because the use of DeprecationProxy to shadow the
real module in sys.modules has the unintended side effect of
breaking autodoc support for :data: members (module variables).
To avoid breaking Sphinx the proxy object is omitted and instead the
aliased names are injected into the original module namespace, to make sure
that imports can be satisfied when the documentation is being rendered.
If you run into cyclic dependencies caused by define_aliases() when
running Sphinx, you can try moving the call to define_aliases() to
the bottom of the Python module you’re working on.
Deprecate positional arguments without dropping backwards compatibility.
Parameters:
names – The positional arguments to deprecated_args() give the names of
the positional arguments that the to-be-decorated function should warn
about being deprecated and translate to keyword arguments.
Returns:
A decorator function specialized to names.
The deprecated_args() decorator function was created to make it
easy to switch from positional arguments to keyword arguments [1] while
preserving backwards compatibility [2] and informing call sites
about the change.
When the decorated function is called with positional arguments
a deprecation warning is given:
>>> report_choice('this will give a deprecation warning')DeprecationWarning: report_choice has deprecated positional arguments, please switch to keyword argumentsthis will give a deprecation warning
But when the function is called with keyword arguments no deprecation
warning is emitted:
>>> report_choice(text='this will not give a deprecation warning')this will not give a deprecation warning
Format a string using the string formatting operator and/or str.format().
Parameters:
text – The text to format (a string).
args – Any positional arguments are interpolated into the text using
the string formatting operator (%). If no positional
arguments are given no interpolation is done.
kw – Any keyword arguments are interpolated into the text using the
str.format() function. If no keyword arguments are given
no interpolation is done.
Returns:
The text with any positional and/or keyword arguments
interpolated (a string).
The implementation of this function is so trivial that it seems silly to
even bother writing and documenting it. Justifying this requires some
context :-).
Why format() instead of the string formatting operator?
For really simple string interpolation Python’s string formatting operator
is ideal, but it does have some strange quirks:
When you switch from interpolating a single value to interpolating
multiple values you have to wrap them in tuple syntax. Because
format() takes a variable number of arguments it always
receives a tuple (which saves me a context switch :-). Here’s an
example:
>>> fromhumanfriendly.textimportformat>>> # The string formatting operator.>>> print('the magic number is %s'%42)the magic number is 42>>> print('the magic numbers are %s and %s'%(12,42))the magic numbers are 12 and 42>>> # The format() function.>>> print(format('the magic number is %s',42))the magic number is 42>>> print(format('the magic numbers are %s and %s',12,42))the magic numbers are 12 and 42
When you interpolate a single value and someone accidentally passes in a
tuple your code raises a TypeError. Because
format() takes a variable number of arguments it always
receives a tuple so this can never happen. Here’s an example:
>>> # How expecting to interpolate a single value can fail.>>> value=(12,42)>>> print('the magic value is %s'%value)Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting>>> # The following line works as intended, no surprises here!>>> print(format('the magic value is %s',value))the magic value is (12, 42)
Why format() instead of the str.format() method?
When you’re doing complex string interpolation the str.format()
function results in more readable code, however I frequently find myself
adding parentheses to force evaluation order. The format() function
avoids this because of the relative priority between the comma and dot
operators. Here’s an example:
>>> "{adjective} example"+" "+"(can't think of anything less {adjective})".format(adjective='silly')"{adjective} example (can't think of anything less silly)">>> ("{adjective} example"+" "+"(can't think of anything less {adjective})").format(adjective='silly')"silly example (can't think of anything less silly)">>> format("{adjective} example"+" "+"(can't think of anything less {adjective})",adjective='silly')"silly example (can't think of anything less silly)"
The compact() and dedent() functions are wrappers that
combine format() with whitespace manipulation to make it easy to
write nice to read Python code.
The prompts module enables interaction with the user
(operator) by asking for confirmation (prompt_for_confirmation()) and
asking to choose from a list of options (prompt_for_choice()). It works
by rendering interactive prompts on the terminal.
The prompts presented by python2:raw_input() (in Python 2) and
python3:input() (in Python 3) are not very user friendly by
default, for example the cursor keys (←, ↑, → and
↓) and the Home and End keys enter characters instead
of performing the action you would expect them to. By simply importing the
readline module these prompts become much friendlier (as mentioned
in the Python standard library documentation).
This function is called by the other functions in this module to enable
user friendly prompts.
Wrap a text to be rendered as an interactive prompt in ANSI escape sequences.
Parameters:
prompt_text – The text to render on the prompt (a string).
options – Any keyword arguments are passed on to ansi_wrap().
Returns:
The resulting prompt text (a string).
ANSI escape sequences are only used when the standard output stream is
connected to a terminal. When the standard input stream is connected to a
terminal any escape sequences are wrapped in “readline hints”.
>>> prompt_for_choice([])Traceback (most recent call last):
File "humanfriendly/prompts.py", line 148, in prompt_for_choiceraiseValueError("Can't prompt for choice without any options!")ValueError: Can't prompt for choice without any options!
If a single option is given the user isn’t prompted:
>>> prompt_for_choice(['only one choice'])'only one choice'
Here’s what the actual prompt looks like by default:
>>> prompt_for_choice(['first option','second option']) 1. first option 2. second option Enter your choice as a number or unique substring (Control-C aborts): second'second option'
If you don’t like the whitespace (empty lines and indentation):
>>> prompt_for_choice(['first option','second option'],padding=False) 1. first option 2. second optionEnter your choice as a number or unique substring (Control-C aborts): first'first option'
If the user enters ‘yes’ or ‘y’ then True is returned.
If the user enters ‘no’ or ‘n’ then False is returned.
If the user doesn’t enter any text or standard input is not
connected to a terminal (which makes it impossible to prompt
the user) the value of the keyword argument default is
returned (if that value is not None).
When default is False and the user doesn’t enter any text an
error message is printed and the prompt is repeated:
>>> prompt_for_confirmation("Are you sure?") Are you sure? [y/n] Error: Please enter 'yes' or 'no' (there's no default choice). Are you sure? [y/n]
The same thing happens when the user enters text that isn’t recognized:
>>> prompt_for_confirmation("Are you sure?") Are you sure? [y/n] about what? Error: Please enter 'yes' or 'no' (the text 'about what?' is not recognized). Are you sure? [y/n]
question – An explanation of what is expected from the user (a string).
default – The return value if the user doesn’t enter any text or
standard input is not connected to a terminal (which
makes it impossible to prompt the user).
padding – Render empty lines before and after the prompt to make it
stand out from the surrounding text? (a boolean, defaults
to True)
strip – Strip leading/trailing whitespace from the user’s reply?
Returns:
The text entered by the user (a string) or the value of the
default argument.
Raises:
KeyboardInterrupt when the program is
interrupted while the prompt is active, for example
because the user presses Control-C.
EOFError when reading from standard input
fails, for example because the user presses Control-D or
because the standard input stream is redirected (only if
default is None).
Wrap text in ANSI escape sequences for the given color and/or style(s).
Parameters:
text – The text to wrap (a string).
kw – Any keyword arguments are passed to ansi_style().
Returns:
The result of this function depends on the keyword arguments:
If ansi_style() generates an ANSI escape sequence based
on the keyword arguments, the given text is prefixed with the
generated ANSI escape sequence and suffixed with
ANSI_RESET.
If ansi_style() returns an empty string then the text
given by the caller is returned unchanged.
Format a string using the string formatting operator and/or str.format().
Parameters:
text – The text to format (a string).
args – Any positional arguments are interpolated into the text using
the string formatting operator (%). If no positional
arguments are given no interpolation is done.
kw – Any keyword arguments are interpolated into the text using the
str.format() function. If no keyword arguments are given
no interpolation is done.
Returns:
The text with any positional and/or keyword arguments
interpolated (a string).
The implementation of this function is so trivial that it seems silly to
even bother writing and documenting it. Justifying this requires some
context :-).
Why format() instead of the string formatting operator?
For really simple string interpolation Python’s string formatting operator
is ideal, but it does have some strange quirks:
When you switch from interpolating a single value to interpolating
multiple values you have to wrap them in tuple syntax. Because
format() takes a variable number of arguments it always
receives a tuple (which saves me a context switch :-). Here’s an
example:
>>> fromhumanfriendly.textimportformat>>> # The string formatting operator.>>> print('the magic number is %s'%42)the magic number is 42>>> print('the magic numbers are %s and %s'%(12,42))the magic numbers are 12 and 42>>> # The format() function.>>> print(format('the magic number is %s',42))the magic number is 42>>> print(format('the magic numbers are %s and %s',12,42))the magic numbers are 12 and 42
When you interpolate a single value and someone accidentally passes in a
tuple your code raises a TypeError. Because
format() takes a variable number of arguments it always
receives a tuple so this can never happen. Here’s an example:
>>> # How expecting to interpolate a single value can fail.>>> value=(12,42)>>> print('the magic value is %s'%value)Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting>>> # The following line works as intended, no surprises here!>>> print(format('the magic value is %s',value))the magic value is (12, 42)
Why format() instead of the str.format() method?
When you’re doing complex string interpolation the str.format()
function results in more readable code, however I frequently find myself
adding parentheses to force evaluation order. The format() function
avoids this because of the relative priority between the comma and dot
operators. Here’s an example:
>>> "{adjective} example"+" "+"(can't think of anything less {adjective})".format(adjective='silly')"{adjective} example (can't think of anything less silly)">>> ("{adjective} example"+" "+"(can't think of anything less {adjective})").format(adjective='silly')"silly example (can't think of anything less silly)">>> format("{adjective} example"+" "+"(can't think of anything less {adjective})",adjective='silly')"silly example (can't think of anything less silly)"
The compact() and dedent() functions are wrappers that
combine format() with whitespace manipulation to make it easy to
write nice to read Python code.
For details about argument handling please refer to
format().
Renders the message using format() and writes
the resulting string (followed by a newline) to sys.stderr using
auto_encode().
If sys.stderr is connected to a terminal that supports colors,
ansi_wrap() is used to color the message in a red font (to make
the warning stand out from surrounding text).
Customizations for and integration with the Sphinx documentation generator.
The humanfriendly.sphinx module uses the Sphinx extension API to
customize the process of generating Sphinx based Python documentation. To
explore the functionality this module offers its best to start reading
from the setup() function.
This function implements a callback for autodoc-process-docstring that
reformats module docstrings to append an overview of aliases defined by the
module.
The parameters expected by this function are those defined for Sphinx event
callback functions (i.e. I’m not going to document them here :-).
As the example shows you can use the role inline, embedded in sentences of
text. In the generated documentation the :man: text is omitted and a
hyperlink pointing to the Debian Linux manual pages is emitted.
As the example shows you can use the role inline, embedded in sentences of
text. In the generated documentation the :pypi: text is omitted and a
hyperlink pointing to the Python Package Index is emitted.
The setup() function makes it easy to enable all of the Sphinx
customizations provided by the humanfriendly.sphinx module with the
least amount of code. All you need to do is to add the module name to the
extensions variable in your conf.py file:
Of course more functionality may be added at a later stage. If you don’t
like that idea you may be better of calling the individual functions from
your own setup() function.
This function implements a callback for autodoc-skip-member events to
include documented “special methods” (method names with two leading and two
trailing underscores) in your documentation. The result is similar to the
use of the special-members flag with one big difference: Special
methods are included but other types of members are ignored. This means
that attributes like __weakref__ will always be ignored (this was my
main annoyance with the special-members flag).
The parameters expected by this function are those defined for Sphinx event
callback functions (i.e. I’m not going to document them here :-).
This function implements a callback for autodoc-process-docstring that
reformats module docstrings using render_usage() so that Sphinx
doesn’t mangle usage messages that were written to be human readable
instead of machine readable. Only module docstrings whose first line starts
with USAGE_MARKER are reformatted.
The parameters expected by this function are those defined for Sphinx event
callback functions (i.e. I’m not going to document them here :-).
Trims leading and trailing whitespace, replaces runs of whitespace
characters with a single space and interpolates any arguments using
format().
Parameters:
text – The text to compact (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The compacted text (a string).
Here’s an example of how I like to use the compact() function, this
is an example from a random unrelated project I’m working on at the moment:
raisePortDiscoveryError(compact(""" Failed to discover port(s) that Apache is listening on! Maybe I'm parsing the wrong configuration file? ({filename})""",filename=self.ports_config))
The combination of compact() and Python’s multi line strings allows
me to write long text fragments with interpolated variables that are easy
to write, easy to read and work well with Python’s whitespace
sensitivity.
Dedent a string (remove common leading whitespace from all lines).
Removes common leading whitespace from all lines in the string using
textwrap.dedent(), removes leading and trailing empty lines using
trim_empty_lines() and interpolates any arguments using
format().
Parameters:
text – The text to dedent (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The dedented text (a string).
The compact() function’s documentation contains an example of how I
like to use the compact() and dedent() functions. The main
difference is that I use compact() for text that will be presented
to the user (where whitespace is not so significant) and dedent()
for data file and code generation tasks (where newlines and indentation are
very significant).
Format a string using the string formatting operator and/or str.format().
Parameters:
text – The text to format (a string).
args – Any positional arguments are interpolated into the text using
the string formatting operator (%). If no positional
arguments are given no interpolation is done.
kw – Any keyword arguments are interpolated into the text using the
str.format() function. If no keyword arguments are given
no interpolation is done.
Returns:
The text with any positional and/or keyword arguments
interpolated (a string).
The implementation of this function is so trivial that it seems silly to
even bother writing and documenting it. Justifying this requires some
context :-).
Why format() instead of the string formatting operator?
For really simple string interpolation Python’s string formatting operator
is ideal, but it does have some strange quirks:
When you switch from interpolating a single value to interpolating
multiple values you have to wrap them in tuple syntax. Because
format() takes a variable number of arguments it always
receives a tuple (which saves me a context switch :-). Here’s an
example:
>>> fromhumanfriendly.textimportformat>>> # The string formatting operator.>>> print('the magic number is %s'%42)the magic number is 42>>> print('the magic numbers are %s and %s'%(12,42))the magic numbers are 12 and 42>>> # The format() function.>>> print(format('the magic number is %s',42))the magic number is 42>>> print(format('the magic numbers are %s and %s',12,42))the magic numbers are 12 and 42
When you interpolate a single value and someone accidentally passes in a
tuple your code raises a TypeError. Because
format() takes a variable number of arguments it always
receives a tuple so this can never happen. Here’s an example:
>>> # How expecting to interpolate a single value can fail.>>> value=(12,42)>>> print('the magic value is %s'%value)Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting>>> # The following line works as intended, no surprises here!>>> print(format('the magic value is %s',value))the magic value is (12, 42)
Why format() instead of the str.format() method?
When you’re doing complex string interpolation the str.format()
function results in more readable code, however I frequently find myself
adding parentheses to force evaluation order. The format() function
avoids this because of the relative priority between the comma and dot
operators. Here’s an example:
>>> "{adjective} example"+" "+"(can't think of anything less {adjective})".format(adjective='silly')"{adjective} example (can't think of anything less silly)">>> ("{adjective} example"+" "+"(can't think of anything less {adjective})").format(adjective='silly')"silly example (can't think of anything less silly)">>> format("{adjective} example"+" "+"(can't think of anything less {adjective})",adjective='silly')"silly example (can't think of anything less silly)"
The compact() and dedent() functions are wrappers that
combine format() with whitespace manipulation to make it easy to
write nice to read Python code.
Some generic notes about the table formatting functions in this module:
These functions were not written with performance in mind (at all) because
they’re intended to format tabular data to be presented on a terminal. If
someone were to run into a performance problem using these functions, they’d
be printing so much tabular data to the terminal that a human wouldn’t be
able to digest the tabular data anyway, so the point is moot :-).
These functions ignore ANSI escape sequences (at least the ones generated by
the terminal module) in the calculation of columns
widths. On reason for this is that column names are highlighted in color when
connected to a terminal. It also means that you can use ANSI escape sequences
to highlight certain column’s values if you feel like it (for example to
highlight deviations from the norm in an overview of calculated values).
Render a table using characters like dashes and vertical bars to emulate borders.
Parameters:
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
horizontal_bar – The character used to represent a horizontal bar (a
string).
vertical_bar – The character used to represent a vertical bar (a
string).
If a column contains numeric data (integer and/or floating point
numbers) in all rows (ignoring column names of course) then the content
of that column is right-aligned, as can be seen in the example above. The
idea here is to make it easier to compare the numbers in different
columns to each other.
The column names are highlighted in color so they stand out a bit more
(see also HIGHLIGHT_COLOR). The following screen shot shows what
that looks like (my terminals are always set to white text on a black
background):
Render tabular data with one column per line (allowing columns with line breaks).
Parameters:
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
Returns:
The rendered table (a string).
Here’s an example:
>>> fromhumanfriendly.tablesimportformat_robust_table>>> column_names=['Version','Uploaded on','Downloads']>>> humanfriendly_releases=[... ['1.23','2015-05-25','218'],... ['1.23.1','2015-05-26','1354'],... ['1.24','2015-05-26','223'],... ['1.25','2015-05-26','4319'],... ['1.25.1','2015-06-02','197'],... ]>>> print(format_robust_table(humanfriendly_releases,column_names))-----------------------Version: 1.23Uploaded on: 2015-05-25Downloads: 218-----------------------Version: 1.23.1Uploaded on: 2015-05-26Downloads: 1354-----------------------Version: 1.24Uploaded on: 2015-05-26Downloads: 223-----------------------Version: 1.25Uploaded on: 2015-05-26Downloads: 4319-----------------------Version: 1.25.1Uploaded on: 2015-06-02Downloads: 197-----------------------
The column names are highlighted in bold font and color so they stand out a
bit more (see HIGHLIGHT_COLOR).
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
Render tabular data using the most appropriate representation.
Parameters:
data – An iterable (e.g. a tuple() or list)
containing the rows of the table, where each row is an
iterable containing the columns of the table (strings).
column_names – An iterable of column names (strings).
Returns:
The rendered table (a string).
If you want an easy way to render tabular data on a terminal in a human
friendly format then this function is for you! It works as follows:
If the input data doesn’t contain any line breaks the function
format_pretty_table() is used to render a pretty table. If the
resulting table fits in the terminal without wrapping the rendered pretty
table is returned.
If the input data does contain line breaks or if a pretty table would
wrap (given the width of the terminal) then the function
format_robust_table() is used to render a more robust table that
can deal with data containing line breaks and long text.
Wrap text in ANSI escape sequences for the given color and/or style(s).
Parameters:
text – The text to wrap (a string).
kw – Any keyword arguments are passed to ansi_style().
Returns:
The result of this function depends on the keyword arguments:
If ansi_style() generates an ANSI escape sequence based
on the keyword arguments, the given text is prefixed with the
generated ANSI escape sequence and suffixed with
ANSI_RESET.
If ansi_style() returns an empty string then the text
given by the caller is returned unchanged.
Determine the number of lines and columns visible in the terminal.
Returns:
A tuple of two integers with the line and column count.
The result of this function is based on the first of the following three
methods that works:
First find_terminal_size_using_ioctl() is tried,
then find_terminal_size_using_stty() is tried,
finally DEFAULT_LINES and DEFAULT_COLUMNS are returned.
Note
The find_terminal_size() function performs the steps
above every time it is called, the result is not cached. This is
because the size of a virtual terminal can change at any time and
the result of find_terminal_size() should be correct.
Pre-emptive snarky comment: It’s possible to cache the result
of this function and use signal.SIGWINCH to
refresh the cached values!
Response: As a library I don’t consider it the role of the
humanfriendly.terminal module to install a process wide
signal handler …
The terminal module makes it easy to interact with
interactive text terminals and format text for rendering on such terminals. If
the terms used in the documentation of this module don’t make sense to you then
please refer to the Wikipedia article on ANSI escape sequences for details
about how ANSI escape sequences work.
This module was originally developed for use on UNIX systems, but since then
Windows 10 gained native support for ANSI escape sequences and this module was
enhanced to recognize and support this. For details please refer to the
enable_ansi_support() function.
Note
Deprecated names
The following aliases exist to preserve backwards compatibility, however a DeprecationWarning is triggered when they are accessed, because these aliases will be removed in a future release.
A dictionary with (name, number) pairs of text styles (effects). Used by
ansi_style() to generate ANSI escape sequences that change text
styles. Only widely supported text styles are included here.
A compiled regular expression used to separate significant characters from other text.
This pattern is used by clean_terminal_output() to split terminal
output into regular text versus backspace, carriage return and line feed
characters and ANSI ‘erase line’ escape sequences.
The color used to highlight important tokens in formatted text (e.g. the usage
message of the humanfriendly program). If the environment variable
$HUMANFRIENDLY_HIGHLIGHT_COLOR is set it determines the value of
HIGHLIGHT_COLOR.
Generate ANSI escape sequences for the given color and/or style(s).
Parameters:
color –
The foreground color. Three types of values are supported:
The name of a color (one of the strings ‘black’, ‘red’,
‘green’, ‘yellow’, ‘blue’, ‘magenta’, ‘cyan’ or ‘white’).
An integer that refers to the 256 color mode palette.
A tuple or list with three integers representing an RGB
(red, green, blue) value.
The value None (the default) means no escape
sequence to switch color will be emitted.
background – The background color (see the description
of the color argument).
bright – Use high intensity colors instead of default colors
(a boolean, defaults to False).
readline_hints – If True then readline_wrap() is
applied to the generated ANSI escape sequences (the
default is False).
kw – Any additional keyword arguments are expected to match a key
in the ANSI_TEXT_STYLES dictionary. If the argument’s
value evaluates to True the respective style will be
enabled.
Returns:
The ANSI escape sequences to enable the requested text styles or
an empty string if no styles were requested.
Raises:
ValueError when an invalid color name is given.
Even though only eight named colors are supported, the use of bright=True
and faint=True increases the number of available colors to around 24 (it
may be slightly lower, for example because faint black is just black).
Support for 8-bit colors
In release 4.7 support for 256 color mode was added. While this
significantly increases the available colors it’s not very human friendly
in usage because you need to look up color codes in the 256 color mode
palette.
You can use the humanfriendly--demo command to get a demonstration of
the available colors, see also the screen shot below. Note that the small
font size in the screen shot was so that the demonstration of 256 color
mode support would fit into a single screen shot without scrolling :-)
(I wasn’t feeling very creative).
Support for 24-bit colors
In release 4.14 support for 24-bit colors was added by accepting a tuple
or list with three integers representing the RGB (red, green, blue) value
of a color. This is not included in the demo because rendering millions of
colors was deemed unpractical ;-).
Wrap text in ANSI escape sequences for the given color and/or style(s).
Parameters:
text – The text to wrap (a string).
kw – Any keyword arguments are passed to ansi_style().
Returns:
The result of this function depends on the keyword arguments:
If ansi_style() generates an ANSI escape sequence based
on the keyword arguments, the given text is prefixed with the
generated ANSI escape sequence and suffixed with
ANSI_RESET.
If ansi_style() returns an empty string then the text
given by the caller is returned unchanged.
Renders the text using format() and writes it
to the given stream. If an UnicodeEncodeError is
encountered in doing so, the text is encoded using DEFAULT_ENCODING
and the write is retried. The reasoning behind this rather blunt approach
is that it’s preferable to get output on the command line in the wrong
encoding then to have the Python program blow up with a
UnicodeEncodeError exception.
text – The raw text with special characters (a Unicode string).
Returns:
A list of Unicode strings (one for each line).
This function emulates the effect of backspace (0x08), carriage return
(0x0D) and line feed (0x0A) characters and the ANSI ‘erase line’ escape
sequence on interactive terminals. It’s intended to clean up command output
that was originally meant to be rendered on an interactive terminal and
that has been captured using e.g. the script program [3] or the
pty module [4].
Some caveats about the use of this function:
Strictly speaking the effect of carriage returns cannot be emulated
outside of an actual terminal due to the interaction between overlapping
output, terminal widths and line wrapping. The goal of this function is
to sanitize noise in terminal output while preserving useful output.
Think of it as a useful and pragmatic but possibly lossy conversion.
The algorithm isn’t smart enough to properly handle a pair of ANSI escape
sequences that open before a carriage return and close after the last
carriage return in a linefeed delimited string; the resulting string will
contain only the closing end of the ANSI escape sequence pair. Tracking
this kind of complexity requires a state machine and proper parsing.
Try to enable support for ANSI escape sequences (required on Windows).
Returns:
True if ANSI is supported, False otherwise.
This functions checks for the following supported configurations, in the
given order:
On Windows, if have_windows_native_ansi_support() confirms
native support for ANSI escape sequences ctypes will be used to
enable this support.
On Windows, if the environment variable $ANSICON is set nothing is
done because it is assumed that support for ANSI escape sequences has
already been enabled via ansicon.
On Windows, an attempt is made to import and initialize the Python
package colorama instead (of course for this to work
colorama has to be installed).
On other platforms this function calls connected_to_terminal()
to determine whether ANSI escape sequences are supported (that is to
say all platforms that are not Windows are assumed to support ANSI
escape sequences natively, without weird contortions like above).
This makes it possible to call enable_ansi_support()
unconditionally without checking the current platform.
The cached() decorator is used to ensure
that this function is only executed once, but its return value remains
available on later calls.
The find_terminal_size() function performs the steps
above every time it is called, the result is not cached. This is
because the size of a virtual terminal can change at any time and
the result of find_terminal_size() should be correct.
Pre-emptive snarky comment: It’s possible to cache the result
of this function and use signal.SIGWINCH to
refresh the cached values!
Response: As a library I don’t consider it the role of the
humanfriendly.terminal module to install a process wide
signal handler …
Get the command to show a text on the terminal using a pager.
Parameters:
text – The text to print to the terminal (a string).
Returns:
A list of strings with the pager command and arguments.
The use of a pager helps to avoid the wall of text effect where the user
has to scroll up to see where the output began (not very user friendly).
If the given text contains ANSI escape sequences the command less--RAW-CONTROL-CHARS is used, otherwise the environment variable
$PAGER is used (if $PAGER isn’t set less is used).
When the selected pager is less, the following options are used to
make the experience more user friendly:
--quit-if-one-screen causes less to automatically exit if the
entire text can be displayed on the first screen. This makes the use of a
pager transparent for smaller texts (because the operator doesn’t have to
quit the pager).
--no-init prevents less from clearing the screen when it
exits. This ensures that the operator gets a chance to review the text
(for example a usage message) after quitting the pager, while composing
the next command.
Check if we’re running on a Windows 10 release with native support for ANSI escape sequences.
Returns:
True if so, False otherwise.
The cached() decorator is used as a minor
performance optimization. Semantically this should have zero impact because
the answer doesn’t change in the lifetime of a computer process.
formatted_text – The text to print to the terminal (a string).
encoding – The name of the text encoding used to encode the formatted
text if the formatted text is a Unicode string (a string,
defaults to DEFAULT_ENCODING).
When connected_to_terminal() returns True a pager is used
to show the text on the terminal, otherwise the text is printed directly
without invoking a pager.
The use of a pager helps to avoid the wall of text effect where the user
has to scroll up to see where the output began (not very user friendly).
Refer to get_pager_command() for details about the command line
that’s used to invoke the pager.
For details about argument handling please refer to
format().
Renders the message using format() and writes
the resulting string (followed by a newline) to sys.stderr using
auto_encode().
If sys.stderr is connected to a terminal that supports colors,
ansi_wrap() is used to color the message in a red font (to make
the warning stand out from surrounding text).
function – The function whose return value should be cached.
Returns:
The decorated function.
The given function will only be called once, the first time the wrapper
function is called. The return value is cached by the wrapper function as
an attribute of the given function and returned on each subsequent call.
Note
Currently no function arguments are supported because only a
single return value can be cached. Accepting any function
arguments at all would imply that the cache is parametrized on
function arguments, which is not currently the case.
Update a module with backwards compatible aliases.
Parameters:
module_name – The __name__ of the module (a string).
aliases – Each keyword argument defines an alias. The values
are expected to be “dotted paths” (strings).
The behavior of this function depends on whether the Sphinx documentation
generator is active, because the use of DeprecationProxy to shadow the
real module in sys.modules has the unintended side effect of
breaking autodoc support for :data: members (module variables).
To avoid breaking Sphinx the proxy object is omitted and instead the
aliased names are injected into the original module namespace, to make sure
that imports can be satisfied when the documentation is being rendered.
If you run into cyclic dependencies caused by define_aliases() when
running Sphinx, you can try moving the call to define_aliases() to
the bottom of the Python module you’re working on.
Find the meta variables in the given usage message.
Parameters:
usage_text – The usage message to parse (a string).
Returns:
A list of strings with any meta variables found in the usage
message.
When a command line option requires an argument, the convention is to
format such options as --option=ARG. The text ARG in this example
is the meta variable.
Format a string using the string formatting operator and/or str.format().
Parameters:
text – The text to format (a string).
args – Any positional arguments are interpolated into the text using
the string formatting operator (%). If no positional
arguments are given no interpolation is done.
kw – Any keyword arguments are interpolated into the text using the
str.format() function. If no keyword arguments are given
no interpolation is done.
Returns:
The text with any positional and/or keyword arguments
interpolated (a string).
The implementation of this function is so trivial that it seems silly to
even bother writing and documenting it. Justifying this requires some
context :-).
Why format() instead of the string formatting operator?
For really simple string interpolation Python’s string formatting operator
is ideal, but it does have some strange quirks:
When you switch from interpolating a single value to interpolating
multiple values you have to wrap them in tuple syntax. Because
format() takes a variable number of arguments it always
receives a tuple (which saves me a context switch :-). Here’s an
example:
>>> fromhumanfriendly.textimportformat>>> # The string formatting operator.>>> print('the magic number is %s'%42)the magic number is 42>>> print('the magic numbers are %s and %s'%(12,42))the magic numbers are 12 and 42>>> # The format() function.>>> print(format('the magic number is %s',42))the magic number is 42>>> print(format('the magic numbers are %s and %s',12,42))the magic numbers are 12 and 42
When you interpolate a single value and someone accidentally passes in a
tuple your code raises a TypeError. Because
format() takes a variable number of arguments it always
receives a tuple so this can never happen. Here’s an example:
>>> # How expecting to interpolate a single value can fail.>>> value=(12,42)>>> print('the magic value is %s'%value)Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting>>> # The following line works as intended, no surprises here!>>> print(format('the magic value is %s',value))the magic value is (12, 42)
Why format() instead of the str.format() method?
When you’re doing complex string interpolation the str.format()
function results in more readable code, however I frequently find myself
adding parentheses to force evaluation order. The format() function
avoids this because of the relative priority between the comma and dot
operators. Here’s an example:
>>> "{adjective} example"+" "+"(can't think of anything less {adjective})".format(adjective='silly')"{adjective} example (can't think of anything less silly)">>> ("{adjective} example"+" "+"(can't think of anything less {adjective})").format(adjective='silly')"silly example (can't think of anything less silly)">>> format("{adjective} example"+" "+"(can't think of anything less {adjective})",adjective='silly')"silly example (can't think of anything less silly)"
The compact() and dedent() functions are wrappers that
combine format() with whitespace manipulation to make it easy to
write nice to read Python code.
Convert HTML with simple text formatting to text with ANSI escape sequences.
Parameters:
data – The HTML to convert (a string).
callback – Optional callback to pass to HTMLConverter.
Returns:
Text with ANSI escape sequences (a string).
Please refer to the documentation of the HTMLConverter class for
details about the conversion process (like which tags are supported) and an
example with a screenshot.
fromhumanfriendly.textimportdedentfromhumanfriendly.terminalimporthtml_to_ansiprint(html_to_ansi(dedent(''' <b>Hello world!</b> <i>Is this thing on?</i> I guess I can <u>underline</u> or <s>strike-through</s> text? And what about <span style="color: red">color</span>?''')))rainbow_colors=['#FF0000','#E2571E','#FF7F00','#FFFF00','#00FF00','#96BF33','#0000FF','#4B0082','#8B00FF','#FFFFFF',]html_rainbow="".join('<span style="color: %s">o</span>'%cforcinrainbow_colors)print(html_to_ansi("Let's try a rainbow: %s"%html_rainbow))
Here’s what the results look like:
Some more details:
Nested tags are supported, within reasonable limits.
Text in <code> and <pre> tags will be highlighted in a
different color from the main text (currently this is yellow).
<ahref="URL">TEXT</a> is converted to the format “TEXT (URL)” where
the uppercase symbols are highlighted in light blue with an underline.
<div>, <p> and <pre> tags are considered block level tags
and are wrapped in vertical whitespace to prevent their content from
“running into” surrounding text. This may cause runs of multiple empty
lines to be emitted. As a workaround the __call__() method
will automatically call compact_empty_lines() on the generated
output before returning it to the caller. Of course this won’t work
when output is set to something like sys.stdout.
<br> is converted to a single plain text line break.
Implementation notes:
A list of dictionaries with style information is used as a stack where
new styling can be pushed and a pop will restore the previous styling.
When new styling is pushed, it is merged with (but overrides) the current
styling.
If you’re going to be converting a lot of HTML it might be useful from
a performance standpoint to re-use an existing HTMLConverter
object for unrelated HTML fragments, in this case take a look at the
__call__() method (it makes this use case very easy).
New in version 4.15: humanfriendly.terminal.HTMLConverter was added to the
humanfriendly package during the initial development of my new
chat-archive project, whose
command line interface makes for a great demonstration of the
flexibility that this feature provides (hint: check out how the search
keyword highlighting combines with the regular highlighting).
callback – Optional keyword argument to specify a function that
will be called to process text fragments before they
are emitted on the output stream. Note that link text
and preformatted text fragments are not processed by
this callback.
output – Optional keyword argument to redirect the output to the
given file-like object. If this is not given a new
StringIO object is created.
This method overrides the same method in the superclass to ensure that
an ANSI_RESET code is emitted when parsing reaches the end of
the input but a style is still active. This is intended to prevent
malformed HTML from messing up terminal output.
Convert HTML with simple text formatting to text with ANSI escape sequences.
Parameters:
data – The HTML to convert (a string).
callback – Optional callback to pass to HTMLConverter.
Returns:
Text with ANSI escape sequences (a string).
Please refer to the documentation of the HTMLConverter class for
details about the conversion process (like which tags are supported) and an
example with a screenshot.
Generate ANSI escape sequences for the given color and/or style(s).
Parameters:
color –
The foreground color. Three types of values are supported:
The name of a color (one of the strings ‘black’, ‘red’,
‘green’, ‘yellow’, ‘blue’, ‘magenta’, ‘cyan’ or ‘white’).
An integer that refers to the 256 color mode palette.
A tuple or list with three integers representing an RGB
(red, green, blue) value.
The value None (the default) means no escape
sequence to switch color will be emitted.
background – The background color (see the description
of the color argument).
bright – Use high intensity colors instead of default colors
(a boolean, defaults to False).
readline_hints – If True then readline_wrap() is
applied to the generated ANSI escape sequences (the
default is False).
kw – Any additional keyword arguments are expected to match a key
in the ANSI_TEXT_STYLES dictionary. If the argument’s
value evaluates to True the respective style will be
enabled.
Returns:
The ANSI escape sequences to enable the requested text styles or
an empty string if no styles were requested.
Raises:
ValueError when an invalid color name is given.
Even though only eight named colors are supported, the use of bright=True
and faint=True increases the number of available colors to around 24 (it
may be slightly lower, for example because faint black is just black).
Support for 8-bit colors
In release 4.7 support for 256 color mode was added. While this
significantly increases the available colors it’s not very human friendly
in usage because you need to look up color codes in the 256 color mode
palette.
You can use the humanfriendly--demo command to get a demonstration of
the available colors, see also the screen shot below. Note that the small
font size in the screen shot was so that the demonstration of 256 color
mode support would fit into a single screen shot without scrolling :-)
(I wasn’t feeling very creative).
Support for 24-bit colors
In release 4.14 support for 24-bit colors was added by accepting a tuple
or list with three integers representing the RGB (red, green, blue) value
of a color. This is not included in the demo because rendering millions of
colors was deemed unpractical ;-).
Support for spinners that represent progress on interactive terminals.
The Spinner class shows a “spinner” on the terminal to let the user
know that something is happening during long running operations that would
otherwise be silent (leaving the user to wonder what they’re waiting for).
Below are some visual examples that should illustrate the point.
Simple spinners:
Here’s a screen capture that shows the simplest form of spinner:
The following code was used to create the spinner above:
importitertoolsimporttimefromhumanfriendlyimportSpinnerwithSpinner(label="Downloading")asspinner:foriinitertools.count():# Do something useful here.time.sleep(0.1)# Advance the spinner.spinner.step()
Spinners that show elapsed time:
Here’s a spinner that shows the elapsed time since it started:
The following code was used to create the spinner above:
importitertoolsimporttimefromhumanfriendlyimportSpinner,TimerwithSpinner(label="Downloading",timer=Timer())asspinner:foriinitertools.count():# Do something useful here.time.sleep(0.1)# Advance the spinner.spinner.step()
Spinners that show progress:
Here’s a spinner that shows a progress percentage:
The following code was used to create the spinner above:
importitertoolsimportrandomimporttimefromhumanfriendlyimportSpinner,TimerwithSpinner(label="Downloading",total=100)asspinner:progress=0whileprogress<100:# Do something useful here.time.sleep(0.1)# Advance the spinner.spinner.step(progress)# Determine the new progress value.progress+=random.random()*5
If you want to provide user feedback during a long running operation but it’s
not practical to periodically call the step() method consider
using AutomaticSpinner instead.
As you may already have noticed in the examples above, Spinner objects
can be used as context managers to automatically call Spinner.clear()
when the spinner ends.
Show a spinner on the terminal that automatically starts animating.
This class shows a spinner on the terminal (just like Spinner
does) that automatically starts animating. This class should be used as a
context manager using the with statement. The animation
continues for as long as the context is active.
AutomaticSpinner provides an alternative to Spinner
for situations where it is not practical for the caller to periodically
call step() to advance the animation, e.g. because
you’re performing a blocking call and don’t fancy implementing threading or
subprocess handling just to provide some user feedback.
This works using the multiprocessing module by spawning a
subprocess to render the spinner while the main process is busy doing
something more useful. By using the with statement you’re
guaranteed that the subprocess is properly terminated at the appropriate
time.
label – The label for the spinner (a string or None, defaults to
None).
total – The expected number of steps (an integer or None). If this is
provided the spinner will show a progress percentage.
stream – The output stream to show the spinner on (a file-like object,
defaults to sys.stderr).
interactive – True to enable rendering of the spinner, False to
disable (defaults to the result of stream.isatty()).
timer – A Timer object (optional). If this is given the spinner
will show the elapsed time according to the timer.
interval – The spinner will be updated at most once every this many seconds
(a floating point number, defaults to MINIMUM_INTERVAL).
glyphs – A list of strings with single characters that are drawn in the same
place in succession to implement a simple animated effect (defaults
to GLYPHS).
progress – The number of the current step, relative to the total
given to the Spinner constructor (an integer,
optional). If not provided the spinner will not show
progress.
label – The label to use while redrawing (a string, optional). If
not provided the label given to the Spinner
constructor is used instead.
This method advances the spinner by one step without starting a new
line, causing an animated effect which is very simple but much nicer
than waiting for a prompt which is completely silent for a long time.
Note
This method uses time based rate limiting to avoid redrawing
the spinner too frequently. If you know you’re dealing with
code that will call step() at a high frequency,
consider using sleep() to avoid creating the
equivalent of a busy loop that’s rate limiting the spinner
99% of the time.
Sleep for a short period before redrawing the spinner.
This method is useful when you know you’re dealing with code that will
call step() at a high frequency. It will sleep for the interval
with which the spinner is redrawn (less than a second). This avoids
creating the equivalent of a busy loop that’s rate limiting the
spinner 99% of the time.
This method doesn’t redraw the spinner, you still have to call
step() in order to do that.
Deprecate positional arguments without dropping backwards compatibility.
Parameters:
names – The positional arguments to deprecated_args() give the names of
the positional arguments that the to-be-decorated function should warn
about being deprecated and translate to keyword arguments.
Returns:
A decorator function specialized to names.
The deprecated_args() decorator function was created to make it
easy to switch from positional arguments to keyword arguments [5] while
preserving backwards compatibility [6] and informing call sites
about the change.
When the decorated function is called with positional arguments
a deprecation warning is given:
>>> report_choice('this will give a deprecation warning')DeprecationWarning: report_choice has deprecated positional arguments, please switch to keyword argumentsthis will give a deprecation warning
But when the function is called with keyword arguments no deprecation
warning is emitted:
>>> report_choice(text='this will not give a deprecation warning')this will not give a deprecation warning
Utility classes and functions that make it easy to write unittest compatible test suites.
Over the years I’ve developed the habit of writing test suites for Python
projects using the unittest module. During those years I’ve come to know
pytest and in fact I use pytest to run my test suites (due to
its much better error reporting) but I’ve yet to publish a test suite that
requirespytest. I have several reasons for doing so:
It’s nice to keep my test suites as simple and accessible as possible and
not requiring a specific test runner is part of that attitude.
Whereas unittest is quite explicit, pytest contains a lot of
magic, which kind of contradicts the Python mantra “explicit is better than
implicit” (IMHO).
Helper for CaptureOutput to provide an easy to use API.
The two methods defined by this subclass were specifically chosen to match
the names of the methods provided by my capturer package which
serves a similar role as CaptureOutput but knows how to simulate
an interactive terminal (tty).
merged – True to merge the streams,
False to capture them separately.
input – The data that reads from sys.stdin
should return (a string).
enabled – True to enable capturing (the default),
False otherwise. This makes it easy to
unconditionally use CaptureOutput in
a with block while preserving the
choice to opt out of capturing output.
returncode – The return code that the program should emit (a
number, defaults to zero).
script – Shell script code to include in the mocked program (a
string or None). This can be used to mock a
program that is expected to generate specific output.
Before the test method starts a newline is emitted, to separate the
name of the test method (which will be printed to the terminal by
unittest or pytest) from the first line of logging
output that the test method is likely going to generate.
log_level – The log verbosity (a number, defaults
to logging.DEBUG).
When coloredlogs is installed coloredlogs.install() will be
used to configure logging to the terminal. When this fails with an
ImportError then logging.basicConfig() is used
as a fall back.
func – A callable. When the callable returns
False it will also be retried.
timeout – The number of seconds after which to abort (a number,
defaults to 60).
exc_type – The type of exceptions to retry (defaults
to AssertionError).
Returns:
The value returned by func.
Raises:
Once the timeout has expired retry() will raise the
previously retried assertion error. When func keeps returning
False until timeout expires CallableTimedOut
will be raised.
This function sleeps between retries to avoid claiming CPU cycles we don’t
need. It starts by sleeping for 0.1 second but adjusts this to one second
as the number of retries grows.
length – The length of the string to be generated (a number or a
tuple with two numbers). If this is a tuple then a random
number between the two numbers given in the tuple is used.
characters – The characters to be used (a string, defaults
to string.ascii_letters).
Returns:
A random string.
The random_string() function is very useful in test suites; by the
time I included it in humanfriendly.text I had already included
variants of this function in seven different test suites :-).
The format(), compact() and dedent() functions
provide a clean and simple to use syntax for composing large text fragments
with interpolated variables.
Trims leading and trailing whitespace, replaces runs of whitespace
characters with a single space and interpolates any arguments using
format().
Parameters:
text – The text to compact (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The compacted text (a string).
Here’s an example of how I like to use the compact() function, this
is an example from a random unrelated project I’m working on at the moment:
raisePortDiscoveryError(compact(""" Failed to discover port(s) that Apache is listening on! Maybe I'm parsing the wrong configuration file? ({filename})""",filename=self.ports_config))
The combination of compact() and Python’s multi line strings allows
me to write long text fragments with interpolated variables that are easy
to write, easy to read and work well with Python’s whitespace
sensitivity.
Dedent a string (remove common leading whitespace from all lines).
Removes common leading whitespace from all lines in the string using
textwrap.dedent(), removes leading and trailing empty lines using
trim_empty_lines() and interpolates any arguments using
format().
Parameters:
text – The text to dedent (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The dedented text (a string).
The compact() function’s documentation contains an example of how I
like to use the compact() and dedent() functions. The main
difference is that I use compact() for text that will be presented
to the user (where whitespace is not so significant) and dedent()
for data file and code generation tasks (where newlines and indentation are
very significant).
Format a string using the string formatting operator and/or str.format().
Parameters:
text – The text to format (a string).
args – Any positional arguments are interpolated into the text using
the string formatting operator (%). If no positional
arguments are given no interpolation is done.
kw – Any keyword arguments are interpolated into the text using the
str.format() function. If no keyword arguments are given
no interpolation is done.
Returns:
The text with any positional and/or keyword arguments
interpolated (a string).
The implementation of this function is so trivial that it seems silly to
even bother writing and documenting it. Justifying this requires some
context :-).
Why format() instead of the string formatting operator?
For really simple string interpolation Python’s string formatting operator
is ideal, but it does have some strange quirks:
When you switch from interpolating a single value to interpolating
multiple values you have to wrap them in tuple syntax. Because
format() takes a variable number of arguments it always
receives a tuple (which saves me a context switch :-). Here’s an
example:
>>> fromhumanfriendly.textimportformat>>> # The string formatting operator.>>> print('the magic number is %s'%42)the magic number is 42>>> print('the magic numbers are %s and %s'%(12,42))the magic numbers are 12 and 42>>> # The format() function.>>> print(format('the magic number is %s',42))the magic number is 42>>> print(format('the magic numbers are %s and %s',12,42))the magic numbers are 12 and 42
When you interpolate a single value and someone accidentally passes in a
tuple your code raises a TypeError. Because
format() takes a variable number of arguments it always
receives a tuple so this can never happen. Here’s an example:
>>> # How expecting to interpolate a single value can fail.>>> value=(12,42)>>> print('the magic value is %s'%value)Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting>>> # The following line works as intended, no surprises here!>>> print(format('the magic value is %s',value))the magic value is (12, 42)
Why format() instead of the str.format() method?
When you’re doing complex string interpolation the str.format()
function results in more readable code, however I frequently find myself
adding parentheses to force evaluation order. The format() function
avoids this because of the relative priority between the comma and dot
operators. Here’s an example:
>>> "{adjective} example"+" "+"(can't think of anything less {adjective})".format(adjective='silly')"{adjective} example (can't think of anything less silly)">>> ("{adjective} example"+" "+"(can't think of anything less {adjective})").format(adjective='silly')"silly example (can't think of anything less silly)">>> format("{adjective} example"+" "+"(can't think of anything less {adjective})",adjective='silly')"silly example (can't think of anything less silly)"
The compact() and dedent() functions are wrappers that
combine format() with whitespace manipulation to make it easy to
write nice to read Python code.
Remove “hard wrapping” from the paragraphs in a string.
Parameters:
text – The text to reformat (a string).
Returns:
The text without hard wrapping (a string).
This function works by removing line breaks when the last character before
a line break and the first character after the line break are both
non-whitespace characters. This means that common leading indentation will
break join_lines() (in that case you can use dedent()
before calling join_lines()).
Select the singular or plural form of a word based on a count.
Parameters:
count – The count (a number).
singular – The singular form of the word (a string).
plural – The plural form of the word (a string or None).
Returns:
The singular or plural form of the word (a string).
When the given count is exactly 1.0 the singular form of the word is
selected, in all other cases the plural form of the word is selected.
If the plural form of the word is not provided it is obtained by
concatenating the singular form of the word with the letter “s”. Of course
this will not always be correct, which is why you have the option to
specify both forms.
length – The length of the string to be generated (a number or a
tuple with two numbers). If this is a tuple then a random
number between the two numbers given in the tuple is used.
characters – The characters to be used (a string, defaults
to string.ascii_letters).
Returns:
A random string.
The random_string() function is very useful in test suites; by the
time I included it in humanfriendly.text I had already included
variants of this function in seven different test suites :-).
Here is an example that parses a nested data structure (a mapping of
logging level names to one or more styles per level) that’s encoded in a
string so it can be set as an environment variable:
This function is used to implement robust tokenization of user input in
functions like parse_size() and parse_timespan(). It
automatically coerces integer and floating point numbers, ignores
whitespace and knows how to separate numbers from strings even without
whitespace. Some examples to make this more concrete:
The usage module parses and reformats usage messages:
The format_usage() function takes a usage message and inserts ANSI
escape sequences that highlight items of special significance like command
line options, meta variables, etc. The resulting usage message is (intended
to be) easier to read on a terminal.
The render_usage() function takes a usage message and rewrites it to
reStructuredText suitable for inclusion in the documentation of a Python
package. This provides a DRY solution to keeping a single authoritative
definition of the usage message while making it easily available in
documentation. As a cherry on the cake it’s not just a pre-formatted dump of
the usage message but a nicely formatted reStructuredText fragment.
The remaining functions in this module support the two functions above.
Usage messages in general are free format of course, however the functions in
this module assume a certain structure from usage messages in order to
successfully parse and reformat them, refer to parse_usage() for
details.
Find the meta variables in the given usage message.
Parameters:
usage_text – The usage message to parse (a string).
Returns:
A list of strings with any meta variables found in the usage
message.
When a command line option requires an argument, the convention is to
format such options as --option=ARG. The text ARG in this example
is the meta variable.
The ‘package’ argument is required when performing a relative import. It
specifies the package to use as the anchor point from which to resolve the
relative import to an absolute import.
module_name – The name of the module whose __doc__ attribute is
the source of the usage message (a string).
This simple wrapper around render_usage() makes it very easy to
inject a reformatted usage message into your documentation using cog. To
use it you add a fragment like the following to your *.rst file:
The lines in the fragment above are single line reStructuredText comments
that are not copied to the output. Their purpose is to instruct cog where
to inject the reformatted usage message. Once you’ve added these lines to
your *.rst file, updating the rendered usage message becomes really
simple thanks to cog:
$cog.py-rREADME.rst
This will inject or replace the rendered usage message in your
README.rst file with an up to date copy.
Parse a usage message by inferring its structure (and making some assumptions :-).
Parameters:
text – The usage message to parse (a string).
Returns:
A tuple of two lists:
A list of strings with the paragraphs of the usage message’s
“introduction” (the paragraphs before the documentation of the
supported command line options).
A list of strings with pairs of command line options and their
descriptions: Item zero is a line listing a supported command
line option, item one is the description of that command line
option, item two is a line listing another supported command
line option, etc.
Usage messages in general are free format of course, however
parse_usage() assume a certain structure from usage messages in
order to successfully parse them:
The usage message starts with a line Usage:... that shows a symbolic
representation of the way the program is to be invoked.
After some free form text a line Supportedoptions: (surrounded by
empty lines) precedes the documentation of the supported command line
options.
The command line options are documented as follows:
-v,--verboseMakemorenoise.
So all of the variants of the command line option are shown together on a
separate line, followed by one or more paragraphs describing the option.
There are several other minor assumptions, but to be honest I’m not sure if
anyone other than me is ever going to use this functionality, so for now I
won’t list every intricate detail :-).
If you’re curious anyway, refer to the usage message of the humanfriendly
package (defined in the humanfriendly.cli module) and compare it with
the usage message you see when you run humanfriendly--help and the
generated usage message embedded in the readme.
Feel free to request more detailed documentation if you’re interested in
using the humanfriendly.usage module outside of the little ecosystem
of Python packages that I have been building over the past years.
Dedent a string (remove common leading whitespace from all lines).
Removes common leading whitespace from all lines in the string using
textwrap.dedent(), removes leading and trailing empty lines using
trim_empty_lines() and interpolates any arguments using
format().
Parameters:
text – The text to dedent (a string).
args – Any positional arguments are interpolated using format().
kw – Any keyword arguments are interpolated using format().
Returns:
The dedented text (a string).
The compact() function’s documentation contains an example of how I
like to use the compact() and dedent() functions. The main
difference is that I use compact() for text that will be presented
to the user (where whitespace is not so significant) and dedent()
for data file and code generation tasks (where newlines and indentation are
very significant).