package core:text/table

⌘K
Ctrl+K
or
/

    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')
    	}
    }
    
    Output:
    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)
    }
    
    Output:
    +-----------------------------------------------+
    |  This is a table caption and it is very long  |
    +------------------+-----------------+----------+
    | AAAAAAAAA        |        B        |        C |
    +------------------+-----------------+----------+
    | 123              | foo             |          |
    | 000000005        | 6.283185        |          |
    |        a         | bbb             | c        |
    +------------------+-----------------+----------+
    
    |    AAAAAAAAA     |        B        |    C     |
    |:-----------------|:---------------:|---------:|
    | 123              | foo             |          |
    | 000000005        | 6.283185        |          |
    | a                | bbb             | c        |
    

    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.

    Example:
    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_Procs, 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.

    Example:
    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)
    }
    
    Output:
    +----------------------------------------------------------------------------------------------------------------------------+
    |                                                        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.

    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.

    Copyright 2023 oskarnp <oskarnp@proton.me>
    Made available under Odin's BSD-3 license.
    
    List of contributors:
    	oskarnp: Initial implementation.
    	Feoramund: Unicode support.
    

    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

    Width_Proc ¶

    Width_Proc :: proc(str: string) -> int
     

    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) {…}

    ascii_width_proc ¶

    ascii_width_proc :: proc(str: string) -> int {…}

    build ¶

    build :: proc(tbl: ^Table, width_proc: Width_Proc) {…}

    caption ¶

    caption :: proc(tbl: ^Table, value: string) {…}

    destroy ¶

    destroy :: proc(tbl: ^Table) {…}

    first_row ¶

    first_row :: proc(tbl: ^Table) -> int {…}

    format ¶

    format :: proc(tbl: ^Table, _fmt: string, .. args: ..any) -> string {…}

    get_cell ¶

    get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell {…}

    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) {…}

    header_row ¶

    header_row :: proc(tbl: ^Table) -> int {…}

    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 {…}

    last_row ¶

    last_row :: proc(tbl: ^Table) -> int {…}

    padding ¶

    padding :: proc(tbl: ^Table, lpad, rpad: int) {…}

    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 {…}

    strings_builder_writer ¶

    strings_builder_writer :: proc(b: ^strings.Builder) -> io.Stream {…}

    unicode_width_proc ¶

    unicode_width_proc :: proc(str: string) -> (width: int) {…}

    write_byte_repeat ¶

    write_byte_repeat :: proc(w: io.Stream, n: int, b: u8) {…}

    write_decorated_table ¶

    write_decorated_table :: proc(w: io.Stream, tbl: ^Table, decorations: Decorations, width_proc: Width_Proc = unicode_width_proc) {…}

    write_html_table ¶

    write_html_table :: proc(w: io.Stream, tbl: ^Table) {…}

    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_table_cell ¶

    write_table_cell :: proc(w: io.Stream, tbl: ^Table, row, col: int) {…}

    write_text_align ¶

    write_text_align :: proc(
    	w:          io.Stream, 
    	text:       string, 
    	alignment:  Cell_Alignment, 
    	lpad, rpad, 
    	space:      int, 
    ) {…}

    Procedure Groups

    Source Files

    Generation Information

    Generated with odin version dev-2024-12 (vendor "odin") Windows_amd64 @ 2024-12-17 21:11:02.083931200 +0000 UTC