chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
# Human friendly input/output in Python.
|
||||
#
|
||||
# Author: Peter Odding <peter@peterodding.com>
|
||||
# Last Change: February 16, 2020
|
||||
# URL: https://humanfriendly.readthedocs.io
|
||||
|
||||
"""
|
||||
Functions that render ASCII tables.
|
||||
|
||||
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 :mod:`~humanfriendly.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).
|
||||
"""
|
||||
|
||||
# Standard library modules.
|
||||
import collections
|
||||
import re
|
||||
|
||||
# Modules included in our package.
|
||||
from humanfriendly.compat import coerce_string
|
||||
from humanfriendly.terminal import (
|
||||
ansi_strip,
|
||||
ansi_width,
|
||||
ansi_wrap,
|
||||
terminal_supports_colors,
|
||||
find_terminal_size,
|
||||
HIGHLIGHT_COLOR,
|
||||
)
|
||||
|
||||
# Public identifiers that require documentation.
|
||||
__all__ = (
|
||||
'format_pretty_table',
|
||||
'format_robust_table',
|
||||
'format_rst_table',
|
||||
'format_smart_table',
|
||||
)
|
||||
|
||||
# Compiled regular expression pattern to recognize table columns containing
|
||||
# numeric data (integer and/or floating point numbers). Used to right-align the
|
||||
# contents of such columns.
|
||||
#
|
||||
# Pre-emptive snarky comment: This pattern doesn't match every possible
|
||||
# floating point number notation!?!1!1
|
||||
#
|
||||
# Response: I know, that's intentional. The use of this regular expression
|
||||
# pattern has a very high DWIM level and weird floating point notations do not
|
||||
# fall under the DWIM umbrella :-).
|
||||
NUMERIC_DATA_PATTERN = re.compile(r'^\d+(\.\d+)?$')
|
||||
|
||||
|
||||
def format_smart_table(data, column_names):
|
||||
"""
|
||||
Render tabular data using the most appropriate representation.
|
||||
|
||||
:param data: An iterable (e.g. a :func:`tuple` or :class:`list`)
|
||||
containing the rows of the table, where each row is an
|
||||
iterable containing the columns of the table (strings).
|
||||
:param 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
|
||||
:func:`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
|
||||
:func:`format_robust_table()` is used to render a more robust table that
|
||||
can deal with data containing line breaks and long text.
|
||||
"""
|
||||
# Normalize the input in case we fall back from a pretty table to a robust
|
||||
# table (in which case we'll definitely iterate the input more than once).
|
||||
data = [normalize_columns(r) for r in data]
|
||||
column_names = normalize_columns(column_names)
|
||||
# Make sure the input data doesn't contain any line breaks (because pretty
|
||||
# tables break horribly when a column's text contains a line break :-).
|
||||
if not any(any('\n' in c for c in r) for r in data):
|
||||
# Render a pretty table.
|
||||
pretty_table = format_pretty_table(data, column_names)
|
||||
# Check if the pretty table fits in the terminal.
|
||||
table_width = max(map(ansi_width, pretty_table.splitlines()))
|
||||
num_rows, num_columns = find_terminal_size()
|
||||
if table_width <= num_columns:
|
||||
# The pretty table fits in the terminal without wrapping!
|
||||
return pretty_table
|
||||
# Fall back to a robust table when a pretty table won't work.
|
||||
return format_robust_table(data, column_names)
|
||||
|
||||
|
||||
def format_pretty_table(data, column_names=None, horizontal_bar='-', vertical_bar='|'):
|
||||
"""
|
||||
Render a table using characters like dashes and vertical bars to emulate borders.
|
||||
|
||||
:param data: An iterable (e.g. a :func:`tuple` or :class:`list`)
|
||||
containing the rows of the table, where each row is an
|
||||
iterable containing the columns of the table (strings).
|
||||
:param column_names: An iterable of column names (strings).
|
||||
:param horizontal_bar: The character used to represent a horizontal bar (a
|
||||
string).
|
||||
:param vertical_bar: The character used to represent a vertical bar (a
|
||||
string).
|
||||
:returns: The rendered table (a string).
|
||||
|
||||
Here's an example:
|
||||
|
||||
>>> from humanfriendly.tables import format_pretty_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_pretty_table(humanfriendly_releases, column_names))
|
||||
-------------------------------------
|
||||
| Version | Uploaded on | Downloads |
|
||||
-------------------------------------
|
||||
| 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 |
|
||||
-------------------------------------
|
||||
|
||||
Notes about the resulting table:
|
||||
|
||||
- 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 :data:`.HIGHLIGHT_COLOR`). The following screen shot shows what
|
||||
that looks like (my terminals are always set to white text on a black
|
||||
background):
|
||||
|
||||
.. image:: images/pretty-table.png
|
||||
"""
|
||||
# Normalize the input because we'll have to iterate it more than once.
|
||||
data = [normalize_columns(r, expandtabs=True) for r in data]
|
||||
if column_names is not None:
|
||||
column_names = normalize_columns(column_names)
|
||||
if column_names:
|
||||
if terminal_supports_colors():
|
||||
column_names = [highlight_column_name(n) for n in column_names]
|
||||
data.insert(0, column_names)
|
||||
# Calculate the maximum width of each column.
|
||||
widths = collections.defaultdict(int)
|
||||
numeric_data = collections.defaultdict(list)
|
||||
for row_index, row in enumerate(data):
|
||||
for column_index, column in enumerate(row):
|
||||
widths[column_index] = max(widths[column_index], ansi_width(column))
|
||||
if not (column_names and row_index == 0):
|
||||
numeric_data[column_index].append(bool(NUMERIC_DATA_PATTERN.match(ansi_strip(column))))
|
||||
# Create a horizontal bar of dashes as a delimiter.
|
||||
line_delimiter = horizontal_bar * (sum(widths.values()) + len(widths) * 3 + 1)
|
||||
# Start the table with a vertical bar.
|
||||
lines = [line_delimiter]
|
||||
# Format the rows and columns.
|
||||
for row_index, row in enumerate(data):
|
||||
line = [vertical_bar]
|
||||
for column_index, column in enumerate(row):
|
||||
padding = ' ' * (widths[column_index] - ansi_width(column))
|
||||
if all(numeric_data[column_index]):
|
||||
line.append(' ' + padding + column + ' ')
|
||||
else:
|
||||
line.append(' ' + column + padding + ' ')
|
||||
line.append(vertical_bar)
|
||||
lines.append(u''.join(line))
|
||||
if column_names and row_index == 0:
|
||||
lines.append(line_delimiter)
|
||||
# End the table with a vertical bar.
|
||||
lines.append(line_delimiter)
|
||||
# Join the lines, returning a single string.
|
||||
return u'\n'.join(lines)
|
||||
|
||||
|
||||
def format_robust_table(data, column_names):
|
||||
"""
|
||||
Render tabular data with one column per line (allowing columns with line breaks).
|
||||
|
||||
:param data: An iterable (e.g. a :func:`tuple` or :class:`list`)
|
||||
containing the rows of the table, where each row is an
|
||||
iterable containing the columns of the table (strings).
|
||||
:param column_names: An iterable of column names (strings).
|
||||
:returns: The rendered table (a string).
|
||||
|
||||
Here's an example:
|
||||
|
||||
>>> from humanfriendly.tables import format_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.23
|
||||
Uploaded on: 2015-05-25
|
||||
Downloads: 218
|
||||
-----------------------
|
||||
Version: 1.23.1
|
||||
Uploaded on: 2015-05-26
|
||||
Downloads: 1354
|
||||
-----------------------
|
||||
Version: 1.24
|
||||
Uploaded on: 2015-05-26
|
||||
Downloads: 223
|
||||
-----------------------
|
||||
Version: 1.25
|
||||
Uploaded on: 2015-05-26
|
||||
Downloads: 4319
|
||||
-----------------------
|
||||
Version: 1.25.1
|
||||
Uploaded on: 2015-06-02
|
||||
Downloads: 197
|
||||
-----------------------
|
||||
|
||||
The column names are highlighted in bold font and color so they stand out a
|
||||
bit more (see :data:`.HIGHLIGHT_COLOR`).
|
||||
"""
|
||||
blocks = []
|
||||
column_names = ["%s:" % n for n in normalize_columns(column_names)]
|
||||
if terminal_supports_colors():
|
||||
column_names = [highlight_column_name(n) for n in column_names]
|
||||
# Convert each row into one or more `name: value' lines (one per column)
|
||||
# and group each `row of lines' into a block (i.e. rows become blocks).
|
||||
for row in data:
|
||||
lines = []
|
||||
for column_index, column_text in enumerate(normalize_columns(row)):
|
||||
stripped_column = column_text.strip()
|
||||
if '\n' not in stripped_column:
|
||||
# Columns without line breaks are formatted inline.
|
||||
lines.append("%s %s" % (column_names[column_index], stripped_column))
|
||||
else:
|
||||
# Columns with line breaks could very well contain indented
|
||||
# lines, so we'll put the column name on a separate line. This
|
||||
# way any indentation remains intact, and it's easier to
|
||||
# copy/paste the text.
|
||||
lines.append(column_names[column_index])
|
||||
lines.extend(column_text.rstrip().splitlines())
|
||||
blocks.append(lines)
|
||||
# Calculate the width of the row delimiter.
|
||||
num_rows, num_columns = find_terminal_size()
|
||||
longest_line = max(max(map(ansi_width, lines)) for lines in blocks)
|
||||
delimiter = u"\n%s\n" % ('-' * min(longest_line, num_columns))
|
||||
# Force a delimiter at the start and end of the table.
|
||||
blocks.insert(0, "")
|
||||
blocks.append("")
|
||||
# Embed the row delimiter between every two blocks.
|
||||
return delimiter.join(u"\n".join(b) for b in blocks).strip()
|
||||
|
||||
|
||||
def format_rst_table(data, column_names=None):
|
||||
"""
|
||||
Render a table in reStructuredText_ format.
|
||||
|
||||
:param data: An iterable (e.g. a :func:`tuple` or :class:`list`)
|
||||
containing the rows of the table, where each row is an
|
||||
iterable containing the columns of the table (strings).
|
||||
:param column_names: An iterable of column names (strings).
|
||||
:returns: The rendered table (a string).
|
||||
|
||||
Here's an example:
|
||||
|
||||
>>> from humanfriendly.tables import format_rst_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_rst_table(humanfriendly_releases, column_names))
|
||||
======= =========== =========
|
||||
Version Uploaded on Downloads
|
||||
======= =========== =========
|
||||
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
|
||||
======= =========== =========
|
||||
|
||||
.. _reStructuredText: https://en.wikipedia.org/wiki/ReStructuredText
|
||||
"""
|
||||
data = [normalize_columns(r) for r in data]
|
||||
if column_names:
|
||||
data.insert(0, normalize_columns(column_names))
|
||||
# Calculate the maximum width of each column.
|
||||
widths = collections.defaultdict(int)
|
||||
for row in data:
|
||||
for index, column in enumerate(row):
|
||||
widths[index] = max(widths[index], len(column))
|
||||
# Pad the columns using whitespace.
|
||||
for row in data:
|
||||
for index, column in enumerate(row):
|
||||
if index < (len(row) - 1):
|
||||
row[index] = column.ljust(widths[index])
|
||||
# Add table markers.
|
||||
delimiter = ['=' * w for i, w in sorted(widths.items())]
|
||||
if column_names:
|
||||
data.insert(1, delimiter)
|
||||
data.insert(0, delimiter)
|
||||
data.append(delimiter)
|
||||
# Join the lines and columns together.
|
||||
return '\n'.join(' '.join(r) for r in data)
|
||||
|
||||
|
||||
def normalize_columns(row, expandtabs=False):
|
||||
results = []
|
||||
for value in row:
|
||||
text = coerce_string(value)
|
||||
if expandtabs:
|
||||
text = text.expandtabs()
|
||||
results.append(text)
|
||||
return results
|
||||
|
||||
|
||||
def highlight_column_name(name):
|
||||
return ansi_wrap(name, bold=True, color=HIGHLIGHT_COLOR)
|
||||
Reference in New Issue
Block a user