package core:text/table
Overview
The package table
implements plain-text/markdown/HTML/custom rendering of tables.
Custom rendering example:
package main import "core:io" import "core:text/table" main :: proc() { stdout := table.stdio_writer() tbl := table.init(&table.Table{}) table.padding(tbl, 0, 1) table.row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM") table.row(tbl, "AN_EVEN_LONGER_ENUM", "= 1,", "// A comment about AN_EVEN_LONGER_ENUM") table.build(tbl, table.unicode_width_proc) for row in 0..<tbl.nr_rows { for col in 0..<tbl.nr_cols { table.write_table_cell(stdout, tbl, row, col) } io.write_byte(stdout, '\n') } }
This outputs:
A_LONG_ENUM = 54, // A comment about A_LONG_ENUM AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM
Plain-text rendering example:
package main import "core:fmt" import "core:io" import "core:text/table" main :: proc() { stdout := table.stdio_writer() tbl := table.init(&table.Table{}) defer table.destroy(tbl) table.caption(tbl, "This is a table caption and it is very long") table.padding(tbl, 1, 1) // Left/right padding of cells table.header(tbl, "AAAAAAAAA", "B") table.header(tbl, "C") // Appends to previous header row. Same as if done header("AAAAAAAAA", "B", "C") from start. // Create a row with two values. Since there are three columns the third // value will become the empty string. // // NOTE: table.header() is not allowed anymore after this. table.row(tbl, 123, "foo") // Use `format()` if you need custom formatting. This will allocate into // the arena specified at init. table.row(tbl, table.format(tbl, "%09d", 5), table.format(tbl, "%.6f", 6.28318530717958647692528676655900576)) // A row with zero values is allowed as long as a previous row or header // exist. The value and alignment of each cell can then be set // individually. table.row(tbl) table.set_cell_value_and_alignment(tbl, table.last_row(tbl), 0, "a", .Center) table.set_cell_value(tbl, table.last_row(tbl), 1, "bbb") table.set_cell_value(tbl, table.last_row(tbl), 2, "c") // Headers are regular cells, too. Use header_row() as row index to modify // header cells. table.set_cell_alignment(tbl, table.header_row(tbl), 1, .Center) // Sets alignment of 'B' column to Center. table.set_cell_alignment(tbl, table.header_row(tbl), 2, .Right) // Sets alignment of 'C' column to Right. table.write_plain_table(stdout, tbl) fmt.println() table.write_markdown_table(stdout, tbl) }
This outputs:
+-----------------------------------------------+ | This is a table caption and it is very long | +------------------+-----------------+----------+ | AAAAAAAAA | B | C | +------------------+-----------------+----------+ | 123 | foo | | | 000000005 | 6.283185 | | | a | bbb | c | +------------------+-----------------+----------+
and
| AAAAAAAAA | B | C | |:-----------------|:---------------:|---------:| | 123 | foo | | | 000000005 | 6.283185 | | | a | bbb | c |
respectively.
Additionally, if you want to set the alignment and values in-line while
constructing a table, you can use aligned_row_of_values
or
row_of_aligned_values
like so:
table.aligned_row_of_values(tbl, .Center, "Foo", "Bar") table.row_of_aligned_values(tbl, {{.Center, "Foo"}, {.Right, "Bar"}})
Caching Results:
If you only need to build a table once but display it potentially many times, it may be more efficient to cache the results of your write into a string.
Here's an example of how you can do that:
package main import "core:fmt" import "core:strings" import "core:text/table" main :: proc() { string_buffer := strings.builder_make() defer strings.builder_destroy(&string_buffer) { tbl: table.Table table.init(&tbl) defer table.destroy(&tbl) table.caption(&tbl, "Hellope!") table.row(&tbl, "Hellope", "World") builder_writer := strings.to_writer(&string_buffer) // The written table will be cached into the string builder after this call. table.write_plain_table(builder_writer, &tbl) } // The table is inaccessible, now that we're back in the first-level scope. // But now the results are stored in the string builder, which can be converted to a string. my_table_string := strings.to_string(string_buffer) // Remember that the string's allocated backing data lives in the // builder and must still be freed. // // The deferred call to `builder_destroy` will take care of that for us // in this simple example. fmt.println(my_table_string) }
Regarding `Width_Procs`:
If you know ahead of time that all the text you're parsing is ASCII, instead of
Unicode, it is more efficient to use table.ascii_width_proc
instead of the
default unicode_width_proc
, as that procedure has to perform in-depth lookups
to determine multiple Unicode characteristics of the codepoints parsed in order
to get the proper alignment for a variety of different scripts.
For example, you may do this instead:
table.write_plain_table(stdout, tbl, table.ascii_width_proc) table.write_markdown_table(stdout, tbl, table.ascii_width_proc)
The output will still be the same, but the preprocessing is much faster.
You may also supply your own Width_Proc
s, if you know more about how the text
is structured than what we can assume.
simple_cjk_width_proc :: proc(str: string) -> (result: int) { for r in str { result += 2 } return } table.write_plain_table(stdout, tbl, simple_cjk_width_proc)
This procedure will output 2 times the number of UTF-8 runes in a string, a simple heuristic for CJK-only wide text.
Unicode Support:
This package makes use of the grapheme_count
procedure from the
core:unicode/utf8
package. It is a complete, standards-compliant
implementation for counting graphemes and calculating visual width of a Unicode
grapheme cluster in monospace cells.
Here is a full example of how well-supported Unicode is with this package:
package main import "core:fmt" import "core:io" import "core:os" import "core:text/table" scripts :: proc(w: io.Writer) { t: table.Table table.init(&t) table.caption(&t, "Tést Suite") table.padding(&t, 1, 3) table.header_of_aligned_values(&t, {{.Left, "Script"}, {.Center, "Sample"}}) table.row(&t, "Latin", "At vero eos et accusamus et iusto odio dignissimos ducimus,") table.row(&t, "Cyrillic", "Ру́сский язы́к — язык восточнославянской группы славянской") table.row(&t, "Greek", "Η ελληνική γλώσσα ανήκει στην ινδοευρωπαϊκή οικογένεια") table.row(&t, "Younger Futhark", "ᚴᚢᚱᛘᛦ ᚴᚢᚾᚢᚴᛦ ᚴᛅᚱᚦᛁ ᚴᚢᛒᛚ ᚦᚢᛋᛁ ᛅᚠᛏ ᚦᚢᚱᚢᛁ ᚴᚢᚾᚢ ᛋᛁᚾᛅ ᛏᛅᚾᛘᛅᚱᚴᛅᛦ ᛒᚢᛏ") table.row(&t, "Chinese hanzi", "官話為汉语的一支,主體分布在中国北部和西南部的大部分地区。") table.row(&t, "Japanese kana", "いろはにほへとちりぬるをわかよたれそつねならむ") table.row(&t, "Korean hangul", "한글, 조선글은 한국어의 공식문자로서, 세종이 한국어를") table.row(&t, "Thai", "ภาษาไทย หรือ ภาษาไทยกลาง เป็นภาษาในกลุ่มภาษาไท ซึ่งเป็นกลุ่มย่อยของตระกูลภาษาขร้า-ไท") table.row(&t, "Georgian", "ქართული ენა — ქართველურ ენათა ოჯახის ენა. ქართველების მშობლიური ენა,") table.row(&t, "Armenian", "Իր շուրջ հինգհազարամյա գոյության ընթացքում հայերենը շփվել է տարբեր") table.row(&t) table.row_of_aligned_values(&t, {{.Left, "Arabic"}, {.Right, "ٱللُّغَةُ ٱلْعَرَبِيَّة هي أكثر اللغات السامية تحدثًا، وإحدى أكثر"}}) table.row_of_aligned_values(&t, {{.Left, "Hebrew"}, {.Right, "עִבְרִית היא שפה שמית, ממשפחת השפות האפרו-אסייתיות, הידועה"}}) table.row(&t) table.row(&t, "Swedish", "Växjö [ˈvɛkːˌɧøː] är en tätort i södra Smålands inland samt centralort i Växjö kommun") table.row(&t, "Saxon", "Hwæt! We Gardena in geardagum, þeodcyninga, þrym gefrunon, hu ða æþelingas ellen fremedon.") table.row(&t) table.aligned_row_of_values(&t, .Center, "Emoji (Single codepoints)", "\U0001f4ae \U0001F600 \U0001F201 \U0001F21A") table.row(&t, "Excessive Diacritics", "H̷e̶l̵l̸o̴p̵e̷ ̸w̶o̸r̵l̶d̵!̴") table.write_plain_table(w, &t) fmt.println() } main :: proc() { stdout := os.stream_from_handle(os.stdout) scripts(stdout) }
This will print out:
+----------------------------------------------------------------------------------------------------------------------------+ | Tést Suite | +-----------------------------+----------------------------------------------------------------------------------------------+ | Script | Sample | +-----------------------------+----------------------------------------------------------------------------------------------+ | Latin | At vero eos et accusamus et iusto odio dignissimos ducimus, | | Cyrillic | Ру́сский язы́к — язык восточнославянской группы славянской | | Greek | Η ελληνική γλώσσα ανήκει στην ινδοευρωπαϊκή οικογένεια | | Younger Futhark | ᚴᚢᚱᛘᛦ ᚴᚢᚾᚢᚴᛦ ᚴᛅᚱᚦᛁ ᚴᚢᛒᛚ ᚦᚢᛋᛁ ᛅᚠᛏ ᚦᚢᚱᚢᛁ ᚴᚢᚾᚢ ᛋᛁᚾᛅ ᛏᛅᚾᛘᛅᚱᚴᛅᛦ ᛒᚢᛏ | | Chinese hanzi | 官話為汉语的一支,主體分布在中国北部和西南部的大部分地区。 | | Japanese kana | いろはにほへとちりぬるをわかよたれそつねならむ | | Korean hangul | 한글, 조선글은 한국어의 공식문자로서, 세종이 한국어를 | | Thai | ภาษาไทย หรือ ภาษาไทยกลาง เป็นภาษาในกลุ่มภาษาไท ซึ่งเป็นกลุ่มย่อยของตระกูลภาษาขร้า-ไท | | Georgian | ქართული ენა — ქართველურ ენათა ოჯახის ენა. ქართველების მშობლიური ენა, | | Armenian | Իր շուրջ հինգհազարամյա գոյության ընթացքում հայերենը շփվել է տարբեր | | | | | Arabic | ٱللُّغَةُ ٱلْعَرَبِيَّة هي أكثر اللغات السامية تحدثًا، وإحدى أكثر | | Hebrew | עִבְרִית היא שפה שמית, ממשפחת השפות האפרו-אסייתיות, הידועה | | | | | Swedish | Växjö [ˈvɛkːˌɧøː] är en tätort i södra Smålands inland samt centralort i Växjö kommun | | Saxon | Hwæt! We Gardena in geardagum, þeodcyninga, þrym gefrunon, hu ða æþelingas ellen fremedon. | | | | | Emoji (Single codepoints) | 💮 😀 🈁 🈚 | | Excessive Diacritics | H̷e̶l̵l̸o̴p̵e̷ ̸w̶o̸r̵l̶d̵!̴ | +-----------------------------+----------------------------------------------------------------------------------------------+
Decorated Tables:
If you'd prefer to change the borders used by the plain-text table printing,
there is the write_decorated_table
procedure that allows you to change the
corners and dividers.
Here is a complete example:
package main import "core:fmt" import "core:io" import "core:os" import "core:text/table" box_drawing :: proc(w: io.Writer) { t: table.Table table.init(&t) table.caption(&t, "Box Drawing Example") table.padding(&t, 2, 2) table.header_of_aligned_values(&t, {{.Left, "Operating System"}, {.Center, "Year Introduced"}}) table.row(&t, "UNIX", "1973") table.row(&t, "MS-DOS", "1981") table.row(&t, "Commodore 64 KERNAL", "1982") table.row(&t, "Mac OS", "1984") table.row(&t, "Amiga", "1985") table.row(&t, "Windows 1.0", "1985") table.row(&t, "Linux", "1991") table.row(&t, "Windows 3.1", "1992") decorations := table.Decorations { "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘", "│", "─", } table.write_decorated_table(w, &t, decorations) fmt.println() } main :: proc() { stdout := os.stream_from_handle(os.stdout) box_drawing(stdout) }
While the decorations support multi-codepoint Unicode graphemes, do note that each border character should not be larger than one monospace cell.
Index
Types (6)
Constants (0)
This section is empty.
Variables (0)
This section is empty.
Procedures (34)
- aligned_header_of_values
- aligned_row_of_values
- ascii_width_proc
- build
- caption
- destroy
- first_row
- format
- get_cell
- header
- header_of_aligned_values
- header_of_values
- header_row
- init_with_allocator
- init_with_mem_arena
- init_with_virtual_arena
- last_row
- padding
- row
- row_of_aligned_values
- row_of_values
- set_cell_alignment
- set_cell_value
- set_cell_value_and_alignment
- stdio_writer
- strings_builder_writer
- unicode_width_proc
- write_byte_repeat
- write_decorated_table
- write_html_table
- write_markdown_table
- write_plain_table
- write_table_cell
- write_text_align
Procedure Groups (1)
Types
Aligned_Value ¶
Aligned_Value :: struct { alignment: Cell_Alignment, value: any, }
Cell ¶
Cell :: struct { text: string, width: int, alignment: Cell_Alignment, }
Related Procedures With Returns
Cell_Alignment ¶
Cell_Alignment :: enum int { Left, Center, Right, }
Related Procedures With Parameters
Decorations ¶
Decorations :: struct { // Connecting decorations: nw: string, n: string, ne: string, w: string, x: string, e: string, sw: string, s: string, se: string, // Straight lines: vert: string, horz: string, }
Related Procedures With Parameters
Table ¶
Table :: struct { lpad: int, rpad: int, // Cell padding (left/right) cells: [dynamic]Cell, caption: string, nr_rows: int, nr_cols: int, has_header_row: bool, table_allocator: runtime.Allocator, // Used for allocating cells/colw format_allocator: runtime.Allocator, // The following are computed on build() colw: [dynamic]int, // Width of each column (excluding padding and borders) tblw: int, }
Related Procedures With Parameters
- aligned_header_of_values
- aligned_row_of_values
- build
- caption
- destroy
- first_row
- format
- get_cell
- header_of_aligned_values
- header_of_values
- header_row
- init_with_allocator
- init_with_mem_arena
- init_with_virtual_arena
- last_row
- padding
- row_of_aligned_values
- row_of_values
- set_cell_alignment
- set_cell_value
- set_cell_value_and_alignment
- write_decorated_table
- write_html_table
- write_markdown_table
- write_plain_table
- write_table_cell
- init (procedure groups)
Width_Proc ¶
Determines the width of a string used in the table for alignment purposes.
Related Procedures With Parameters
Constants
This section is empty.
Variables
This section is empty.
Procedures
aligned_header_of_values ¶
aligned_header_of_values :: proc(tbl: ^Table, alignment: Cell_Alignment, .. values: ..any, loc := #caller_location) {…}
aligned_row_of_values ¶
aligned_row_of_values :: proc(tbl: ^Table, alignment: Cell_Alignment, .. values: ..any, loc := #caller_location) {…}
build ¶
build :: proc(tbl: ^Table, width_proc: Width_Proc) {…}
get_cell ¶
get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell {…}
header ¶
header :: header_of_values
header_of_aligned_values ¶
header_of_aligned_values :: proc(tbl: ^Table, aligned_values: []Aligned_Value, loc := #caller_location) {…}
header_of_values ¶
header_of_values :: proc(tbl: ^Table, .. values: ..any, loc := #caller_location) {…}
init_with_allocator ¶
init_with_allocator :: proc(tbl: ^Table, format_allocator := context.temp_allocator, table_allocator := context.allocator) -> ^Table {…}
init_with_mem_arena ¶
init_with_mem_arena :: proc(tbl: ^Table, format_arena: ^mem.Arena, table_allocator := context.allocator) -> ^Table {…}
init_with_virtual_arena ¶
init_with_virtual_arena :: proc(tbl: ^Table, format_arena: ^mem_virtual.Arena, table_allocator := context.allocator) -> ^Table {…}
row ¶
row :: row_of_values
row_of_aligned_values ¶
row_of_aligned_values :: proc(tbl: ^Table, aligned_values: []Aligned_Value, loc := #caller_location) {…}
row_of_values ¶
row_of_values :: proc(tbl: ^Table, .. values: ..any, loc := #caller_location) {…}
set_cell_alignment ¶
set_cell_alignment :: proc(tbl: ^Table, row, col: int, alignment: Cell_Alignment, loc := #caller_location) {…}
set_cell_value ¶
set_cell_value :: proc(tbl: ^Table, row, col: int, value: any, loc := #caller_location) {…}
set_cell_value_and_alignment ¶
set_cell_value_and_alignment :: proc( tbl: ^Table, row, col: int, value: any, alignment: Cell_Alignment, loc := #caller_location, ) {…}
stdio_writer ¶
stdio_writer :: proc() -> io.Stream {…}
write_decorated_table ¶
write_decorated_table :: proc(w: io.Stream, tbl: ^Table, decorations: Decorations, width_proc: Width_Proc = unicode_width_proc) {…}
write_markdown_table ¶
write_markdown_table :: proc(w: io.Stream, tbl: ^Table, width_proc: Width_Proc = unicode_width_proc) {…}
Renders table according to GitHub Flavored Markdown (GFM) specification
write_plain_table ¶
write_plain_table :: proc(w: io.Stream, tbl: ^Table, width_proc: Width_Proc = unicode_width_proc) {…}
write_text_align ¶
write_text_align :: proc( w: io.Stream, text: string, alignment: Cell_Alignment, lpad, rpad, space: int, ) {…}
Procedure Groups
init ¶
init :: proc{ init_with_allocator, init_with_virtual_arena, init_with_mem_arena, }
Source Files
Generation Information
Generated with odin version dev-2024-07 (vendor "odin") Windows_amd64 @ 2024-07-26 21:10:25.142043000 +0000 UTC