tabular

package module
v1.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2021 License: MIT Imports: 6 Imported by: 2

README

tabular

Continuous Integration Documentation Coverage Status Current Tag Issues Repo Size

The tabular package provides a Golang library for storing data in a table consisting of rows and columns. Sub-packages provide for rendering such a table as a terminal box-table (line-drawing with UTF-8 box-drawing in various styles, or ASCII), as HTML, or as CSV data.

The core data model is designed to be extensible and powerful, letting such a table be embedded in various more sophisticated models. (Eg, core of a spreadsheet). Cells in the table contain arbitrary data and possess metadata in the form of "properties", modelled after the context package's Context.

A table can be created from the base package and then populated, before being passed to any of the renderers, or a table can be directly created using a sub-package, such that you probably won't need to import the base package directly.

An overview guide to the codebase can be found in the Overview.md

The usage documentation is in Godoc format

See the examples for a gentler introduction.

This package should be installable in the usual go get manner.

This software is under a MIT-type license.

When embedding into another tool, Go Modules support is able to report on the version numbers of all dependencies with go version -m $cmdname; to support your own version reporting framework, go.pennock.tech/tabular.Versions() returns a slice of strings (including API versions and the value of the LinkerSpecifiedVersion top-level variable).

This package uses semantic versioning.
Note that Go only supports the most recent two minor versions of the language; for the purposes of semver, we do not consider it a breaking change to add a dependency upon a language or standard library feature supported by all currently-supported releases of Go.


Projects using Tabular
  • character — Unicode character lookups and manipulations

Documentation

Overview

The tabular package provides for a model of a two-dimensional grid of cells, or a "table". Sub-packages provide for rendering such a table in convenient ways. For many uses, you only need to import the sub-package.

Sub-packages provide for rendering a TextTable which is designed for a fixed-cell grid layout of character cells, such as a Unix TTY; for rendering as an HTML table; for rendering as a CSV.

The core package provides a Table interface; each sub-package embeds a Table in their own core object, so all methods in the Table interface can be directly used upon such objects. These provide for your basic addition of cells, etc.

The API is designed to let you add data quickly, without error-checking every addition. Instead, error containers are used, and errors accumulate therein. The table is an error-container. A row which is not in a table is also an error container, but any such errors get moved into the table's collection when the row is added, and the row thenceforth uses the table's container.

In addition to tables of rows of cells, with virtual columns, we also have "properties" and "property callbacks". People using tables with pre-canned rendering should not need to care about these, but people writing renderers will need to.

Properties are held by cells, rows, columns and tables. They are metadata, designed to be used by sub-packages or anything doing non-trivial table embedding, to register extra data "about" something. They're akin to stdlib's "context", in that the various users can maintained their own namespaced properties, using a similar API, but can be automatically updated by the table.

Examples might include terminal text properties, color, calculation attributes.

Properties are held by anything which satisfies the PropertyOwner interface. Such objects then have GetProperty and SetProperty methods.

There is an API balancing act for callers to decide between registering a property upon the table itself, or wrapping the table: if you want the value to be automatically updated as a table is mutated, the property approach might make the most sense, but then with a simple API to present it via a method of your wrapper object.

A Property Callback is "anything with the UpdateProperties method", which can be registered with something in the table to be called to automatically update properties. These can be registered on a table, row, column or cell; most are applied to cells, but some are applied to larger objects. The registration needs to know what object is to hold the callback, which target the callback is to be applied to, when it should be invoked, and the new callback being registered.

Index

Constants

View Source
const (
	// CB_AT_ADD called when an item is added to a container
	CB_AT_ADD callbackTime = iota

	// CB_AT_RENDER_PRECELL for callbacks registered on containers, to be
	// called before cell's own callbacks.  Use before dimensions/etc locked
	// down.
	CB_AT_RENDER_PRECELL

	// CB_AT_RENDER is called for two sets: table (not row/column) and cell
	// callbacks.  An assumption is that it might be used for deriving
	// properties such as dimensions, the effect of which cascade outwards.
	CB_AT_RENDER

	// CB_AT_RENDER_POSTCELL is called after the cell's own callbacks.
	CB_AT_RENDER_POSTCELL
)

These constants are used for callback functions, to control when the function is invoked.

View Source
const (
	CB_ON_ITSELF cbTarget = iota
	CB_ON_CELL
	CB_ON_ROW
)

These constants are used when registering a callback to indicate what should be passed to the callback. Eg, a row might have a set of callbacks to update the row itself, and a separate set of callbacks which are invoked upon each cell in the row.

View Source
const APIVersion string = "1.0"

Variables

View Source
var ErrMissingPropertyHolder = errors.New("tabular: missing item upon which to set a property")

ErrMissingPropertyHolder is returned if you call SetProperty upon a nil object.

View Source
var LinkerSpecifiedVersion string

We are a library, not a top-level binary, so we can't depend upon any particular top-level linker action specifying versions. That said, we can provide hooks for applications to cooperate, if they so choose.

*Clients* please consider invoking the `.version` shell-script inside this repo and passing it to the Go linker. See github.com/philpennock/character for an example.

Functions

func NoProperty

func NoProperty() propertySet

func Versions

func Versions() []string

Types

type ATable

type ATable struct {
	*ErrorContainer
	// contains filtered or unexported fields
}

An ATable is the top-level container for a grid in tabular. You should not be declaring fields to be of type ATable; instead, use the Table interface.

An ATable consists of rows of cells; there may also be a header row which defines names for columns. An ATable, a Column, a Row and a Cell can all contain both Properties, and callbacks for updating properties. The callbacks can each individually be registered to activate upon object addition or upon object rendering. Property Callbacks can take various child objects. For instance, a table-level callback upon cells might update a namespaced Property which holds the cell's rendered width when emitted as characters for terminal display.

func New

func New() *ATable

New creates a new empty ATable, which satisfies Table.

func (*ATable) AddHeaders

func (t *ATable) AddHeaders(items ...interface{}) Table

AddHeaders creates a header-row from the passed items and sets it as the table's header row. The table is returned.

func (*ATable) AddRow

func (t *ATable) AddRow(row *Row) Table

AddRow adds a *Row to the *ATable, returning the table to allow for chaining. Any errors accumulate in the table. Any existing errors in the row become table errors.

func (*ATable) AddRowItems

func (t *ATable) AddRowItems(items ...interface{}) Table

AddRowItems creates a row from the passed items and adds it to the table, returning the table for chaining.

func (*ATable) AddSeparator

func (t *ATable) AddSeparator() Table

AddSeparator adds a rule to the table.

func (*ATable) AllRows

func (t *ATable) AllRows() []*Row

AllRows returns an iterable of rows.

func (*ATable) AppendNewRow

func (t *ATable) AppendNewRow() *Row

AppendNewRow creates a new row sized for the table (per NewRowSizedFor) and adds it to the Table, returning the row.

func (*ATable) CellAt

func (t *ATable) CellAt(loc CellLocation) (*Cell, error)

CellAt returns a pointer to the cell found at the given row and column coordinates, where the top-left item is 1,1.

func (*ATable) Column

func (t *ATable) Column(n int) *column

Column returns a representation of a given column in the table. Column counting starts at 1. Providing an invalid column count returns nil. Column 0 also exists but is the implicit defaults column, letting you set default column properties and then override for other columns.

func (*ATable) GetProperty

func (pi *ATable) GetProperty(key interface{}) interface{}

GetProperty returns the property stored for the given key. If no such property has been stored, then nil will be returned. Thus we can't tell the difference between "nil stored" and "nothing stored", thus property storage is free to treat storing "nil" as "remove".

func (*ATable) GoString

func (t *ATable) GoString() string

func (*ATable) Headers

func (t *ATable) Headers() []Cell

Headers returns the headers of a table, as Cells TODO v2: what if we have multiple header rows? How does this change?

func (*ATable) InvokeRenderCallbacks

func (t *ATable) InvokeRenderCallbacks()

InvokeRenderCallbacks is used by rendering packages to trigger a table-wide invocation of render-time callbacks.

We first invoke pre-cell callbacks going: table->column->row->cell We then invoke regular render callbacks cell->row->column->table

func (*ATable) NColumns

func (t *ATable) NColumns() int

NColumns says how many columns are in the table

func (*ATable) NRows

func (t *ATable) NRows() int

NRows says how many rows are in the table; separators count

func (*ATable) NewRowSizedFor

func (t *ATable) NewRowSizedFor() *Row

NewRowSizedFor creates a new Row sized for the given table which it is called upon, assuming that NColumns() is usefully available.

func (*ATable) RegisterPropertyCallback

func (tb *ATable) RegisterPropertyCallback(
	owner PropertyOwner,
	when callbackTime,
	target cbTarget,
	theNewCallback PropertyCallback,
) error

RegisterPropertyCallback is used to register your object, which has the UpdateProperties method available, upon something within the table, to be invoked against something (else, perhaps), at a specified time.

func (*ATable) SetProperty

func (pi *ATable) SetProperty(key, value interface{}) error

SetProperty sets the value stored for a given key, removing any other values stored for that key first. If the value is nil then no value will be stored.

type AnonFielder

type AnonFielder interface {
	AnonFields() []interface{}
}

AnonFielder to avoid caller having to iterate an []interface{} to construct strings; we then just do cell breakdown on each.

type Cell

type Cell struct {
	// contains filtered or unexported fields
}

A Cell is one item in a table; it holds an object and fields calculated from it. If the object added is mutated after addition, it is the mutator's responsibility to call Update.

func NewCell

func NewCell(object interface{}) Cell

NewCell creates a Cell, handling object rendering at init time. TODO: handle rune as rune, or as int? Any special flags to use?

func (*Cell) Empty added in v1.1.0

func (c *Cell) Empty() bool

Empty returns true if the cell is "empty", whatever that might mean. This includes nothing stored, a cell of nil, or an empty string.

func (*Cell) GetProperty

func (pi *Cell) GetProperty(key interface{}) interface{}

GetProperty returns the property stored for the given key. If no such property has been stored, then nil will be returned. Thus we can't tell the difference between "nil stored" and "nothing stored", thus property storage is free to treat storing "nil" as "remove".

func (*Cell) GoString

func (cell *Cell) GoString() string

func (Cell) Height

func (c Cell) Height() int

Height returns the height of a cell; usually this is the number of lines in the string representation of the object stored in the cell, as returned by Lines, but an object which has a Height method will override this.

func (Cell) Item

func (c Cell) Item() interface{}

Item returns the object stored inside a cell.

func (Cell) Lines

func (c Cell) Lines() []string

Lines returns the string representation of the content of a cell, as a splice of strings, one per line, without newlines; if the string has a final \n then there will NOT be an extra empty in the result for the "empty" final segment.

func (Cell) Location

func (c Cell) Location() (loc CellLocation)

Location determines the current location of a cell

func (*Cell) SetProperty

func (pi *Cell) SetProperty(key, value interface{}) error

SetProperty sets the value stored for a given key, removing any other values stored for that key first. If the value is nil then no value will be stored.

func (Cell) String

func (c Cell) String() string

String returns some string representation of the content of a cell.

func (Cell) TerminalCellWidth

func (c Cell) TerminalCellWidth() int

TerminalCellWidth returns the number of terminal cells which we believe are necessary to render the contents of the object stored in the cell. This is overriden by a TerminalCellWidth method on the object being stored. To a first approximation, this is how many runes are in a cell, but we handle combining characters, wide characters, etc.

func (*Cell) Update

func (c *Cell) Update()

Update changes metadata to reflect the current state of the object stored in a cell.

type CellLocation

type CellLocation struct {
	Row    int
	Column int
}

We measure from 1:1 so that 0:0 means "not initialized" and if either x or y is zero in a cell, we know the data is invalid. For some contexts, one or the other might be zero, to describe an entire row or column or just unknown.

type ErrorContainer

type ErrorContainer struct {
	// contains filtered or unexported fields
}

ErrorContainer holds a list of errors. The tabular package uses a batched error model, where errors accumulate in the top-level linked object to which an item is being added.

func NewErrorContainer

func NewErrorContainer() *ErrorContainer

NewErrorContainer creates an object which satisfies both ErrorReceiver and ErrorSource.

func (*ErrorContainer) AddError

func (ec *ErrorContainer) AddError(err error)

AddError puts a Golang error object into the container. If the error is nil, this is a no-op.

func (*ErrorContainer) AddErrorList

func (ec *ErrorContainer) AddErrorList(el []error)

AddErrorList takes a list of errors and adds them to the container. Any errors which are nil will be dropped.

func (*ErrorContainer) Errors

func (ec *ErrorContainer) Errors() []error

Errors returns either a non-empty list of errors, or nil.

type ErrorReceiver

type ErrorReceiver interface {
	AddError(error)
}

An ErrorReceiver can accumulate errors for later retrieval.

type ErrorSource

type ErrorSource interface {
	Errors() []error
}

An ErrorSource can be interrogated for errors.

type Fielder

type Fielder interface {
	Fields() []string
}

Fielder for ability to grab "a row" from a type.

type GoStringer

type GoStringer interface {
	GoString() string
}

GoStringer to match fmt's.

type Heighter

type Heighter interface {
	Height() int
}

Heighter lets an object override how tall it is.

type NoSuchCellError

type NoSuchCellError struct {
	Location CellLocation
	// contains filtered or unexported fields
}

NoSuchCellError is returned when a table is asked for a cell at some coordinates which do not exist within the table.

func (NoSuchCellError) Error

func (e NoSuchCellError) Error() string

type PropertyCallback

type PropertyCallback interface {
	UpdateProperties(PropertyOwner) error
}

A PropertyCallback registration is used to update cell properties. Properties are akin to stdlib's "context", in that various users of cells can maintain their own namespaced properties, using a similar API. Examples might include terminal text properties, color, calculation attributes and more. A callback can be registered to be applied to any PropertyOwner, whether cell, row, column or table.

type PropertyOwner

type PropertyOwner interface {
	SetProperty(interface{}, interface{}) error
	GetProperty(interface{}) interface{}
}

PropertyOwner is the high-level interface satisfied by anything which holds metadata in the form of properties.

type Row

type Row struct {
	*ErrorContainer
	// contains filtered or unexported fields
}

A Row represents a row in a Table.

func NewRow

func NewRow() *Row

NewRow creates a new Row.

func NewRowWithCapacity

func NewRowWithCapacity(c int) *Row

NewRowWithCapacity create a new row pre-sized to contain the given number of Cells.

func (*Row) Add

func (r *Row) Add(c Cell) *Row

Add adds one cell to this row, and returns the row for chaining, thus r.Add(c1).Add(c2).Add(c3)

func (*Row) AddError

func (r *Row) AddError(e error)

AddError records that an error has happened when dealing with a row.

func (*Row) Cells

func (r *Row) Cells() []Cell

Cells returns an iterable of the cells in a row. If it returns nil then you have a non-cell row (probably a separator).

func (*Row) GetProperty

func (pi *Row) GetProperty(key interface{}) interface{}

GetProperty returns the property stored for the given key. If no such property has been stored, then nil will be returned. Thus we can't tell the difference between "nil stored" and "nothing stored", thus property storage is free to treat storing "nil" as "remove".

func (*Row) GoString

func (row *Row) GoString() string

func (*Row) IsSeparator

func (r *Row) IsSeparator() bool

IsSeparator is true iff a row is a "separator".

func (*Row) Location

func (r *Row) Location() (loc CellLocation)

Location returns a row's CellLocation where the column is 0

func (*Row) SetProperty

func (pi *Row) SetProperty(key, value interface{}) error

SetProperty sets the value stored for a given key, removing any other values stored for that key first. If the value is nil then no value will be stored.

type Stringer

type Stringer interface {
	String() string
}

Stringer to match fmt's. Embedded newlines will be handled to provide lines.

type Table

type Table interface {
	AddRow(row *Row) Table
	AddSeparator() Table
	NColumns() int
	NRows() int
	Headers() []Cell
	AddHeaders(items ...interface{}) Table
	AllRows() []*Row
	NewRowSizedFor() *Row
	AppendNewRow() *Row
	AddRowItems(items ...interface{}) Table
	CellAt(location CellLocation) (*Cell, error)
	Column(int) *column

	RegisterPropertyCallback(PropertyOwner, callbackTime, cbTarget, PropertyCallback) error
	InvokeRenderCallbacks()

	// Also the other interfaces embedded in ATable:
	ErrorReceiver
	ErrorSource
	PropertyOwner
}

The Table interface is a thin wrapper around the actual *ATable struct, so that methods can all be on the interface and objects which embed an unnamed table can be used as tables.

If you want to create and use a table for pre-canned rendering then look at sub-packages for TextTable, HTMLTable, etc wrappers.

type TerminalCellWidther

type TerminalCellWidther interface {
	TerminalCellWidth() int
}

TerminalCellWidther should be implemented to override tabular's conception of how "wide" a cell's contents are.

Directories

Path Synopsis
The html wrapper provides a means for generating HTML from a tabular table.
The html wrapper provides a means for generating HTML from a tabular table.
Markdown core does not support tables; for the most portable support, assuming HTML is the final target, use the `html` sub-package instead.
Markdown core does not support tables; for the most portable support, assuming HTML is the final target, use the `html` sub-package instead.
decoration
The decoration package provides controls for how text tables are decorated for rendering.
The decoration package provides controls for how text tables are decorated for rendering.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL