pprint

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 12, 2021 License: MIT Imports: 8 Imported by: 1

README

Overview

Pprint is a library to typeset output with auto-width padding. It's implemented using a tree and has directory-like context. Supports typesetting or sorting per directory context.

  • Auto column width calculation
  • Fixed width, left alignment, no padding
  • Sort on raw value
  • Folder context: different typesets or sorting per folder

Concepts

Pprint is built on a tree structure.

Each tree node maintains its own "folder layout", which is represented by ColumnSchema, for its child ndoes.

A ColumnSchema can be shared among entire tree.

Each node hold a Row contains input data and with a reference to an instance of ColumnSchema.

Installing

go get -u github.com/adios/pprint

Next, include pprint in your application:

import "github.com/adios/pprint"

Examples

package main

import (
	"fmt"
	"time"
	
	pp "github.com/adios/pprint"
)

func main() {
	var (
		pt = func(date string) time.Time {
			t, _ := time.Parse("2006-01-02", date)
			return t
		}
		data = [][]interface{}{
			{21196, "Keep On Truckin'", pt("1999-05-17"), "ahote glowtusks", 9.75},
			{-1162, "Cry Wolf", pt("2007-10-16"), "adahy windshot", 4.22},
			{-1248, "Needle In a Haystack", pt("1988-09-06"), "shikpa longmoon", 0.7},
			{50994, "Greased Lightning", pt("1989-06-04"), "helushka emberhair", 2.72},
			{80640, "Let Her Rip", pt("1981-01-13"), "geashkoo grassdream", 1.6},
			{50997, "Up In Arms", pt("1981-01-13"), "oonnak hardrage", 0.58},
		}
	)

	n := pp.NewNode()
	for _, row := range data {
		_, err := n.Push(row...)
		if err != nil {
			panic(err)
		}
	}
	// All push are auto-width by default
	pp.Print(n)

	fmt.Println("===")

	// Or using a customized a typeset
	n = pp.NewNode(
		pp.WithColumns(
			pp.NewColumn(),
			pp.NewColumn(pp.WithLeftAlignment()),
			pp.NewColumn(),
			pp.NewColumn(pp.WithWidth(24)),
			// Width 0 means no padding
			pp.NewColumn(pp.WithWidth(0)),
		),
	)
	for _, row := range data {
		n.Push(row...)
	}
	pp.Print(n, pp.WithColSep("|"))

	fmt.Println("===")

	// Use with a folder-like context
	n = pp.NewNode()
	// Data 0, 1, 2 are under node n
	n.Push(data[0]...)
	m, _ := n.Push(data[1]...)
	n.Push(data[2]...)

	// Data 3, 4, 5 are under node m, which is the position of data 1
	m.Push(data[3]...)
	m.Push(data[4]...)
	m.Push(data[5]...)

	// Output order would be 0, 1, 3, 4, 5, 2
	pp.Print(n)
}

Output:

Output:
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58
===
21196|Keep On Truckin'    |1999-05-17 00:00:00 +0000 UTC|         ahote glowtusks|9.75
-1162|Cry Wolf            |2007-10-16 00:00:00 +0000 UTC|          adahy windshot|4.22
-1248|Needle In a Haystack|1988-09-06 00:00:00 +0000 UTC|         shikpa longmoon|0.7
50994|Greased Lightning   |1989-06-04 00:00:00 +0000 UTC|      helushka emberhair|2.72
80640|Let Her Rip         |1981-01-13 00:00:00 +0000 UTC|     geashkoo grassdream|1.6
50997|Up In Arms          |1981-01-13 00:00:00 +0000 UTC|         oonnak hardrage|0.58
===
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7

Documentation

Overview

Package pprint is a library to typeset output with auto-width padding. It's implemented using a tree and has directory-like context. Supports typesetting or sorting per directory context.

Example (DefaultUsage)
var (
	pt = func(date string) time.Time {
		t, _ := time.Parse("2006-01-02", date)
		return t
	}
	data = [][]interface{}{
		{21196, "Keep On Truckin'", pt("1999-05-17"), "ahote glowtusks", 9.75},
		{-1162, "Cry Wolf", pt("2007-10-16"), "adahy windshot", 4.22},
		{-1248, "Needle In a Haystack", pt("1988-09-06"), "shikpa longmoon", 0.7},
		{50994, "Greased Lightning", pt("1989-06-04"), "helushka emberhair", 2.72},
		{80640, "Let Her Rip", pt("1981-01-13"), "geashkoo grassdream", 1.6},
		{50997, "Up In Arms", pt("1981-01-13"), "oonnak hardrage", 0.58},
	}
)

n := NewNode()
for _, row := range data {
	_, err := n.Push(row...)
	if err != nil {
		panic(err)
	}
}
// All push are auto-width by default
Print(n)

fmt.Println("===")

// Or using a customized a typeset
n = NewNode(
	WithColumns(
		NewColumn(),
		NewColumn(WithLeftAlignment()),
		NewColumn(),
		NewColumn(WithWidth(24)),
		// Width 0 means no padding
		NewColumn(WithWidth(0)),
	),
)
for _, row := range data {
	n.Push(row...)
}
Print(n, WithColSep("|"))

fmt.Println("===")

// Use with a folder-like context
n = NewNode()
// Data 0, 1, 2 are under node n
n.Push(data[0]...)
m, _ := n.Push(data[1]...)
n.Push(data[2]...)

// Data 3, 4, 5 are under node m, which is the position of data 1
m.Push(data[3]...)
m.Push(data[4]...)
m.Push(data[5]...)

// Output order would be 0, 1, 3, 4, 5, 2
Print(n)
Output:
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58
===
21196|Keep On Truckin'    |1999-05-17 00:00:00 +0000 UTC|         ahote glowtusks|9.75
-1162|Cry Wolf            |2007-10-16 00:00:00 +0000 UTC|          adahy windshot|4.22
-1248|Needle In a Haystack|1988-09-06 00:00:00 +0000 UTC|         shikpa longmoon|0.7
50994|Greased Lightning   |1989-06-04 00:00:00 +0000 UTC|      helushka emberhair|2.72
80640|Let Her Rip         |1981-01-13 00:00:00 +0000 UTC|     geashkoo grassdream|1.6
50997|Up In Arms          |1981-01-13 00:00:00 +0000 UTC|         oonnak hardrage|0.58
===
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7
Example (DifferentLayouts)
var (
	pt = func(date string) time.Time {
		t, _ := time.Parse("2006-01-02", date)
		return t
	}
	data = [][]interface{}{
		{21196, "Keep On Truckin'", pt("1999-05-17"), "ahote glowtusks", 9.75},
		{-1162, "Cry Wolf", pt("2007-10-16"), "adahy windshot", 4.22},
		{-1248, "Needle In a Haystack", pt("1988-09-06"), "shikpa longmoon", 0.7},
		{5099, "Greased Lightning", pt("1989-06-04"), "helushka emberhair", 2.72},
		{8064, "Let Her Rip", pt("1981-01-13"), "geashkoo grassdream", 1.6},
		{5099, "Up In Arms", pt("1981-01-13"), "oonnak hardrage", 0.58},
	}
)

n := NewNode()
// These 3 rows are under n, and share the same auto-width schema.
n.Push(data[0]...)
m, _ := n.Push(data[1]...)
n.Push(data[2]...)

// By default, Push() creates nodes that always share the same schema, even for different node level.
// We explictly create a typesetted row, and PushRow() it to the node m.
r := NewRow(
	WithRowData(data[3]...),
	WithRowColumns(
		NewColumn(WithLeftAlignment()),
		NewColumn(WithWidth(24)),
		NewColumn(),
		NewColumn(WithLeftAlignment()),
		NewColumn(WithWidth(0)),
	),
)

// Push it to the 2nd to create a new context (just like a new folder)
// Now data[3] is under node m.
_, err := m.PushRow(r)
if err != nil {
	panic(err)
}

// These 2 rows are also under node m and data 3, 4, 5 share the same schema.
m.Push(data[4]...)
m.Push(data[5]...)

Print(n, WithColSep("|"))
Output:
21196|    Keep On Truckin'|1999-05-17 00:00:00 +0000 UTC|ahote glowtusks|9.75
-1162|            Cry Wolf|2007-10-16 00:00:00 +0000 UTC| adahy windshot|4.22
5099|       Greased Lightning|1989-06-04 00:00:00 +0000 UTC|helushka emberhair |2.72
8064|             Let Her Rip|1981-01-13 00:00:00 +0000 UTC|geashkoo grassdream|1.6
5099|              Up In Arms|1981-01-13 00:00:00 +0000 UTC|oonnak hardrage    |0.58
-1248|Needle In a Haystack|1988-09-06 00:00:00 +0000 UTC|shikpa longmoon| 0.7
Example (Sort)
var (
	pt = func(date string) time.Time {
		t, _ := time.Parse("2006-01-02", date)
		return t
	}
	data = [][]interface{}{
		{21196, "Keep On Truckin'", pt("1999-05-17"), "ahote glowtusks", 9.75},
		{-1162, "Cry Wolf", pt("2007-10-16"), "adahy windshot", 4.22},
		{-1248, "Needle In a Haystack", pt("1988-09-06"), "shikpa longmoon", 0.7},
		{50994, "Greased Lightning", pt("1989-06-04"), "helushka emberhair", 2.72},
		{80640, "Let Her Rip", pt("1981-01-13"), "geashkoo grassdream", 1.6},
		{50997, "Up In Arms", pt("1981-01-13"), "oonnak hardrage", 0.58},
	}
)

n := NewNode()
for _, row := range data {
	n.Push(row...)
}

// Sort on 3rd column, note that it compares raw value, not the string representation.
n.Sort(2)

Print(n)
Output:
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
Example (SortRecursively)
var (
	pt = func(date string) time.Time {
		t, _ := time.Parse("2006-01-02", date)
		return t
	}
	data = [][]interface{}{
		{21196, "Keep On Truckin'", pt("1999-05-17"), "ahote glowtusks", 9.75},
		{-1162, "Cry Wolf", pt("2007-10-16"), "adahy windshot", 4.22},
		{-1248, "Needle In a Haystack", pt("1988-09-06"), "shikpa longmoon", 0.7},
		{50994, "Greased Lightning", pt("1989-06-04"), "helushka emberhair", 2.72},
		{80640, "Let Her Rip", pt("1981-01-13"), "geashkoo grassdream", 1.6},
		{50997, "Up In Arms", pt("1981-01-13"), "oonnak hardrage", 0.58},
	}
)

n := NewNode()
// These 3 rows are under n
n.Push(data[0]...)
n.Push(data[1]...)
m, _ := n.Push(data[2]...)

// These 3 rows are under m
m.Push(data[3]...)
m.Push(data[4]...)
m.Push(data[5]...)

// Sort n affects only data 0, 1, 2. So you can sort differently per node level.
n.Sort(0)
m.Sort(0, WithDescending())

Print(n)

fmt.Println("===")

// Or recursively sort by Walk()
n.Sort(1)
n.Walk(func(c *Node) {
	c.Sort(1)
})

Print(n)
Output:
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
===
-1162             Cry Wolf 2007-10-16 00:00:00 +0000 UTC      adahy windshot 4.22
21196     Keep On Truckin' 1999-05-17 00:00:00 +0000 UTC     ahote glowtusks 9.75
-1248 Needle In a Haystack 1988-09-06 00:00:00 +0000 UTC     shikpa longmoon  0.7
50994    Greased Lightning 1989-06-04 00:00:00 +0000 UTC  helushka emberhair 2.72
80640          Let Her Rip 1981-01-13 00:00:00 +0000 UTC geashkoo grassdream  1.6
50997           Up In Arms 1981-01-13 00:00:00 +0000 UTC     oonnak hardrage 0.58

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MustToString

func MustToString(a interface{}) string

Converts anything to a string. The function itself handles the common types including: fmt.Stringer, string, []byte, uint, int and nil. It passes anything else to the fmt.Sprintf to get the string representation of that value. It is used when initializing a Row instance.

func Print

func Print(n *Node, opts ...PrintingOpt)

Convenient helper to run a Printing instance.

Types

type CmpFn

type CmpFn func(a, b interface{}) bool

A comparator looks like this:

func(a, b interface{}) {
  return a.(int) < b.(int)
}

It is passed to generate a sort.Less() function.

func MatchCmp

func MatchCmp(a interface{}) CmpFn

The default CmpFn matcher used in createSortableOn(). It uses type switch to find the type it can compare. It currently supports only types of string, int or time.Time.

type Column

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

Stores alignment and width.

func NewColumn

func NewColumn(opts ...ColumnOpt) Column

Returns a Column instance. Column options are:

WithWidth(int): by default all columns are auto-width. Set to fix-width. WithWidth(20) is translated to "%20s".

WithLeftAlignment(): set to pad to the right. For example: WithWidth(20), WithLeftAlignment() = "%-20s".

func (Column) String

func (c Column) String() string

Turns current column into a format string, e.g.: "%3s", "%-5s".

type ColumnOpt

type ColumnOpt func(*Column)

func WithLeftAlignment

func WithLeftAlignment() ColumnOpt

Set to pad to the right. For example: WithWidth(20), WithLeftAlignment() = "%-20s".

func WithWidth

func WithWidth(w int) ColumnOpt

By default all columns are auto-width. Set to fix-width. WithWidth(20) is translated to "%20s".

type ColumnSchema

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

Defines how many columns in a row and their corresponding Column data.

func NewSchema

func NewSchema(c ...Column) *ColumnSchema

func NewSchemaFrom

func NewSchemaFrom(fields []interface{}) *ColumnSchema

Creates a column schema instance with N columns. N is the length of input fields.

type Node

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

Tree node.

func NewNode

func NewNode(opts ...NodeOpt) *Node

Returns a pointer to a Node instance. Node options are:

WithRow(*Row): creates a node with provided Row instance.

WithSchema(*ColumnSchema): to inherit the schema from an existing row or node to be applied to all of its children.

WithColumns(...Column): to create a node with provided column schema to be applied to all of its children.

func (*Node) EachNode

func (n *Node) EachNode(fn func(*Node))

Traverses receiver's children. Use Walk() to traverse descendants.

func (*Node) IsNotRoot

func (n *Node) IsNotRoot() bool

Returns true if receiver has parent.

func (*Node) NodesCount

func (n *Node) NodesCount() int

Returns receiver's child count.

func (*Node) Parent

func (n *Node) Parent() *Node

Returns receiver's parent.

func (*Node) Push

func (n *Node) Push(a ...interface{}) (newNode *Node, err error)

Creates a node to store the inputs and makes it a child of the current receiver. Returns a pointer to the created node and any error encountered.

The parent of the receiver applys its schema to the inputs being pushed. That is, all the column numbers, width and padding of the inputs inherits the schema that is shared among entire tree.

If the reciever has no schema, i.e. a root node without children. A schema instance will be generated based on the first pushed input. The self-generated schema instance is always of columns with auto-width and right-alignment.

Use PushRow() or PushNode() to create tree containing different schemas per node.

The column (fields) amount of the input doesn't have to be the same with receiver's. It will be enlarged (with empty string) or shrinked to fit the receiver's schema.

func (*Node) PushNode

func (n *Node) PushNode(in *Node) (inMutated *Node, err error)

Makes incoming node become a child of the receiver. Returns a pointer to the mutated incoming node and any error encountered.

Consistency is maintained by comparing each other's node.schema and node.row.schema. Receiver(A) accepts incoming node(B) if:

1. A has no node schema, B contains a Row instance. 2. A has no node schema, B contains no Row instance, but B has node schema (tree root) 3. A has node schema, B contains no Row instance. (tree root) 4. A has node schema, B contains a Row instance with the schema which is exactly A's node schema.

BUG(adios): Use carefully, no loop detections.

func (*Node) PushRow

func (n *Node) PushRow(r *Row) (newNode *Node, err error)

Accepts a customized Row. Returns a pointer to the created node and any error encountered.

func (*Node) Row

func (n *Node) Row() *Row

Returns the attached Row instance of current receiver.

func (*Node) Schema

func (n *Node) Schema() *ColumnSchema

Returns the schema instance of current receiver.

func (*Node) Sort

func (n *Node) Sort(col int, opts ...SortOpt) error

Sort receiver's child nodes (that contain rows) on the given column of that node's row. Accepts a column index starting from 0. Returns any error encountered.

It uses stable sort to compare the raw value of the specified column field. Sort on values with non identical type returns an error. Sort on values with no type comparators returns an error.

Note that it doesn't sort descendants.

Sorting options are:

WithDescending(): default is ascending.

WithCmpMatchers(...func(a interface{}) CmpFn): to sort more types. Builtins: int, string and time.Time.

func (*Node) String

func (n *Node) String() string

Collects each descendant's String() and prints with default options.

func (*Node) Walk

func (n *Node) Walk(fn func(*Node))

Traverses receiver's descendants.

type NodeOpt

type NodeOpt func(*Node)

func WithColumns

func WithColumns(c ...Column) NodeOpt

To create a node with provided column schema to be applied to all of its children.

func WithRow

func WithRow(r *Row) NodeOpt

Creates a node with provided Row instance.

func WithSchema

func WithSchema(s *ColumnSchema) NodeOpt

To inherit the schema from an existing row or node to be applied to all of its children.

type Printing

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

Algorithm for printing.

func NewPrinting

func NewPrinting(opts ...PrintingOpt) *Printing

Printing options are:

WithColSep(string): set column separator (field separator). Defaults to " ".

WithLineBrk(string): set line break. Defaults to "\n".

WithWriter(io.Writer): set writer. Defaults to os.Stdout.

func (*Printing) RunNode

func (p *Printing) RunNode(n *Node)

Do nothing if n is nil.

func (*Printing) RunRow

func (p *Printing) RunRow(r *Row)

Do nothing if r is nil or there is no columns to print.

type PrintingOpt

type PrintingOpt func(*Printing)

func WithColSep

func WithColSep(sep string) PrintingOpt

Set column separator (field separator). Defaults to " ".

func WithLineBrk

func WithLineBrk(brk string) PrintingOpt

Set line break. Defaults to "\n".

func WithWriter

func WithWriter(w io.Writer) PrintingOpt

Set writer. Defaults to os.Stdout.

type Row

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

Stores both raw input values and their string representations.

func NewRow

func NewRow(opts ...RowOpt) *Row

Returns a pointer to a Row instance. Row options are:

WithRowSchema(*ColumnSchema): to inherit the schema from an existing row or node.

WithRowColumns(...Column): to create a row with provided column schema.

WithData(...interface{}): set data to the row.

func (*Row) EachFmtStr

func (r *Row) EachFmtStr(fn func(string))

Traverses format strings with String() on each Column instance.

func (*Row) FmtArgs

func (r *Row) FmtArgs() []interface{}

Returns the string slice that stores the string representations of raw values.

func (*Row) Schema

func (r *Row) Schema() *ColumnSchema

func (*Row) String

func (r *Row) String() string

Returns a string by calling fmt.Fprintf() on fmtStr and fmtArgs.

type RowOpt

type RowOpt func(*Row)

func WithRowColumns

func WithRowColumns(c ...Column) RowOpt

To create a row with provided column schema.

func WithRowData

func WithRowData(a ...interface{}) RowOpt

Set data to the row.

func WithRowSchema

func WithRowSchema(s *ColumnSchema) RowOpt

To inherit the schema from an existing row or node.

type SortOpt

type SortOpt func(*sortable)

func WithCmpMatchers

func WithCmpMatchers(m ...func(interface{}) CmpFn) SortOpt

Multiple matcher functions can be provided as input. The method executes them in order until a matcher can handle the current comparing type. A finder should look like this:

func(a interface{}) {
  // you can do type switch on a to find a exact type of the input value,
  // or simply ignores it if you know in advance the field type you are comparing to.
  return func(a, b interface{}) { return a.(int) < b.(int) }
}

See MatchCmp() to learn how to write a matcher.

func WithDescending

func WithDescending() SortOpt

Notes

Bugs

  • Use carefully, no loop detections.

Jump to

Keyboard shortcuts

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