diff --git a/README.md b/README.md index 611d56c..b26821c 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,16 @@ Module for converting 2D Python lists to a fancy ASCII/Unicode tables - [table2ascii](#table2ascii) - [๐Ÿ“ฅ Installation](#-installation) - [๐Ÿง‘โ€๐Ÿ’ป Usage](#-usage) + - [Convert lists to ASCII tables](#convert-lists-to-ascii-tables) + - [Set first or last column headings](#set-first-or-last-column-headings) + - [Set column widths and alignments](#set-column-widths-and-alignments) + - [Use a preset style](#use-a-preset-style) + - [Define a custom style](#define-a-custom-style) + - [๐ŸŽจ Preset styles](#-preset-styles) - [โš™๏ธ Options](#๏ธ-options) - [๐Ÿ‘จโ€๐ŸŽจ Use cases](#-use-cases) + - [Discord messages and embeds](#discord-messages-and-embeds) + - [Terminal outputs](#terminal-outputs) - [๐Ÿงฐ Development](#-development) @@ -22,7 +30,7 @@ Module for converting 2D Python lists to a fancy ASCII/Unicode tables ## ๐Ÿง‘โ€๐Ÿ’ป Usage -Convert Python lists to ASCII tables +### Convert lists to ASCII tables ```py from table2ascii import table2ascii @@ -47,6 +55,8 @@ print(output) """ ``` +### Set first or last column headings + ```py from table2ascii import table2ascii @@ -65,6 +75,8 @@ print(output) """ ``` +### Set column widths and alignments + ```py from table2ascii import table2ascii, Alignment @@ -88,21 +100,78 @@ print(output) """ ``` +### Use a preset style + +```py +from table2ascii import table2ascii, PresetStyle + +output = table2ascii( + header=["First", "Second", "Third", "Fourth"], + body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], + column_widths=[10] * 4, + style=PresetStyle.ascii_box +) + +print(output) + +""" ++----------+----------+----------+----------+ +| First | Second | Third | Fourth | ++----------+----------+----------+----------+ +| 10 | 30 | 40 | 35 | ++----------+----------+----------+----------+ +| 20 | 10 | 20 | 5 | ++----------+----------+----------+----------+ +""" +``` + +### Define a custom style + +Check [`TableStyle`](https://github.com/DenverCoder1/table2ascii/blob/main/table2ascii/table_style.py) for more info and [`PresetStyle`](https://github.com/DenverCoder1/table2ascii/blob/main/table2ascii/preset_style.py) for examples. + +```py +from table2ascii import table2ascii, TableStyle + +my_style = TableStyle.from_string("*-..*||:+-+:+ *''*") + +output = table2ascii( + header=["First", "Second", "Third"], + body=[["10", "30", "40"], ["20", "10", "20"], ["30", "20", "30"]], + style=my_style +) + +print(output) + +""" +*-------.--------.-------* +| First : Second : Third | ++-------:--------:-------+ +| 10 : 30 : 40 | +| 20 : 10 : 20 | +| 30 : 20 : 30 | +*-------'--------'-------* +""" +``` + +## ๐ŸŽจ Preset styles + +See a list of all preset styles [here](/style_list). + ## โš™๏ธ Options All parameters are optional. Soon table2ascii will support more options for customization. -| Option | Type | Default | Description | -| :-----------------: | :-------: | :----------: | :------------------------------------------------------------------------------------: | -| `header` | `List` | `None` | First row of table seperated by header row seperator | -| `body` | `2D List` | `None` | List of rows for the main section of the table | -| `footer` | `List` | `None` | Last row of table seperated by header row seperator | -| `column_widths` | `List` | automatic | List of column widths in characters for each column | -| `alignments` | `List` | all centered | Alignments for each column (ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) | -| `first_col_heading` | `bool` | `False` | Whether to add a heading column seperator after the first column | -| `last_col_heading` | `bool` | `False` | Whether to add a heading column seperator before the last column | +| Option | Type | Default | Description | +| :-----------------: | :---------------: | :----------: | :----------------------------------------------------------------------------------------: | +| `header` | `List[str]` | `None` | First row of table seperated by header row seperator | +| `body` | `List[List[str]]` | `None` | List of rows for the main section of the table | +| `footer` | `List[str]` | `None` | Last row of table seperated by header row seperator | +| `column_widths` | `List[int]` | automatic | List of column widths in characters for each column | +| `alignments` | `List[int]` | all centered | Alignments for each column
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) | +| `first_col_heading` | `bool` | `False` | Whether to add a heading column seperator after the first column | +| `last_col_heading` | `bool` | `False` | Whether to add a heading column seperator before the last column | ## ๐Ÿ‘จโ€๐ŸŽจ Use cases diff --git a/setup.py b/setup.py index fec0b7c..9f146be 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ def run(self): setup( name="table2ascii", - version="0.0.3", + version="0.1.1", author="Jonah Lawrence", author_email="jonah@freshidea.com", description="Convert 2D Python lists into Unicode/Ascii tables", diff --git a/style_list/README.md b/style_list/README.md new file mode 100644 index 0000000..b92755d --- /dev/null +++ b/style_list/README.md @@ -0,0 +1,510 @@ +## Preset styles + +- [Preset styles](#preset-styles) + - [`ascii`](#ascii) + - [`ascii_borderless`](#ascii_borderless) + - [`ascii_box`](#ascii_box) + - [`ascii_compact`](#ascii_compact) + - [`ascii_double`](#ascii_double) + - [`ascii_minimalist`](#ascii_minimalist) + - [`ascii_simple`](#ascii_simple) + - [`borderless`](#borderless) + - [`double`](#double) + - [`double_box`](#double_box) + - [`double_compact`](#double_compact) + - [`double_thin_compact`](#double_thin_compact) + - [`markdown`](#markdown) + - [`minimalist`](#minimalist) + - [`simple`](#simple) + - [`thick`](#thick) + - [`thick_box`](#thick_box) + - [`thick_compact`](#thick_compact) + - [`thin`](#thin) + - [`thin_box`](#thin_box) + - [`thin_compact`](#thin_compact) + - [`thin_compact_rounded`](#thin_compact_rounded) + - [`thin_double`](#thin_double) + - [`thin_double_rounded`](#thin_double_rounded) + - [`thin_rounded`](#thin_rounded) + - [`thin_thick`](#thin_thick) + - [`thin_thick_rounded`](#thin_thick_rounded) + +### `ascii` + +``` ++-----+-----------------------+ +| # | G H R S | ++-----+-----------------------+ +| 1 | 30 40 35 30 | ++-----+-----------------------+ +| 2 | 30 40 35 30 | ++-----+-----------------------+ +| SUM | 130 140 135 130 | ++-----+-----------------------+ + ++---+-------------------+ +| 1 | 30 40 35 30 | ++---+-------------------+ +| 2 | 30 40 35 30 | ++---+-------------------+ +``` +### `ascii_borderless` + +``` + # | G H R S + ----- ----- ----- ----- ----- + 1 | 30 40 35 30 + 2 | 30 40 35 30 + ----- ----- ----- ----- ----- + SUM | 130 140 135 130 + + 1 | 30 40 35 30 + 2 | 30 40 35 30 +``` +### `ascii_box` + +``` ++-----+-----+-----+-----+-----+ +| # | G | H | R | S | ++-----+-----+-----+-----+-----+ +| 1 | 30 | 40 | 35 | 30 | ++-----+-----+-----+-----+-----+ +| 2 | 30 | 40 | 35 | 30 | ++-----+-----+-----+-----+-----+ +| SUM | 130 | 140 | 135 | 130 | ++-----+-----+-----+-----+-----+ + ++---+----+----+----+----+ +| 1 | 30 | 40 | 35 | 30 | ++---+----+----+----+----+ +| 2 | 30 | 40 | 35 | 30 | ++---+----+----+----+----+ +``` +### `ascii_compact` + +``` ++-----+-----------------------+ +| # | G H R S | ++-----+-----------------------+ +| 1 | 30 40 35 30 | +| 2 | 30 40 35 30 | ++-----+-----------------------+ +| SUM | 130 140 135 130 | ++-----+-----------------------+ + ++---+-------------------+ +| 1 | 30 40 35 30 | +| 2 | 30 40 35 30 | ++---+-------------------+ +``` +### `ascii_double` + +``` ++-----+-----------------------+ +| # | G H R S | ++=====+=======================+ +| 1 | 30 40 35 30 | ++-----+-----------------------+ +| 2 | 30 40 35 30 | ++=====+=======================+ +| SUM | 130 140 135 130 | ++-----+-----------------------+ + ++---+-------------------+ +| 1 | 30 40 35 30 | ++---+-------------------+ +| 2 | 30 40 35 30 | ++---+-------------------+ +``` +### `ascii_minimalist` + +``` + ----------------------------- + # | G H R S + ============================= + 1 | 30 40 35 30 + ----------------------------- + 2 | 30 40 35 30 + ============================= + SUM | 130 140 135 130 + ----------------------------- + + ----------------------- + 1 | 30 40 35 30 + ----------------------- + 2 | 30 40 35 30 + ----------------------- +``` +### `ascii_simple` + +``` + ===== ===== ===== ===== ===== + # | G H R S + ===== ===== ===== ===== ===== + 1 | 30 40 35 30 + 2 | 30 40 35 30 + ===== ===== ===== ===== ===== + SUM | 130 140 135 130 + ===== ===== ===== ===== ===== + + === ==== ==== ==== ==== + 1 | 30 40 35 30 + 2 | 30 40 35 30 + === ==== ==== ==== ==== +``` +### `borderless` + +``` + # โ”ƒ G H R S + โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” + 1 โ”ƒ 30 40 35 30 + 2 โ”ƒ 30 40 35 30 + โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” + SUM โ”ƒ 130 140 135 130 + + 1 โ”ƒ 30 40 35 30 + 2 โ”ƒ 30 40 35 30 +``` +### `double` + +``` +โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ # โ•‘ G H R S โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ SUM โ•‘ 130 140 135 130 โ•‘ +โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ +โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ +โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +``` +### `double_box` + +``` +โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•— +โ•‘ # โ•‘ G โ•‘ H โ•‘ R โ•‘ S โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ +โ•‘ 1 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ +โ•‘ 2 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ +โ•‘ SUM โ•‘ 130 โ•‘ 140 โ•‘ 135 โ•‘ 130 โ•‘ +โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ• + +โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•— +โ•‘ 1 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘ +โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•ฃ +โ•‘ 2 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘ +โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ• +``` +### `double_compact` + +``` +โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ # โ•‘ G H R S โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ +โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ SUM โ•‘ 130 140 135 130 โ•‘ +โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ +โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ +โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +``` +### `double_thin_compact` + +``` +โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ # โ•‘ G H R S โ•‘ +โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข +โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ +โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ +โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข +โ•‘ SUM โ•‘ 130 140 135 130 โ•‘ +โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ +โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ +โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +``` +### `markdown` + +``` +| # | G | H | R | S | +| ----- | ----- | ----- | ----- | ----- | +| 1 | 30 | 40 | 35 | 30 | +| 2 | 30 | 40 | 35 | 30 | +| ----- | ----- | ----- | ----- | ----- | +| SUM | 130 | 140 | 135 | 130 | + +| 1 | 30 | 40 | 35 | 30 | +| 2 | 30 | 40 | 35 | 30 | +``` +### `minimalist` + +``` + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # โ”‚ G H R S + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + 1 โ”‚ 30 40 35 30 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 2 โ”‚ 30 40 35 30 + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + SUM โ”‚ 130 140 135 130 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 1 โ”‚ 30 40 35 30 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 2 โ”‚ 30 40 35 30 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +``` +### `simple` + +``` + โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• + # โ•‘ G H R S + โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• + 1 โ•‘ 30 40 35 30 + 2 โ•‘ 30 40 35 30 + โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• + SUM โ•‘ 130 140 135 130 + โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• + + โ•โ•โ• โ•โ•โ•โ• โ•โ•โ•โ• โ•โ•โ•โ• โ•โ•โ•โ• + 1 โ•‘ 30 40 35 30 + 2 โ•‘ 30 40 35 30 + โ•โ•โ• โ•โ•โ•โ• โ•โ•โ•โ• โ•โ•โ•โ• โ•โ•โ•โ• +``` +### `thick` + +``` +โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ +โ”ƒ # โ”ƒ G H R S โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ +โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ +โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ +โ”ƒ SUM โ”ƒ 130 140 135 130 โ”ƒ +โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› + +โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ +โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ +โ”ฃโ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ +โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ +โ”—โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› +``` +### `thick_box` + +``` +โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”“ +โ”ƒ # โ”ƒ G โ”ƒ H โ”ƒ R โ”ƒ S โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ +โ”ƒ 1 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ +โ”ƒ 2 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ +โ”ƒ SUM โ”ƒ 130 โ”ƒ 140 โ”ƒ 135 โ”ƒ 130 โ”ƒ +โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”› + +โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”“ +โ”ƒ 1 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ +โ”ฃโ”โ”โ”โ•‹โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”ซ +โ”ƒ 2 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ +โ”—โ”โ”โ”โ”ปโ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”› +``` +### `thick_compact` + +``` +โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ +โ”ƒ # โ”ƒ G H R S โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ +โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ +โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ +โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ +โ”ƒ SUM โ”ƒ 130 140 135 130 โ”ƒ +โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› + +โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ +โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ +โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ +โ”—โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› +``` +### `thin` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ # โ”‚ G H R S โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` +### `thin_box` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ” +โ”‚ # โ”‚ G โ”‚ H โ”‚ R โ”‚ S โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ SUM โ”‚ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ” +โ”‚ 1 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚ +โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”˜ +``` +### `thin_compact` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ # โ”‚ G H R S โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` +### `thin_compact_rounded` + +``` +โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ # โ”‚ G H R S โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` +### `thin_double` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ # โ”‚ G H R S โ”‚ +โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` +### `thin_double_rounded` + +``` +โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ # โ”‚ G H R S โ”‚ +โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` +### `thin_rounded` + +``` +โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ # โ”‚ G H R S โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` +### `thin_thick` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ # โ”‚ G H R S โ”‚ +โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` +### `thin_thick_rounded` + +``` +โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ # โ”‚ G H R S โ”‚ +โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ +โ”‚ SUM โ”‚ 130 140 135 130 โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ 1 โ”‚ 30 40 35 30 โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ 30 40 35 30 โ”‚ +โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +``` diff --git a/style_list/generate_style_list.py b/style_list/generate_style_list.py new file mode 100644 index 0000000..150c32a --- /dev/null +++ b/style_list/generate_style_list.py @@ -0,0 +1,40 @@ +import os +from table2ascii import PresetStyle +from table2ascii.table_to_ascii import table2ascii + +# generate README.md containing all themes with previews +if __name__ == "__main__": + # get attributes in PresetStyle + attribute_names = [attr for attr in dir(PresetStyle) if not attr.startswith("__")] + attributes = [getattr(PresetStyle, attr) for attr in attribute_names] + # make a dict mapping style names to TableStyles + styles = dict(zip(attribute_names, attributes)) + # README output variables + heading = "## Preset styles" + table_of_contents = "- [Preset styles](#preset-styles)\n" + style_list = "" + # generate tables for each style + for style in list(styles.keys()): + full = table2ascii( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=styles[style], + ) + body_only = table2ascii( + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + first_col_heading=True, + last_col_heading=False, + style=styles[style], + ) + table_of_contents += f" - [`{style}`](#{style})\n" + style_list += f"### `{style}`\n\n```\n{full}\n\n{body_only}\n```\n" + # put it all together + output = f"{heading}\n\n{table_of_contents}\n{style_list}" + + # overwrite `style_list/README.md` with the changes + f = open(os.path.join("style_list", "README.md"), "w") + f.write(output) + f.close() diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index dc29de7..fd73c98 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -1,295 +1,11 @@ -import enum -from dataclasses import dataclass -from math import ceil, floor -from typing import List, Optional, Union - - -class Alignment(enum.Enum): - """Enum for alignment types""" - - LEFT = 0 - CENTER = 1 - RIGHT = 2 - - -@dataclass -class Options: - """Class for storing options that the user sets""" - - header: Optional[List] = None - body: Optional[List[List]] = None - footer: Optional[List] = None - first_col_heading: bool = False - last_col_heading: bool = False - column_widths: Optional[List[int]] = None - alignments: Optional[List[Alignment]] = None - - -class TableToAscii: - """Class used to convert a 2D Python table to ASCII text""" - - def __init__(self, options: Options): - """Validate arguments and initialize fields""" - # initialize fields - self.__header = options.header - self.__body = options.body - self.__footer = options.footer - self.__first_col_heading = options.first_col_heading - self.__last_col_heading = options.last_col_heading - - # calculate number of columns - self.__columns = self.__count_columns() - - # check if footer has a different number of columns - if options.footer and len(options.footer) != self.__columns: - raise ValueError( - "Footer must have the same number of columns as the other rows" - ) - # check if any rows in body have a different number of columns - if options.body and any(len(row) != self.__columns for row in options.body): - raise ValueError( - "All rows in body must have the same number of columns as the other rows" - ) - - # calculate or use given column widths - self.__column_widths = options.column_widths or self.__auto_column_widths() - - # check if column widths specified have a different number of columns - if options.column_widths and len(options.column_widths) != self.__columns: - raise ValueError( - "Length of `column_widths` list must equal the number of columns" - ) - # check if column widths are not all at least 2 - if options.column_widths and min(options.column_widths) < 2: - raise ValueError( - "All values in `column_widths` must be greater than or equal to 2" - ) - - self.__alignments = options.alignments or [Alignment.CENTER] * self.__columns - - # check if alignments specified have a different number of columns - if options.alignments and len(options.alignments) != self.__columns: - raise ValueError( - "Length of `alignments` list must equal the number of columns" - ) - - """ - โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— ABBBBBCBBBBBDBBBBBDBBBBBDBBBBBE - โ•‘ # โ•‘ G H R S โ•‘ F G H H H F - โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข IJJJJJKJJJJJLJJJJJLJJJJJLJJJJJM - โ•‘ 1 โ•‘ 30 40 35 30 โ•‘ F G H H H F - โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ F G H H H F - โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข NOOOOOPOOOOOQOOOOOQOOOOOQOOOOOR - โ•‘ SUM โ•‘ 130 140 135 130 โ•‘ F G H H H F - โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• SBBBBBTBBBBBUBBBBBUBBBBBUBBBBBV - """ - self.__parts = { - "top_left_corner": "โ•”", # A - "top_and_bottom_edge": "โ•", # B - "heading_col_top_tee": "โ•ฆ", # C - "top_tee": "โ•", # D - "top_right_corner": "โ•—", # E - "left_and_right_edge": "โ•‘", # F - "heading_col_sep": "โ•‘", # G - "middle_edge": " ", # H - "header_left_tee": "โ•Ÿ", # I - "header_row_sep": "โ”€", # J - "heading_col_header_cross": "โ•ซ", # K - "header_row_cross": "โ”€", # L - "header_right_tee": "โ•ข", # M - "footer_left_tee": "โ•Ÿ", # N - "footer_row_sep": "โ”€", # O - "heading_col_footer_cross": "โ•ซ", # P - "footer_row_cross": "โ”€", # Q - "footer_right_tee": "โ•ข", # R - "bottom_left_corner": "โ•š", # S - "heading_col_bottom_tee": "โ•ฉ", # T - "bottom_tee": "โ•", # U - "bottom_right_corner": "โ•", # V - } - - def __count_columns(self) -> int: - """Get the number of columns in the table - based on the provided header, footer, and body lists. - """ - if self.__header: - return len(self.__header) - if self.__footer: - return len(self.__footer) - if self.__body and len(self.__body) > 0: - return len(self.__body[0]) - return 0 - - def __auto_column_widths(self) -> List[int]: - """Get the minimum number of characters needed for the values - in each column in the table with 1 space of padding on each side. - """ - column_widths = [] - for i in range(self.__columns): - # number of characters in column of i of header, each body row, and footer - header_size = len(self.__header[i]) if self.__header else 0 - body_size = ( - map(lambda row, i=i: len(row[i]), self.__body) if self.__body else [0] - ) - footer_size = len(self.__footer[i]) if self.__footer else 0 - # get the max and add 2 for padding each side with a space - column_widths.append(max(header_size, *body_size, footer_size) + 2) - return column_widths - - def __pad(self, text: str, width: int, alignment: Alignment): - """Pad a string of text to a given width with specified alignment""" - if alignment == Alignment.LEFT: - # pad with spaces on the end - return f" {text} " + (" " * (width - len(text) - 2)) - if alignment == Alignment.CENTER: - # pad with spaces, half on each side - before = " " * floor((width - len(text) - 2) / 2) - after = " " * ceil((width - len(text) - 2) / 2) - return before + f" {text} " + after - if alignment == Alignment.RIGHT: - # pad with spaces at the beginning - return (" " * (width - len(text) - 2)) + f" {text} " - raise ValueError(f"The value '{alignment}' is not valid for alignment.") - - def __row_to_ascii( - self, - left_edge: str, - heading_col_sep: str, - column_seperator: str, - right_edge: str, - filler: Union[str, List], - ) -> str: - """Assembles a row of the ascii table""" - # left edge of the row - output = left_edge - # add columns - for i in range(self.__columns): - # content between separators - output += ( - # edge or row separator if filler is a specific character - filler * self.__column_widths[i] - if isinstance(filler, str) - # otherwise, use the column content - else self.__pad( - str(filler[i]), self.__column_widths[i], self.__alignments[i] - ) - ) - # column seperator - sep = column_seperator - if i == 0 and self.__first_col_heading: - # use column heading if first column option is specified - sep = heading_col_sep - elif i == self.__columns - 2 and self.__last_col_heading: - # use column heading if last column option is specified - sep = heading_col_sep - elif i == self.__columns - 1: - # replace last seperator with symbol for edge of the row - sep = right_edge - output += sep - return output + "\n" - - def __top_edge_to_ascii(self) -> str: - """Assembles the top edge of the ascii table""" - return self.__row_to_ascii( - left_edge=self.__parts["top_left_corner"], - heading_col_sep=self.__parts["heading_col_top_tee"], - column_seperator=self.__parts["top_tee"], - right_edge=self.__parts["top_right_corner"], - filler=self.__parts["top_and_bottom_edge"], - ) - - def __bottom_edge_to_ascii(self) -> str: - """Assembles the top edge of the ascii table""" - return self.__row_to_ascii( - left_edge=self.__parts["bottom_left_corner"], - heading_col_sep=self.__parts["heading_col_bottom_tee"], - column_seperator=self.__parts["bottom_tee"], - right_edge=self.__parts["bottom_right_corner"], - filler=self.__parts["top_and_bottom_edge"], - ) - - def __header_row_to_ascii(self) -> str: - """Assembles the header row line of the ascii table""" - return self.__row_to_ascii( - left_edge=self.__parts["left_and_right_edge"], - heading_col_sep=self.__parts["heading_col_sep"], - column_seperator=self.__parts["middle_edge"], - right_edge=self.__parts["left_and_right_edge"], - filler=self.__header, - ) - - def __footer_row_to_ascii(self) -> str: - """Assembles the header row line of the ascii table""" - return self.__row_to_ascii( - left_edge=self.__parts["left_and_right_edge"], - heading_col_sep=self.__parts["heading_col_sep"], - column_seperator=self.__parts["middle_edge"], - right_edge=self.__parts["left_and_right_edge"], - filler=self.__footer, - ) - - def __header_sep_to_ascii(self) -> str: - """Assembles the seperator below the header of the ascii table""" - return self.__row_to_ascii( - left_edge=self.__parts["header_left_tee"], - heading_col_sep=self.__parts["heading_col_header_cross"], - column_seperator=self.__parts["header_row_cross"], - right_edge=self.__parts["header_right_tee"], - filler=self.__parts["header_row_sep"], - ) - - def __footer_sep_to_ascii(self) -> str: - """Assembles the seperator below the header of the ascii table""" - return self.__row_to_ascii( - left_edge=self.__parts["footer_left_tee"], - heading_col_sep=self.__parts["heading_col_footer_cross"], - column_seperator=self.__parts["footer_row_cross"], - right_edge=self.__parts["footer_right_tee"], - filler=self.__parts["footer_row_sep"], - ) - - def __body_to_ascii(self) -> str: - return "".join( - self.__row_to_ascii( - left_edge=self.__parts["left_and_right_edge"], - heading_col_sep=self.__parts["heading_col_sep"], - column_seperator=self.__parts["middle_edge"], - right_edge=self.__parts["left_and_right_edge"], - filler=row, - ) - for row in self.__body - ) - - def to_ascii(self) -> str: - # top row of table - table = self.__top_edge_to_ascii() - # add table header - if self.__header: - table += self.__header_row_to_ascii() - table += self.__header_sep_to_ascii() - # add table body - if self.__body: - table += self.__body_to_ascii() - # add table footer - if self.__footer: - table += self.__footer_sep_to_ascii() - table += self.__footer_row_to_ascii() - # bottom row of table - table += self.__bottom_edge_to_ascii() - # reurn ascii table - return table - - -def table2ascii(**options) -> str: - """Convert a 2D Python table to ASCII text - - ### Arguments - :param header: :class:`Optional[List]` List of column values in the table's header row - :param body: :class:`Optional[List[List]]` 2-dimensional list of values in the table's body - :param footer: :class:`Optional[List]` List of column values in the table's footer row - :param column_widths: :class:`Optional[List[int]]` List of widths in characters for each column (defaults to auto-sizing) - :param alignments: :class:`Optional[List[Alignment]]` List of alignments (ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) - :param first_col_heading: :class:`Optional[bool]` Whether to add a header column separator after the first column - :param last_col_heading: :class:`Optional[bool]` Whether to add a header column separator before the last column - """ - return TableToAscii(Options(**options)).to_ascii() +from .alignment import Alignment +from .preset_style import PresetStyle +from .table_style import TableStyle +from .table_to_ascii import table2ascii + +__all__ = [ + "table2ascii", + "Alignment", + "TableStyle", + "PresetStyle", +] diff --git a/table2ascii/alignment.py b/table2ascii/alignment.py new file mode 100644 index 0000000..0f677ce --- /dev/null +++ b/table2ascii/alignment.py @@ -0,0 +1,9 @@ +import enum + + +class Alignment(enum.Enum): + """Enum for alignment types""" + + LEFT = 0 + CENTER = 1 + RIGHT = 2 diff --git a/table2ascii/options.py b/table2ascii/options.py new file mode 100644 index 0000000..7635711 --- /dev/null +++ b/table2ascii/options.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from typing import List, Optional + +from .preset_style import PresetStyle +from .alignment import Alignment +from .table_style import TableStyle + + +@dataclass +class Options: + """Class for storing options that the user sets""" + + first_col_heading: bool = False + last_col_heading: bool = False + column_widths: Optional[List[int]] = None + alignments: Optional[List[Alignment]] = None + style: TableStyle = PresetStyle.double_thin_compact diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py new file mode 100644 index 0000000..71b518e --- /dev/null +++ b/table2ascii/preset_style.py @@ -0,0 +1,33 @@ +from .table_style import TableStyle + + +class PresetStyle: + """Importable preset styles for more easily selecting a table style""" + + thin = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”คโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜") + thin_box = TableStyle.from_string("โ”Œโ”€โ”ฌโ”ฌโ”โ”‚โ”‚โ”‚โ”œโ”€โ”ผโ”ผโ”คโ”œโ”€โ”ผโ”ผโ”คโ””โ”ดโ”ดโ”˜") + thin_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”คโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏ") + thin_compact = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”ค โ””โ”ดโ”€โ”˜") + thin_compact_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”ค โ•ฐโ”ดโ”€โ•ฏ") + thin_thick = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”โ”โ”ฟโ”โ”ฅโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜") + thin_thick_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”โ”โ”ฟโ”โ”ฅโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏ") + thin_double = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ•žโ•โ•ชโ•โ•กโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜") + thin_double_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ•žโ•โ•ชโ•โ•กโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏ") + thick = TableStyle.from_string("โ”โ”โ”ณโ”โ”“โ”ƒโ”ƒ โ”ฃโ”โ•‹โ”โ”ซโ”ฃโ”โ•‹โ”โ”ซโ”—โ”ปโ”โ”›") + thick_box = TableStyle.from_string("โ”โ”โ”ณโ”ณโ”“โ”ƒโ”ƒโ”ƒโ”ฃโ”โ•‹โ•‹โ”ซโ”ฃโ”โ•‹โ•‹โ”ซโ”—โ”ปโ”ปโ”›") + thick_compact = TableStyle.from_string("โ”โ”โ”ณโ”โ”“โ”ƒโ”ƒ โ”ฃโ”โ•‹โ”โ”ซ โ”—โ”ปโ”โ”›") + double = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ• โ•โ•ฌโ•โ•ฃโ• โ•โ•ฌโ•โ•ฃโ•šโ•ฉโ•โ•") + double_box = TableStyle.from_string("โ•”โ•โ•ฆโ•ฆโ•—โ•‘โ•‘โ•‘โ• โ•โ•ฌโ•ฌโ•ฃโ• โ•โ•ฌโ•ฌโ•ฃโ•šโ•ฉโ•ฉโ•") + double_compact = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ• โ•โ•ฌโ•โ•ฃ โ•šโ•ฉโ•โ•") + double_thin_compact = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ•Ÿโ”€โ•ซโ”€โ•ข โ•šโ•ฉโ•โ•") + minimalist = TableStyle.from_string(" โ”€โ”€โ”€ โ”‚ โ”โ”โ” โ”€โ”€โ”€ โ”€โ”€ ") + borderless = TableStyle.from_string(" โ”ƒ โ” ") + simple = TableStyle.from_string(" โ• โ•‘ โ• ") + ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+") + ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++") + ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+") + ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+") + ascii_minimalist = TableStyle.from_string(" --- | === --- -- ") + ascii_borderless = TableStyle.from_string(" | - ") + ascii_simple = TableStyle.from_string(" = | = ") + markdown = TableStyle.from_string(" ||||-||| ") diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py new file mode 100644 index 0000000..238d963 --- /dev/null +++ b/table2ascii/table_style.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass + + +@dataclass +class TableStyle: + """Class for storing information about a table style + + **Parts of the table labeled alphabetically** + + ```text + ABBBBBCBBBBBDBBBBBDBBBBBDBBBBBE + F G H H H F + IJJJJJKJJJJJLJJJJJLJJJJJLJJJJJM + F G H H H F + NOOOOOPOOOOOQOOOOOQOOOOOQOOOOOR + F G H H H F + IJJJJJKJJJJJLJJJJJLJJJJJLJJJJJM + F G H H H F + SBBBBBTBBBBBUBBBBBUBBBBBUBBBBBV + ``` + + **How the theme is displayed with double thickness for + heading rows and columns and thin for normal rows and columns** + + ```text + โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•— + โ•‘ # โ•‘ G โ”‚ H โ”‚ R โ”‚ S โ•‘ + โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ + โ•‘ 1 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ 2 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ + โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ + โ•‘ SUM โ•‘ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ•‘ + โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ• + ``` + """ + + # parts of the table + top_left_corner: str # A + top_and_bottom_edge: str # B + heading_col_top_tee: str # C + top_tee: str # D + top_right_corner: str # E + left_and_right_edge: str # F + heading_col_sep: str # G + col_sep: str # H + heading_row_left_tee: str # I + heading_row_sep: str # J + heading_col_heading_row_cross: str # K + heading_row_cross: str # L + heading_row_right_tee: str # M + row_left_tee: str # N + row_sep: str # O + heading_col_row_cross: str # P + col_row_cross: str # Q + row_right_tee: str # R + bottom_left_corner: str # S + heading_col_bottom_tee: str # T + bottom_tee: str # U + bottom_right_corner: str # V + + # method for splitting string into argument list + @classmethod + def from_string(cls, string: str) -> "TableStyle": + return cls(*string) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py new file mode 100644 index 0000000..fcfc813 --- /dev/null +++ b/table2ascii/table_to_ascii.py @@ -0,0 +1,241 @@ +from math import ceil, floor +from typing import List, Union + +from .alignment import Alignment +from .options import Options + + +class TableToAscii: + """Class used to convert a 2D Python table to ASCII text""" + + def __init__(self, header: List, body: List[List], footer: List, options: Options): + """Validate arguments and initialize fields""" + # initialize fields + self.__header = header + self.__body = body + self.__footer = footer + self.__style = options.style + self.__first_col_heading = options.first_col_heading + self.__last_col_heading = options.last_col_heading + + # calculate number of columns + self.__columns = self.__count_columns() + + # check if footer has a different number of columns + if footer and len(footer) != self.__columns: + raise ValueError( + "Footer must have the same number of columns as the other rows" + ) + # check if any rows in body have a different number of columns + if body and any(len(row) != self.__columns for row in body): + raise ValueError( + "All rows in body must have the same number of columns as the other rows" + ) + + # calculate or use given column widths + self.__column_widths = self.__auto_column_widths() + if options.column_widths: + # check that the right number of columns were specified + if len(options.column_widths) != self.__columns: + raise ValueError( + "Length of `column_widths` list must equal the number of columns" + ) + # check that each column is at least as large as the minimum size + for i in range(len(options.column_widths)): + option = options.column_widths[i] + minimum = self.__column_widths[i] + if option < minimum: + raise ValueError( + f"The value at index {i} of `column_widths` is {option} which is less than the minimum {minimum}." + ) + self.__column_widths = options.column_widths + + self.__alignments = options.alignments or [Alignment.CENTER] * self.__columns + + # check if alignments specified have a different number of columns + if options.alignments and len(options.alignments) != self.__columns: + raise ValueError( + "Length of `alignments` list must equal the number of columns" + ) + + def __count_columns(self) -> int: + """Get the number of columns in the table + based on the provided header, footer, and body lists. + """ + if self.__header: + return len(self.__header) + if self.__footer: + return len(self.__footer) + if self.__body and len(self.__body) > 0: + return len(self.__body[0]) + return 0 + + def __auto_column_widths(self) -> List[int]: + """Get the minimum number of characters needed for the values + in each column in the table with 1 space of padding on each side. + """ + column_widths = [] + for i in range(self.__columns): + # number of characters in column of i of header, each body row, and footer + header_size = len(self.__header[i]) if self.__header else 0 + body_size = ( + map(lambda row, i=i: len(row[i]), self.__body) if self.__body else [0] + ) + footer_size = len(self.__footer[i]) if self.__footer else 0 + # get the max and add 2 for padding each side with a space + column_widths.append(max(header_size, *body_size, footer_size) + 2) + return column_widths + + def __pad(self, text: str, width: int, alignment: Alignment): + """Pad a string of text to a given width with specified alignment""" + if alignment == Alignment.LEFT: + # pad with spaces on the end + return f" {text} " + (" " * (width - len(text) - 2)) + if alignment == Alignment.CENTER: + # pad with spaces, half on each side + before = " " * floor((width - len(text) - 2) / 2) + after = " " * ceil((width - len(text) - 2) / 2) + return before + f" {text} " + after + if alignment == Alignment.RIGHT: + # pad with spaces at the beginning + return (" " * (width - len(text) - 2)) + f" {text} " + raise ValueError(f"The value '{alignment}' is not valid for alignment.") + + def __row_to_ascii( + self, + left_edge: str, + heading_col_sep: str, + column_seperator: str, + right_edge: str, + filler: Union[str, List], + ) -> str: + """Assembles a row of the ascii table""" + # left edge of the row + output = left_edge + # add columns + for i in range(self.__columns): + # content between separators + output += ( + # edge or row separator if filler is a specific character + filler * self.__column_widths[i] + if isinstance(filler, str) + # otherwise, use the column content + else self.__pad( + str(filler[i]), self.__column_widths[i], self.__alignments[i] + ) + ) + # column seperator + sep = column_seperator + if i == 0 and self.__first_col_heading: + # use column heading if first column option is specified + sep = heading_col_sep + elif i == self.__columns - 2 and self.__last_col_heading: + # use column heading if last column option is specified + sep = heading_col_sep + elif i == self.__columns - 1: + # replace last seperator with symbol for edge of the row + sep = right_edge + output += sep + # don't use separation row if it's only space + if output.strip() == "": + return "" + # otherwise, return the row followed by newline + return output + "\n" + + def __top_edge_to_ascii(self) -> str: + """Assembles the top edge of the ascii table""" + return self.__row_to_ascii( + left_edge=self.__style.top_left_corner, + heading_col_sep=self.__style.heading_col_top_tee, + column_seperator=self.__style.top_tee, + right_edge=self.__style.top_right_corner, + filler=self.__style.top_and_bottom_edge, + ) + + def __bottom_edge_to_ascii(self) -> str: + """Assembles the top edge of the ascii table""" + return self.__row_to_ascii( + left_edge=self.__style.bottom_left_corner, + heading_col_sep=self.__style.heading_col_bottom_tee, + column_seperator=self.__style.bottom_tee, + right_edge=self.__style.bottom_right_corner, + filler=self.__style.top_and_bottom_edge, + ) + + def __heading_row_to_ascii(self, row: List) -> str: + """Assembles the header or footer row line of the ascii table""" + return self.__row_to_ascii( + left_edge=self.__style.left_and_right_edge, + heading_col_sep=self.__style.heading_col_sep, + column_seperator=self.__style.col_sep, + right_edge=self.__style.left_and_right_edge, + filler=row, + ) + + def __heading_sep_to_ascii(self) -> str: + """Assembles the seperator below the header or above footer of the ascii table""" + return self.__row_to_ascii( + left_edge=self.__style.heading_row_left_tee, + heading_col_sep=self.__style.heading_col_heading_row_cross, + column_seperator=self.__style.heading_row_cross, + right_edge=self.__style.heading_row_right_tee, + filler=self.__style.heading_row_sep, + ) + + def __body_to_ascii(self) -> str: + separation_row = self.__row_to_ascii( + left_edge=self.__style.row_left_tee, + heading_col_sep=self.__style.heading_col_row_cross, + column_seperator=self.__style.col_row_cross, + right_edge=self.__style.row_right_tee, + filler=self.__style.row_sep, + ) + return separation_row.join( + self.__row_to_ascii( + left_edge=self.__style.left_and_right_edge, + heading_col_sep=self.__style.heading_col_sep, + column_seperator=self.__style.col_sep, + right_edge=self.__style.left_and_right_edge, + filler=row, + ) + for row in self.__body + ) + + def to_ascii(self) -> str: + # top row of table + table = self.__top_edge_to_ascii() + # add table header + if self.__header: + table += self.__heading_row_to_ascii(self.__header) + table += self.__heading_sep_to_ascii() + # add table body + if self.__body: + table += self.__body_to_ascii() + # add table footer + if self.__footer: + table += self.__heading_sep_to_ascii() + table += self.__heading_row_to_ascii(self.__footer) + # bottom row of table + table += self.__bottom_edge_to_ascii() + # reurn ascii table + return table.strip("\n") + + +def table2ascii( + header: List = None, body: List[List] = None, footer: List = None, **options +) -> str: + """Convert a 2D Python table to ASCII text + + ### Arguments + :param header: :class:`Optional[List]` List of column values in the table's header row + :param body: :class:`Optional[List[List]]` 2-dimensional list of values in the table's body + :param footer: :class:`Optional[List]` List of column values in the table's footer row + + ### Keyword required + :param style: :class:`Optional[TableStyle]` Table style to use for styling (preset styles can be imported) + :param column_widths: :class:`Optional[List[int]]` List of widths in characters for each column (defaults to auto-sizing) + :param alignments: :class:`Optional[List[Alignment]]` List of alignments (ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) + :param first_col_heading: :class:`Optional[bool]` Whether to add a header column separator after the first column + :param last_col_heading: :class:`Optional[bool]` Whether to add a header column separator before the last column + """ + return TableToAscii(header, body, footer, Options(**options)).to_ascii() diff --git a/tests/test_alignments.py b/tests/test_alignments.py index 12667dc..de34d37 100644 --- a/tests/test_alignments.py +++ b/tests/test_alignments.py @@ -19,7 +19,7 @@ def test_first_left_four_right(): "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected diff --git a/tests/test_column_widths.py b/tests/test_column_widths.py index 28be80d..5ae9ace 100644 --- a/tests/test_column_widths.py +++ b/tests/test_column_widths.py @@ -20,7 +20,7 @@ def test_column_widths(): "โ•‘ 2 โ•‘ 30 40 35 โ•‘ 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ TOTL โ•‘ 130 140 135 โ•‘ 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -47,3 +47,15 @@ def test_negative_column_widths(): last_col_heading=True, column_widths=[7, 5, 5, 5, -1], ) + + +def test_column_width_less_than_size(): + with pytest.raises(ValueError): + t2a( + header=["Wide Column", "Another Wide Column", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["TOTL", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=True, + column_widths=[5, 3, 3, 3, 3], + ) diff --git a/tests/test_convert.py b/tests/test_convert.py index b731988..9142b49 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -18,7 +18,7 @@ def test_header_body_footer(): "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -35,7 +35,7 @@ def test_body_footer(): "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -52,7 +52,7 @@ def test_header_body(): "โ•Ÿโ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -69,7 +69,7 @@ def test_header_footer(): "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -83,7 +83,7 @@ def test_header(): "โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" "โ•‘ # โ•‘ G H R S โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" - "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -97,7 +97,7 @@ def test_body(): "โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -111,7 +111,7 @@ def test_footer(): "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -161,7 +161,7 @@ def test_empty_header(): "โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -176,6 +176,6 @@ def test_empty_body(): "โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" "โ•‘ # โ•‘ G H R S โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" - "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected diff --git a/tests/test_heading_cols.py b/tests/test_heading_cols.py index 62cf5b5..2a5ccef 100644 --- a/tests/test_heading_cols.py +++ b/tests/test_heading_cols.py @@ -17,7 +17,7 @@ def test_first_column_heading(): "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -31,7 +31,7 @@ def test_first_column_heading_body_only(): "โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -52,7 +52,7 @@ def test_last_column_heading(): "โ•‘ 2 30 40 35 โ•‘ 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM 130 140 135 โ•‘ 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -66,7 +66,7 @@ def test_last_column_heading_body_only(): "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•—\n" "โ•‘ 1 30 40 35 โ•‘ 30 โ•‘\n" "โ•‘ 2 30 40 35 โ•‘ 30 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•" ) assert text == expected @@ -87,7 +87,7 @@ def test_both_column_heading(): "โ•‘ 2 โ•‘ 30 40 35 โ•‘ 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM โ•‘ 130 140 135 โ•‘ 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•" ) assert text == expected @@ -108,6 +108,6 @@ def test_neither_column_heading(): "โ•‘ 2 30 40 35 30 โ•‘\n" "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" "โ•‘ SUM 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" ) assert text == expected diff --git a/tests/test_styles.py b/tests/test_styles.py new file mode 100644 index 0000000..0e8a553 --- /dev/null +++ b/tests/test_styles.py @@ -0,0 +1,605 @@ +from table2ascii import table2ascii as t2a, PresetStyle + + +def test_thin(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin, + ) + expected = ( + "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + ) + assert text == expected + + +def test_thin_box(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_box, + ) + expected = ( + "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”\n" + "โ”‚ # โ”‚ G โ”‚ H โ”‚ R โ”‚ S โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 1 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ SUM โ”‚ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ”‚\n" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜" + ) + assert text == expected + + +def test_thin_rounded(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_rounded, + ) + expected = ( + "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + ) + assert text == expected + + +def test_thin_compact(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_compact, + ) + expected = ( + "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + ) + assert text == expected + + +def test_thin_compact_rounded(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_compact_rounded, + ) + expected = ( + "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + ) + assert text == expected + + +def test_thin_thick(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_thick, + ) + expected = ( + "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + ) + assert text == expected + + +def test_thin_thick_rounded(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_thick_rounded, + ) + expected = ( + "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + ) + assert text == expected + + +def test_thin_double(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_double, + ) + expected = ( + "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + ) + assert text == expected + + +def test_thin_double_rounded(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thin_double_rounded, + ) + expected = ( + "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" + "โ”‚ # โ”‚ G H R S โ”‚\n" + "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" + "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" + "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" + "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" + "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + ) + assert text == expected + + +def test_thick(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thick, + ) + expected = ( + "โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\n" + "โ”ƒ # โ”ƒ G H R S โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ SUM โ”ƒ 130 140 135 130 โ”ƒ\n" + "โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›" + ) + assert text == expected + + +def test_thick_box(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thick_box, + ) + expected = ( + "โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”“\n" + "โ”ƒ # โ”ƒ G โ”ƒ H โ”ƒ R โ”ƒ S โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ 1 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ 2 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ SUM โ”ƒ 130 โ”ƒ 140 โ”ƒ 135 โ”ƒ 130 โ”ƒ\n" + "โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”›" + ) + assert text == expected + + +def test_thick_compact(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.thick_compact, + ) + expected = ( + "โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\n" + "โ”ƒ # โ”ƒ G H R S โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ\n" + "โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ\n" + "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" + "โ”ƒ SUM โ”ƒ 130 140 135 130 โ”ƒ\n" + "โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›" + ) + assert text == expected + + +def test_double(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.double, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G H R S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_double_box(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.double_box, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G โ•‘ H โ•‘ R โ•‘ S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 2 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ•‘ 130 โ•‘ 140 โ•‘ 135 โ•‘ 130 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_double_compact(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.double_compact, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G H R S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" + "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_double_thin_compact(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.double_thin_compact, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G H R S โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" + "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_minimalist(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.minimalist, + ) + expected = ( + " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ \n" + " # โ”‚ G H R S \n" + " โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” \n" + " 1 โ”‚ 30 40 35 30 \n" + " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ \n" + " 2 โ”‚ 30 40 35 30 \n" + " โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” \n" + " SUM โ”‚ 130 140 135 130 \n" + " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ " + ) + assert text == expected + + +def test_borderless(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.borderless, + ) + expected = ( + " # โ”ƒ G H R S \n" + " โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” \n" + " 1 โ”ƒ 30 40 35 30 \n" + " 2 โ”ƒ 30 40 35 30 \n" + " โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” \n" + " SUM โ”ƒ 130 140 135 130 " + ) + assert text == expected + + +def test_simple(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.simple, + ) + expected = ( + " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• \n" + " # โ•‘ G H R S \n" + " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• \n" + " 1 โ•‘ 30 40 35 30 \n" + " 2 โ•‘ 30 40 35 30 \n" + " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• \n" + " SUM โ•‘ 130 140 135 130 \n" + " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• " + ) + assert text == expected + + +def test_ascii(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii, + ) + expected = ( + "+-----+-----------------------+\n" + "| # | G H R S |\n" + "+-----+-----------------------+\n" + "| 1 | 30 40 35 30 |\n" + "+-----+-----------------------+\n" + "| 2 | 30 40 35 30 |\n" + "+-----+-----------------------+\n" + "| SUM | 130 140 135 130 |\n" + "+-----+-----------------------+" + ) + assert text == expected + + +def test_ascii_box(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii_box, + ) + expected = ( + "+-----+-----+-----+-----+-----+\n" + "| # | G | H | R | S |\n" + "+-----+-----+-----+-----+-----+\n" + "| 1 | 30 | 40 | 35 | 30 |\n" + "+-----+-----+-----+-----+-----+\n" + "| 2 | 30 | 40 | 35 | 30 |\n" + "+-----+-----+-----+-----+-----+\n" + "| SUM | 130 | 140 | 135 | 130 |\n" + "+-----+-----+-----+-----+-----+" + ) + assert text == expected + + +def test_ascii_compact(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii_compact, + ) + expected = ( + "+-----+-----------------------+\n" + "| # | G H R S |\n" + "+-----+-----------------------+\n" + "| 1 | 30 40 35 30 |\n" + "| 2 | 30 40 35 30 |\n" + "+-----+-----------------------+\n" + "| SUM | 130 140 135 130 |\n" + "+-----+-----------------------+" + ) + assert text == expected + + +def test_ascii_double(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii_double, + ) + expected = ( + "+-----+-----------------------+\n" + "| # | G H R S |\n" + "+=====+=======================+\n" + "| 1 | 30 40 35 30 |\n" + "+-----+-----------------------+\n" + "| 2 | 30 40 35 30 |\n" + "+=====+=======================+\n" + "| SUM | 130 140 135 130 |\n" + "+-----+-----------------------+" + ) + assert text == expected + + +def test_ascii_minimalist(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii_minimalist, + ) + expected = ( + " ----------------------------- \n" + " # | G H R S \n" + " ============================= \n" + " 1 | 30 40 35 30 \n" + " ----------------------------- \n" + " 2 | 30 40 35 30 \n" + " ============================= \n" + " SUM | 130 140 135 130 \n" + " ----------------------------- " + ) + assert text == expected + + +def test_ascii_borderless(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii_borderless, + ) + expected = ( + " # | G H R S \n" + " ----- ----- ----- ----- ----- \n" + " 1 | 30 40 35 30 \n" + " 2 | 30 40 35 30 \n" + " ----- ----- ----- ----- ----- \n" + " SUM | 130 140 135 130 " + ) + assert text == expected + + +def test_ascii_simple(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.ascii_simple, + ) + expected = ( + " ===== ===== ===== ===== ===== \n" + " # | G H R S \n" + " ===== ===== ===== ===== ===== \n" + " 1 | 30 40 35 30 \n" + " 2 | 30 40 35 30 \n" + " ===== ===== ===== ===== ===== \n" + " SUM | 130 140 135 130 \n" + " ===== ===== ===== ===== ===== " + ) + assert text == expected + + +def test_markdown(): + text = t2a( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + first_col_heading=True, + last_col_heading=False, + style=PresetStyle.markdown, + ) + expected = ( + "| # | G | H | R | S |\n" + "|-----|-----|-----|-----|-----|\n" + "| 1 | 30 | 40 | 35 | 30 |\n" + "| 2 | 30 | 40 | 35 | 30 |\n" + "|-----|-----|-----|-----|-----|\n" + "| SUM | 130 | 140 | 135 | 130 |" + ) + assert text == expected