flexwriter

package module
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Jan 26, 2025 License: MIT Imports: 11 Imported by: 1

README

Flexwriter

Go Reference

go get github.com/hchargois/flexwriter

Flexwriter arranges rows of data into columns with configurable widths and alignments.

As the name suggests, it implements the CSS flexbox model to define column widths.

If the contents are too long, flexwriter automatically wraps the text over multiple lines. Text containing escape sequences (e.g. color codes) is correctly wrapped.

The output can be decorated with simple column separators or to look like tables.

demo screenshot showing features such as flexed columns, alignments and color support

Basic usage

import "github.com/hchargois/flexwriter"

// by default, the flexwriter will output to standard output; and all
// columns will default to being "shrinkable" columns (i.e. they will match
// their content size if it fits within the output width, but will shrink to
// match the output width if the content is too big to fit on a single line);
// and all columns will be separated by two spaces
writer := flexwriter.New()

// write some data (any non-string will pass through fmt.Sprint)
writer.WriteRow("deep", "thought", "says", ":")
writer.WriteRow("the", "answer", "is", 42)
writer.WriteRow(true, "or", false, "?")

// calling Flush() is required to actually output the rows
writer.Flush()

This will output:

deep  thought  says   :
the   answer   is     42
true  or       false  ?

Here's another example showing how to configure the columns and set a table decorator, and that shows how the Shrinkable columns shrinks to fit in the configured width of the output (70 columns wide):

writer := flexwriter.New()
writer.SetColumns(
    // first column, a Rigid, will not shrink and wrap
    flexwriter.Rigid{},
    // second column will
    flexwriter.Shrinkable{})
writer.SetDecorator(flexwriter.AsciiTableDecorator())
writer.SetWidth(70)

lorem := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "+
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim "+
"ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip"

writer.WriteRow("lorem ipsum says:", lorem)

writer.Flush()

This outputs:

+-------------------+------------------------------------------------+
| lorem ipsum says: | Lorem ipsum dolor sit amet, consectetur        |
|                   | adipiscing elit, sed do eiusmod tempor         |
|                   | incididunt ut labore et dolore magna aliqua.   |
|                   | Ut enim ad minim veniam, quis nostrud          |
|                   | exercitation ullamco laboris nisi ut aliquip   |
+-------------------+------------------------------------------------+

Many more examples can be found in the godoc.

Alternatives

Thanks

Documentation

Overview

Package flexwriter arranges rows of data into columns with configurable widths and alignments.

Flexwriter tries to match a given output size, which by default is the width of the terminal (if the output is the standard output, which is the default, and it is a terminal). Depending on the content and the configuration of the columns, flexwriter will wrap text in multiple lines in order to fit the target output width.

The size of each column is determined by the flexbox algorithm.

The Basis, Grow, and Shrink properties of the flexbox model can be configured for each column by using the Flexbox column type.

Moreover, a few "preconfigured" column types are provided for common use cases:

  • a Rigid is an inflexible column, it is sized depending on its content, regardless of the width of the output;
  • a Flexed is a "proportional" or "absolute" flexed column, expanding to take a specified share of the output width, regardless of the size of its content;
  • a Shrinkable is a column that is the same size as its content if it's small enough, but it can shrink as needed to fit the width of the output if the content is bigger.

These 3 column types are similar to the "flex: none", "flex: N", and "flex: initial" CSS shorthand values, respectively.

A column can also be omitted from the output by using the special Omit column type.

The output can be decorated with simple column separators or to look like a table, and any decorator can be colorized.

Flexwriter correctly supports aligning and wrapping text even if it contains ANSI escape sequences, such as color codes.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	// by default, the flexwriter will output to standard output; and all
	// columns will default to being left-aligned rigids with no maximum width
	// (i.e. they will be exactly as wide as needed to fit their content);
	// and all columns will be separated by two spaces
	writer := flexwriter.New()

	// write some data (any non-string will pass through fmt.Sprint)
	writer.WriteRow("deep", "thought", "says", ":")
	writer.WriteRow("the", "answer", "is", 42)
	writer.WriteRow(true, "or", false, "?")

	// calling Flush() is required to actually output the rows
	writer.Flush()
}
Output:

deep  thought  says   :
the   answer   is     42
true  or       false  ?
Example (Configuration)
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetColumns(
		// the first column will have a width equal to the width of its content,
		// same as the default for a new flexwriter as shown above
		flexwriter.Rigid{},

		// the second column will have a width equal to the width of its content
		// but only if it's less than 10 characters, otherwise it will be 10
		// characters wide and longer content will be wrapped
		flexwriter.Rigid{Max: 10},

		// the third column is a flexed with an implicit weight of 1, so it
		// will take one third of the remaining space
		flexwriter.Flexed{},

		// the fourth column is a right-aligned flexed that will take two thirds
		// of the remaining space
		flexwriter.Flexed{Weight: 2, Align: flexwriter.Right},
	)
	// any additional column will be exactly 5 characters wide and centered
	writer.SetDefaultColumn(flexwriter.Rigid{Min: 5, Max: 5, Align: flexwriter.Center})

	// use a decorator to make columns stand out better
	writer.SetDecorator(flexwriter.GapDecorator{Left: "| ", Gap: " | ", Right: " |"})

	writer.WriteRow(1, "hello", "world", "what's up", "A")
	writer.WriteRow(2, "this text is quite long", "so", "it will wrap", "B")
	writer.WriteRow(3, "I", "like", "bunnies", "C")

	writer.Flush()
}
Output:

| 1 | hello      | world            |                        what's up |   A   |
| 2 | this text  | so               |                     it will wrap |   B   |
|   | is quite   |                  |                                  |       |
|   | long       |                  |                                  |       |
| 3 | I          | like             |                          bunnies |   C   |

Index

Examples

Constants

View Source
const Auto = -1

Auto can be set as the Basis of a Flexbox column to make the basis as large as the content.

Variables

This section is empty.

Functions

This section is empty.

Types

type Alignment

type Alignment int
const (
	Left Alignment = iota
	Center
	Right
)

type Column

type Column interface {
	// contains filtered or unexported methods
}

Column holds the configuration of a column for the flex writer. This interface is sealed, use one of the provided implementations:

type Decorator

type Decorator interface {
	// RowSeparator defines the horizontal separator between rows, as well as
	// before the first row and after the last one. rowIdx will be 0 for the
	// separator before the first row, -1 for the separator after the last row.
	// It will be N for the separator just below the Nth row.
	// If the empty string is returned, rows will not be separated.
	// widths are the widths of the column without any padding.
	RowSeparator(rowIdx int, widths []int) string

	// ColumnSeparator defines the vertical separator between columns.
	// For a given colIdx, the length of the string returned should stay
	// constant for all rows, otherwise the result will not be properly aligned.
	// colIdx starts at 0 for the separator left of the first column, and ends
	// at -1 for the separator right of the last column; otherwise it is N for
	// the separator just to the right of the Nth column.
	// rowIdx starts at 1 for the first row, and ends at -1 for the last row.
	ColumnSeparator(rowIdx, colIdx int) string
}

Decorator is used to decorate the output. It can be used to add spacing between columns, or create borders in order to create a table.

func AsciiTableDecorator

func AsciiTableDecorator() Decorator

AsciiTableDecorator creates a table with ASCII characters + and - for an old-school look.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetDecorator(flexwriter.AsciiTableDecorator())

	writer.WriteRow("a", "nice", "table")
	writer.WriteRow("with", "a classic", "look")

	writer.Flush()
}
Output:

+------+-----------+-------+
| a    | nice      | table |
+------+-----------+-------+
| with | a classic | look  |
+------+-----------+-------+

func BoxDrawingTableDecorator

func BoxDrawingTableDecorator() Decorator

BoxDrawingTableDecorator creates a table with Unicode box drawing characters.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetDecorator(flexwriter.BoxDrawingTableDecorator())

	writer.WriteRow("a", "nice", "table")
	writer.WriteRow("with", "a modern", "look")

	writer.Flush()
}
Output:

┌──────┬──────────┬───────┐
│ a    │ nice     │ table │
├──────┼──────────┼───────┤
│ with │ a modern │ look  │
└──────┴──────────┴───────┘

func ColorizeDecorator

func ColorizeDecorator(parent Decorator, color *color.Color) Decorator

ColorizeDecorator wraps a decorator to make it colorful.

type Flexbox added in v1.2.0

type Flexbox struct {
	// Basis is the flexbox basis, i.e. the initial size of the column before
	// it grows or shrinks. Use the constant Auto (or -1) to make the basis
	// equal to the content size.
	Basis int
	// Grow is the flexbox grow weight.
	Grow int
	// Shrink is the flexbox shrink weight.
	Shrink int
	// Min is the minimum width of the column. If 0, it defaults to the
	// "min content" size, i.e. the size of the longest word in the content.
	Min int
	// Max is the maximum width of the column, if the content is longer it will
	// be wrapped. If Max is 0, then there is no maximum width.
	Max int
	// Align is the alignment of the content within the column; default is left.
	Align Alignment
}

Flexbox columns allow you to specify the exact flex attributes as in CSS flexbox; however note that default values are all zero, there are no "smart" defaults as when using the "flex: ..." CSS syntax.

type Flexed

type Flexed struct {
	// Weight is the grow weight of the column; if 0 or less, it defaults to 1.
	Weight int
	// Min is the minimum width of the column. If 0, it defaults to the
	// "min content" size, i.e. the size of the longest word in the content.
	Min int
	// Max is the maximum width of the column, if the content is longer it will
	// be wrapped. If Max is 0, then there is no maximum width.
	Max int
	// Align is the alignment of the content within the column; default is left.
	Align Alignment
}

Flexed columns take a size proportional to their weight (vs all other flexed columns weights) within the available width, regardless of the size of their content.

A Flexed column is similar to a "flex: N" column in CSS.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetColumns(
		flexwriter.Flexed{},
		flexwriter.Flexed{Weight: 2},
		flexwriter.Flexed{Weight: 3},
	)
	writer.SetDecorator(flexwriter.GapDecorator{Left: "| ", Gap: " | ", Right: " |"})

	writer.WriteRow("one sixth,", "one third,", "and half of the output width")

	writer.Flush()
}
Output:

| one sixth,  | one third,              | and half of the output width         |

type GapDecorator

type GapDecorator struct {
	Gap   string
	Left  string
	Right string
}

GapDecorator is a simple decorator that adds a fixed gap between each column, as well as a left gap (before the left-most column) and a right gap (after the right-most column).

func (GapDecorator) ColumnSeparator

func (d GapDecorator) ColumnSeparator(_, colIdx int) string

func (GapDecorator) RowSeparator

func (d GapDecorator) RowSeparator(rowIdx int, widths []int) string

type Omit added in v1.1.0

type Omit struct{}

Omit columns will not appear in the output.

This can be very useful in cases where it's simpler to modify the columns configuration than the row data.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetColumns(
		flexwriter.Rigid{},
		flexwriter.Omit{}, // the second column will not appear in the output
		flexwriter.Rigid{},
	)
	// Omit can also be specified for the default column; here that means that
	// all columns after the 3rd one will be omitted too
	writer.SetDefaultColumn(flexwriter.Omit{})

	writer.WriteRow("A", "B", "C", "D", "E")
	writer.WriteRow("F", "G", "H", "I", "J")
	writer.Flush()
}
Output:

A  C
F  H

type Rigid

type Rigid struct {
	// Min is the minimum width of the column. If the content is smaller, the
	// column will be padded.
	Min int
	// Max is the maximum width of the column, if the content is longer it will
	// be wrapped. If Max is 0, then there is no maximum width.
	Max int
	// Align is the alignment of the content within the column; default is left.
	Align Alignment
}

Rigid columns try to match the size of their content, as long as it is between Min and Max, regardless of the width of the output.

By setting Min and Max to the same value, you can create a column of a fixed width.

A Rigid is actually just a shortcut for a Flexbox with an Auto Basis, and Grow and Shrink factors of 0. This is similar to a "flex: none" in CSS.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetColumns(
		flexwriter.Rigid{},
		flexwriter.Rigid{Min: 20},
		flexwriter.Rigid{Max: 20},
	)
	writer.SetDecorator(flexwriter.GapDecorator{Left: "| ", Gap: " | ", Right: " |"})

	writer.WriteRow("sized to content", "min 20 wide", "maximum of 20 characters, longer content will wrap")

	writer.Flush()
}
Output:

| sized to content | min 20 wide          | maximum of 20        |
|                  |                      | characters, longer   |
|                  |                      | content will wrap    |

type Shrinkable added in v1.2.0

type Shrinkable struct {
	// Weight is the shrink weight of the column; if 0 or less, it defaults to 1.
	Weight int
	// Min is the minimum width of the column. If the content is smaller, the
	// column will be padded.
	Min int
	// Max is the maximum width of the column, if the content is longer it will
	// be wrapped. If Max is 0, then there is no maximum width.
	Max int
	// Align is the alignment of the content within the column; default is left.
	Align Alignment
}

Shrinkable columns try to match the size of their content, but if the width of the output is too small, they can shrink up to their Min width.

A Shrinkable is actually just a shortcut for a Flexbox with an Auto Basis, a Grow of 0 and a Shrinkable of the given Weight, or 1 if 0/unset. This is similar to a "flex: initial" in CSS.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetDefaultColumn(flexwriter.Shrinkable{})
	writer.SetDecorator(flexwriter.AsciiTableDecorator())

	writer.WriteRow("just", "a few", "short columns")
	writer.WriteRow("no", "need", "to wrap")
	writer.WriteRow("columns stay", "as small", "as required")
	writer.Flush()

	writer.WriteRow(
		"but now",
		"the row is much longer",
		"and it can't all fit in a single line of the output",
		"so the columns will shrink and contents will be wrapped")
	writer.Flush()
}
Output:

+--------------+----------+---------------+
| just         | a few    | short columns |
+--------------+----------+---------------+
| no           | need     | to wrap       |
+--------------+----------+---------------+
| columns stay | as small | as required   |
+--------------+----------+---------------+
+------+-------------+----------------------------+----------------------------+
| but  | the row is  | and it can't all fit in a  | so the columns will shrink |
| now  | much longer | single line of the output  | and contents will be       |
|      |             |                            | wrapped                    |
+------+-------------+----------------------------+----------------------------+

type TableDecorator

type TableDecorator struct {
	TopIntersections    [3]string // (left, middle, right) top intersections
	MiddleIntersections [3]string // etc.
	BottomIntersections [3]string
	VertBorders         [3]string
	HorizBorders        [3]string // (top, middle, bottom), must be of width 1, will be repeated as needed
}

TableDecorator is a decorator that creates a table with configurable borders and intersections.

func (TableDecorator) ColumnSeparator

func (d TableDecorator) ColumnSeparator(_, colIdx int) string

func (TableDecorator) RowSeparator

func (d TableDecorator) RowSeparator(rowIdx int, widths []int) string

type Writer

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

func New

func New() *Writer

New creates a new flex writer with the default configuration:

  • write to standard output
  • a target width equal to the width of the standard output if it's a terminal, otherwise 80
  • a gap of 2 spaces between columns, none on the sides
  • a default column setting of a left-aligned Shrinkable column

func (*Writer) Flush

func (w *Writer) Flush() error

Flush writes the contents of the internal buffer to the output. This also resets the internal buffer and the associated column widths.

func (*Writer) SetColumns

func (w *Writer) SetColumns(cols ...Column)

SetColumns sets the configuration for the first len(cols) columns.

func (*Writer) SetDecorator

func (w *Writer) SetDecorator(deco Decorator)

SetDecorator sets the decorator for this flex writer.

func (*Writer) SetDefaultColumn

func (w *Writer) SetDefaultColumn(col Column)

SetDefaultColumn sets the default column configuration. This configuration is used when more columns are written than are configured with Writer.SetColumns.

Example
package main

import (
	"github.com/hchargois/flexwriter"
)

func main() {
	writer := flexwriter.New()
	writer.SetColumns(flexwriter.Rigid{})
	writer.SetDefaultColumn(flexwriter.Flexed{})

	writer.WriteRow(
		"first column is sized to content",
		"all other columns",
		"will share the rest of the output width",
		"equally and wrap as needed.")

	writer.Flush()
}
Output:

first column is sized to content  all other       will share the  equally and
                                  columns         rest of the     wrap as
                                                  output width    needed.

func (*Writer) SetOutput

func (w *Writer) SetOutput(out io.Writer)

SetOutput sets the output writer for this flex writer. If the output is a terminal, the width of the flex writer is automatically configured to be the width of the terminal. If auto-detection is not desired, call Writer.SetWidth after SetOutput.

func (*Writer) SetWidth

func (w *Writer) SetWidth(width int)

SetWidth sets the target width of the output; note however that depending on the columns min width constraints, this may not be honored. The width is also set when the output is set with Writer.SetOutput and the output is a terminal. If you want to force a width even if the output is a terminal, call SetWidth after Writer.SetOutput.

func (*Writer) Write

func (w *Writer) Write(b []byte) (int, error)

Write writes row(s) to the flex writer; rows are delimited by a newline (`\n`) and within a row the columns are delimited by a tab (`\t`). This is mostly compatible with the text/tabwriter package. If possible, use Writer.WriteRow instead.

This method only appends to an internal buffer and thus never returns an error. Call Writer.Flush to write the buffer to the output.

You should not alternate between calls to Write and Writer.WriteRow, unless you call Writer.Flush in between.

func (*Writer) WriteRow

func (w *Writer) WriteRow(cells ...any)

WriteRow writes a single row of cells to the flex writer. If the cells are not strings, they are converted to strings using fmt.Sprint.

This is the recommended method for writing data to the flex writer.

This method only appends to an internal buffer; call Writer.Flush to write the buffer to the output.

You should not alternate between calls to Writer.Write and WriteRow, unless you call Writer.Flush in between.

Directories

Path Synopsis
Package flex implements a simplified flexbox layout algorithm.
Package flex implements a simplified flexbox layout algorithm.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳