littlejonny

Overview

Format output to a text table using unicode box characters.

Deprecated since version 1.6.0: Will be removed in 2.0.

In case you have been wondering, the name is a reference to https://xkcd.com/327/.

Classes

class pymisclib.littlejonny.Box(origin_column: int = 1, origin_row: int = 1, width: int = 40, height: int = 20, line_style: ~pymisclib.littlejonny.LineStyle = LineStyle.light, border_style: str = '\x1b[30m\x1b[47m', text_style: str = '\x1b[34m\x1b[103m\x1b[1m', _text: list[str] = <factory>)

A box consisting of a frame with content.

Constructor arguments:
param int origin_column:

Top left corner column.

param int origin_row:

Top left corner row.

param int width:

Width in columns.

param int height:

Height in rows.

param LineStyle line_style:

Border line style.

param str border_style:

Border color and font style expressed as ANSI format codes.

param str text_style:

Content text color and font style expressed as ANSI format codes.

flow_text(text: str, truncate: bool = True)

Flow the given text in to the current box.

Parameters:
  • text (str) – Text to flow into the box.

  • truncate (bool) – If False, raise an exception if text won’t fit into the box. If True, truncate the text.

classmethod make_from_points(top_left: Point, bottom_right: Point, line_style: LineStyle = LineStyle.light) Box

Create a new Box instance with the given top left and bottom right points.

Parameters:
  • top_left (Point) – starting point in the upper left corner.

  • bottom_right (Point) – ending point (inclusive) in the lower right corner.

  • line_style (LineStyle) – Style to use when rendering the box.

Returns:

An initialized instance.

Rtype Box:

render(output_format: OutputFormat) list[str]

Render the box in the given output_format.

Parameters:

output_format (OutputFormat) – Rendering format.

Returns:

A list of lines rendering the instance.

Rtype Text:

border_style: str = '\x1b[30m\x1b[47m'

Rendering style (color and font style) of the border.

height: int = 20

Height of the box in rows.

line_style: LineStyle = Namespace(down_and_horizontal='┬', down_and_left='┐', down_and_right='┌', horizontal='─', up_and_horizontal='┴', up_and_left='┘', up_and_right='└', vertical='│', vertical_and_horizontal='┼', vertical_and_left='┤', vertical_and_right='├')

Style of the border characters.

origin_column: int = 1

X-coordinate of the upper left corner of the box.

origin_row: int = 1

Y-coordinate of the upper left corner of the box.

property text: list[str]

Text contained in the box.

text_style: str = '\x1b[34m\x1b[103m\x1b[1m'

Text style (color and font) of the content.

width: int = 40

Width of the box in columns.

class pymisclib.littlejonny.CellStyle(heading_style: str = '\x1b[1m', plain_style: str = '')

Styling to use for the cell.

heading_style: str = '\x1b[1m'

Heading cell content style as a string of ANSI escape codes. Default is bold.

plain_style: str = ''

Plain cell content style as a string of ANSI escape codes. Default is plain.

class pymisclib.littlejonny.ColumnTable(_headings: list[str] = <factory>, _formats: list[str] = <factory>, _cells: list[list[~typing.Any]] = <factory>, _num_columns: int = 0, _num_rows: int = 0)

Table with data arranged by column.

Deprecated since version 2.0.0.

__init__(_headings: list[str] = <factory>, _formats: list[str] = <factory>, _cells: list[list[~typing.Any]] = <factory>, _num_columns: int = 0, _num_rows: int = 0) None
draw(style: LineStyle) list[str]

Render the table to a list of lines.

Parameters:

style (LineStyle) – The line style to use for the table.

Rtype Text:

Returns:

A list of rows which make up the rendered table.

set_table(headings: list[str], cell_formats: list[str], cells: list[list[str]])

Set the content of the table with cells specified by column.

Parameters:
  • headings (list[str]) – List of headings.

  • cell_formats (list[str]) – List of cell formats for each column. The formats use the same format-language as Python format strings.

  • cells (list[str]) – Matrix of cell contents, specified column[row].

Example

ct2 = ColumnTable()
ct2.set_table(
    headings=['First', 'Second', 'Third', 'Fourth'],
    cell_formats=['3d', '08x', 's', '>s'],
    cells=[[1, 2, 12, 123, 1000],
           [0x12345678, 0xffffee01, -1, 0, 23],
           ['abc def geh', '12345', 'minus one', 'zero', 'First'],
           ['this is a sample text', 'Short', 'negative hex number',
            'Zero hexadecimal number', 'The first column is too large.']]
)
print_lines(ct2.draw(LineStyle.light))

Output:

┌───────┬──────────┬─────────────┬────────────────────────────────┐
│ First │ Second   │ Third       │ Fourth                         │
├───────┼──────────┼─────────────┼────────────────────────────────┤
│   1   │ 12345678 │ abc def geh │ this is a sample text          │
│   2   │ ffffee01 │ 12345       │ Short                          │
│  12   │ -0000001 │ minus one   │ negative hex number            │
│ 123   │ 00000000 │ zero        │ Zero hexadecimal number        │
│ 1000  │ 00000017 │ First       │ The first column is too large. │
└───────┴──────────┴─────────────┴────────────────────────────────┘
set_table_transposed(headings: list[str], cell_formats: list[str], cells: list[list[str]])

Set the content of the table with cells specified by row.

Parameters:
  • headings (list[str]) – List of headings.

  • cell_formats (list[str]) – List of cell formats for each column.

  • cells (list[str]) – Matrix of cell contents, specified row[column].

Example

ct1 = ColumnTable()
ct1.set_table_transposed(
    headings=['First', 'Second', 'Third', 'Fourth'],
    cell_formats=['3d', '08x', 's', '>s'],
    cells=[[1, 0x12345678, 'abc def geh', 'this is a sample text'],
           [2, 0xffffee01, '12345', 'Short'],
           [12, -1, 'minus one', 'negative hex number'],
           [123, 0, 'zero', 'Zero hexadecimal number'],
           [1000, 23, 'First', 'The first column is too large.']]
)
print_lines(ct1.draw(LineStyle.light))

Output:

┌───────┬──────────┬─────────────┬────────────────────────────────┐
│ First │ Second   │ Third       │ Fourth                         │
├───────┼──────────┼─────────────┼────────────────────────────────┤
│   1   │ 12345678 │ abc def geh │ this is a sample text          │
│   2   │ ffffee01 │ 12345       │ Short                          │
│  12   │ -0000001 │ minus one   │ negative hex number            │
│ 123   │ 00000000 │ zero        │ Zero hexadecimal number        │
│ 1000  │ 00000017 │ First       │ The first column is too large. │
└───────┴──────────┴─────────────┴────────────────────────────────┘
property cell_formats: list[str]

Return cell formats by columns.

property headings: list[str]

Return headings by column.

property num_columns

Number of columns in the table.

property num_rows

Number of rows in the table.

class pymisclib.littlejonny.LineStyle(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

Rendering style for lines.

double = Namespace(down_and_horizontal='╦', down_and_left='╗', down_and_right='╔', horizontal='═', up_and_horizontal='╩', up_and_left='╝', up_and_right='╚', vertical='║', vertical_and_horizontal='╬', vertical_and_left='╣', vertical_and_right='╠')

Double line style ║.

heavy = Namespace(down_and_horizontal='┳', down_and_left='┓', down_and_right='┏', horizontal='━', up_and_horizontal='┻', up_and_left='┛', up_and_right='┗', vertical='┃', vertical_and_horizontal='╋', vertical_and_left='┫', vertical_and_right='┣')

Heavy single line style ┃.

light = Namespace(down_and_horizontal='┬', down_and_left='┐', down_and_right='┌', horizontal='─', up_and_horizontal='┴', up_and_left='┘', up_and_right='└', vertical='│', vertical_and_horizontal='┼', vertical_and_left='┤', vertical_and_right='├')

Light single line style │.

class pymisclib.littlejonny.OutputFormat(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

Format for rendering the output.

ANSI = 1

Render for an ANSI terminal.

TEXT = 2

Render as plain text.

class pymisclib.littlejonny.Point(column: int = 1, row: int = 1)

Coordinate in column (x) / row (y) space.

column: int = 1
row: int = 1
class pymisclib.littlejonny.Table(cells: list[list[~pymisclib.littlejonny.TableCell]] | None = None, logger: ~logging.Logger = <Logger pymisclib.littlejonny (WARNING)>, default_style: ~pymisclib.littlejonny.CellStyle = <factory>, column_widths: list[int] = <factory>)

Table made up of a rectangle of TableCell instances.

Constructor arguments:
param list[list[TableCell]] | None cells:

Two-dimensional list of TableCell instances making up the table. The cells are organized by row and column (.e.g. cells[row][column]).

param logging.Logger logger:

Logger instance for diagnostics.

param CellsStyle default_style:

All new cells start with this style.

cell_at(column: int, row: int) TableCell

Return the cell at the given location.

Parameters:
  • column (int) – Column number of the cell.

  • row (int) – Row number of the cell.

Returns:

Cell of the table at the given location.

Return type:

TableCell

Raises:

IndexError – Row or column was invalid.

column(column_idx: int) list[TableCell]

Return the specified column of the table.

Parameters:

column_idx (int) – Column index [0..table.width[.

Returns:

List of cells in the column ordered by row.

Rtype list[TableCell]:

Raises:

IndexError – Column was invalid

column_width(column_idx: int) int

Return the configured width of the specified column.

Parameters:

column_idx (int) – Column index [0..table.width[.

Returns:

Configured width of the column, or 0 to auto-size.

Return type:

int

Raises:

IndexError – Column was invalid

join_cells_horizontally(first_column: int, last_column: int, row: int)

Join the cells from the first to last column in the given row.

Parameters:
  • first_column (int) – Column of the first cell to join.

  • last_column (int) – Column of the last cell to join.

  • row (int) – Row number of the cells.

Raises:

IndexError – Row or column was invalid.

join_cells_vertically(column: int, first_row: int, last_row: int)

Join cells from the first to last row in the given column.

Parameters:
  • column (int) – Column of the cells to join.

  • first_row (int) – Row number of the first cell to join.

  • last_row (int) – Row number of the last cell to join.

Raises:

IndexError – Row or column was invalid.

populate(content: list[list[Any]], column_formats: list[str], column_alignment: list[str] | None = None)

Resize and populate a table with the given content.

render(output_format: OutputFormat = OutputFormat.TEXT, line_style: LineStyle = LineStyle.light) list[str]

Render the table in the given format to a list of strings.

Parameters:
Returns:

Rendered repsentation of the instance.

Return type:

Text

row(row_idx: int) list[TableCell]

Return the specified row of the table.

Parameters:

row_idx (int) – Row index [0..table.height[.

Returns:

List of cells in the row, ordered by column.

Rtype list[TableCell]:

Raises:

IndexError – Column was invalid

set_column_width(column_idx: int, width: int)

Set the width in characters of the given column..

Parameters:
  • column_idx (int) – Column index [0..table.width[.

  • width (int) – Width in characters or 0 to auto-size.

Raises:
  • IndexError – Column was invalid.

  • ValueError – Width was less than zero.

set_size(width: int, height: int)

Create an empty table with given width and height.

Parameters:
  • width (int) – Number of columns in the table.

  • height (int) – Number of rows in the table.

cells: list[list[TableCell]] | None = None

Cells, ordered by row and column, e.g. cells[row][column].

column_widths: list[int]
default_style: CellStyle

Style all cells using this style unless specified otherwise.

property height: int

Number of rows in the table.

logger: Logger = <Logger pymisclib.littlejonny (WARNING)>

Logger instance for diagnostics.

property width: int

Number of columns in the table.

class pymisclib.littlejonny.TableCell(content: Any = '', fmt: str = 's', is_heading: bool = False, alignment: Alignment = Alignment.Right, style: CellStyle = None, _left_cell: TableCell = None, _right_cell: TableCell = None, _top_cell: TableCell = None, _bottom_cell: TableCell = None, _position: Point = None)

A single cell of a table.

Constructor arguments:
param Any content:

Content of the cell. Usually a string or number.

param str fmt:

f-string format_spec to format the content.

param bool is_heading:

True if the cell is a heading cell.

param Alignment alignment:

Alignment of the formatted content in the cell.

param CellStyle style:

Style of the rendered content.

render(output_format: OutputFormat = OutputFormat.TEXT, width: int = 0, height: int = 1) list[str]

Render the box in the given output_format.

Parameters:
  • output_format (OutputFormat) – Rendering format.

  • width (int) – Width of the cell. If zero, the width is unconstrained and the content will not be aligned.

  • height (int) – Height of the cell in lines. If zero, the height is unconstrained and the text will flow as far as required.

Returns:

A list of lines representing the instance.

Return type:

Text

set(value: Any, fmt: str = '%s', justify: Alignment = Alignment.Right, is_heading: bool = False)

Set the value of the content and the format string for rendering.

Parameters:
  • value (Any) – Content of the cell.

  • fmt (str) – Format string for the cell value. Uses f-string syntax.

  • justify (Alignment) – Cell justification after formatting.

  • is_heading (bool) – True if cell gets heading formatting, False otherwise.

alignment: Alignment = '>'
content: Any = ''
fmt: str = 's'
is_heading: bool = False
style: CellStyle = None
pymisclib.littlejonny.Text

Type definition: a Text is a collection of lines, each of which is a string.

The lines are organized by row, so the first string in the list is the first text line.

Functions

pymisclib.littlejonny.draw_box(x, y, style) list[str]

Construct a box of size x * y with style.

pymisclib.littlejonny.print_lines(lines: list[str])

Print lines stored as a list of strings to stdout.

Boxes Example

from pymisclib.littlejonny import print_lines, Box, LineStyle, OutputFormat
from pymisclib.ansiterminal import BgColor, FgColor, TextStyle
b1 = Box(8, 3, 17, 9, LineStyle.light,
         FgColor.Green.value + BgColor.White.value,
         FgColor.Blue.value + BgColor.BrightYellow.value + TextStyle.Italic.value)
b1.text = [
    '(8;7)      (17;8)',
    '',
    '',
    '',
    '        *       ',
    '',
    '',
    '',
    '(8;16)    (17;16)',
]
print_lines(b1.render(OutputFormat.TEXT))

This will yield:

┌─────────────────┐
│(8;7)      (17;8)│
│                 │
│                 │
│                 │
│        *        │
│                 │
│                 │
│                 │
│(8;16)    (17;16)│
└─────────────────┘

If you use ANSI style

print_lines(b1.render(OutputFormat.ANSI))

The box will be placed in the terminal at the absolute coordinates over whatever the previous content was, and it will be rendered in color if your terminal supports color:

In [5]:     from pymisclib.littlejonny import print_lines, Box, LineStyle, OutputFormat
   ...:┌─────────────────┐.ansiterminal import BgColor, FgColor, TextStyle
   ...:│(8;7)      (17;8)│ 17, 9, LineStyle.light,
   ...:│                 │or.Green.value + BgColor.White.value,
   ...:│                 │or.Blue.value + BgColor.BrightYellow.value + TextStyle.Italic.value)
   ...:│                 │
   ...:│        *        │  (17;8)',
   ...:│                 │
   ...:│                 │
   ...:│                 │
   ...:│(8;16)    (17;16)│       ',
   ...:└─────────────────┘
   ...:         '',
In [6]:
b2 = Box(1, 1, 64, 13, LineStyle.double,
         FgColor.Red.value + BgColor.Green.value,
         FgColor.Black.value + BgColor.BrightWhite.value + TextStyle.Bold.value)
b2.text = [
    '',
    ' 2nd line: This is a line of text and it is much too long for this box.',
    '         1         2         3         4         5         6    ',
    '1234567890123456789012345678901234567890123456789012345678901234',
    '',
    '                                                           right',
    'left',
    '                               center',
]
print_lines(b2.render(OutputFormat.TEXT))

Yields:

╔════════════════════════════════════════════════════════════════╗
║                                                                ║
║ 2nd line: This is a line of text and it is much too long for th║
║         1         2         3         4         5         6    ║
║1234567890123456789012345678901234567890123456789012345678901234║
║                                                                ║
║                                                           right║
║left                                                            ║
║                               center                           ║
║                                                                ║
║                                                                ║
║                                                                ║
║                                                                ║
║                                                                ║
╚════════════════════════════════════════════════════════════════╝
from pymisclib.littlejonny import print_lines, Box, LineStyle, ColumnTable, OutputFormat
from pymisclib.ansiterminal import AnsiColor, BgColor, FgColor, TextStyle
b1 = Box(1, 1, 5, 3, LineStyle.light,FgColor.Green.value + BgColor.White.value,
         FgColor.Blue.value + BgColor.BrightYellow.value, TextStyle.Italic.value)
b2 = Box(1, 6, 1, 1, LineStyle.double,
         FgColor.Red.value, BgColor.Blue.value, TextStyle.Bold.value)
b2.text = '*'
b3 = Box(5, 9, 40, 3, LineStyle.heavy, FgColor.White.value + AnsiColor(128, 128, 128).bg, FgColor.Green.value + BgColor.BrightYellow.value, TextStyle.Underline.value)
b3.text = 'The quick brown fox jumps over the lazy dog.'
for b in [b1, b2, b3]:
    print_lines(b.render(OutputFormat.TEXT))
for b in [b1, b2, b3]:
    print_lines(b.render(OutputFormat.ANSI))

ct1 = ColumnTable()
ct1.set_table_transposed(
    headings=['First', 'Second', 'Third', 'Fourth'],
    cell_formats=['3d', '08x', 's', '>s'],
    cells=[[1, 0x12345678, 'abc def geh', 'this is a sample text'],
           [2, 0xffffee01, '12345', 'Short'],
           [12, -1, 'minus one', 'negative hex number'],
           [123, 0, 'zero', 'Zero hexadecimal number'],
           [1000, 23, 'First', 'The first column is too large.']]
)
print_lines(ct1.draw(LineStyle.light))

This will yield:

┌─────┐
│     │
│     │
│     │
└─────┘
╔═╗
║ ║
╚═╝
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                        ┃
┃                                        ┃
┃                                        ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┌───────┬──────────┬─────────────┬────────────────────────────────┐
│ First │ Second   │ Third       │ Fourth                         │
├───────┼──────────┼─────────────┼────────────────────────────────┤
│   1   │ 12345678 │ abc def geh │ this is a sample text          │
│   2   │ ffffee01 │ 12345       │ Short                          │
│  12   │ -0000001 │ minus one   │ negative hex number            │
│ 123   │ 00000000 │ zero        │ Zero hexadecimal number        │
│ 1000  │ 00000017 │ First       │ The first column is too large. │
└───────┴──────────┴─────────────┴────────────────────────────────┘

Tables

A Table is made up of instances of TableCell. Each TableCell instance can hold a value, as well a alignment, formatting, and style information.

When rendering the table, each cell is rendered using the format, width, alignment, and style specified, in this order.

Each cell in a column of the table has the same width. Unless specified otherwise, the width of each column is the width of the widest cell in the column.

If the content of a cell will not fit into the column width, it will be truncated.

Rendering can be done in several formats:

ANSI

Use ANSI formatting codes to style the output.

TEXT

Plain Unicode text rendering.