psv

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 14, 2022 License: MIT Imports: 7 Imported by: 0

README

PSV - Pipe Separated Values

(or "Data Table Parser And Generator")

psv is a unix command line utility and go package to help create, maintain and utilise data in simple, text-based tables using a variant of the Pipe Separated Values format.

In short, psv helps you "draw" tables of data in text files and/or then use that data programmatically.

For example, psv can help you turn this (deliberately hacked up text for demonstration purposes):

                  Controls
    +---
        |key|action|alternative
    | - | --- |
|h|left
|   j|down
|k  |up
        |  l |      right |||
    :  :
    |   :wq |   write & quit |      ZZ
        +----------

into this:

                  Controls
    +----------------------------------+
    | key | action       | alternative |
    | --- | ------------ | ----------- |
    | h   | left         |             |
    | j   | down         |             |
    | k   | up           |             |
    | l   | right        |             |
    :     :              :             :
    | :wq | write & quit | ZZ          |
    +----------------------------------+

with a single call to psv.

In this case, the vim [^1] command: vip!psv [^2]

[^1]: You don't have to use vim! psv can be used from any editor that lets you pipe text through shell commands.

[^2]: which translates to: - v start a visual selection ... - i select everything in ... - p the current paragraph - !psv and replace the current selection with whatever psv makes of it.

Index

Description

psv reads, formats and writes tables of data in text files.

In doing so, psv focuses on human readibility, the documentation of intentions and easy of use, rather than trying to provide a loss-less, ubiquitous, machine-readable data transfer format.

The same could be said of markdown, and indeed, psv can be used to generate github-style markdown tables that look nice in their markdown source code, not just after they have been converted to HTML.

Another intended use case is data tables in Gherkin files, which are a central component of Behaviour Driven Development (BDD).

However, the real reason for creating psv was to be able to use text tables as the source of data for running automated tests. Hence the go package.

Main Features

  • normalisation of rows and columns, so that every row has the same number of cells
  • automatic table indentation and column alignment
  • the ability to automatically draw horizontal separation lines, called rulers
  • the ability to re-format existing tables, while leaving lines which "do not look like table rows" unchanged
  • a simple way to read data from tables into go programs via the psv go package
  • and more ...
Design Principles
  • self contained
    • psv is a single go binary with no external dependencies
    • the psv go package is a single package, also with no external dependecies other than go's standard packages
    • all psv actions occur locally (no network access required)
  • non-destructive
    • if psv doesn't know how to interperet a line of text, the text remains unchanged
    • only lines between the first and last table row are reformatted, all other lines remain unchanged
  • idempotent
    • any table generated by psv can also be read be psv
    • running a formatted table through psv again must not change the table in any way
  • easy to use
    • normal use should not require any configuration

Documentation

Installation

psv consists of two components: the psv command and the psv go package.

To use the psv command, you only need the binary psv executable in your PATH, e.g. ~/bin/psv (see binary installation below).

If you don't want to install "a binary, downloaded from the 'net", you can download the source and build your own version.

Source Installation
Pre-Requisites
  • go 1.17 or later
  • make (optional, but recommended)

Clone the psv git repository and use make to build and install psv for you

git clone -o codeberg https://codeberg.org/japh/psv
cd psv
make install

This will build, test and install the psv binary in your $GOBIN directory (typically $GOPATH/bin or ~/Go/bin)

Binary Installation

Note: currently only available for darwin amd64 (64-bit Intel Macs)

  • download the latest psv.gz from https://codeberg.org/japh/psv/releases
  • verify psv.gz with gpg --verify psv.gz.asc
  • compare psv.gz's checksums against those provided with shasum -c psv.gz.sha256
  • unpack psv.gz with gunzip psv.gz
  • copy psv to any directory in your $PATH, or use it directly via ./psv
  • don't forget to check that it is executable, e.g. chmod +x psv

Now you can use the psv command...

Using The psv Package In Go Projects
Pre-Requisites
  • go 1.17 or later

To use psv in your go project, simply import codeberg.org/japh/psv and go mod tidy will download it, build it and make it available for your project.

See the psv package documentation for the API and code examples.

Alternatives

  • psv-spec
    • an attempt to standardize a CSV replacement using pipes as the delimiter
    • focuses on electronic data transfers
    • does not provide a tabular layout
    • escaping just |, \, \n and \r is nice
      • but does not allow for whitespace quoting
      • future: | " " | could be used by psv to represent a space

Copyright 2022 Stephen Riehm japh-codeberg@opensauce.de

Documentation

Overview

Package psv provides a way to convert arrays of structs or maps into pretty, pipe-separated text tables, and vice-versa. (psv: pipe separated values)

Example

data := []map[string]string{
    {"name":"Joe", "age":"42"},
    {"name":"Freddie", "age":"41"},
    {"name":"Amy", "age":"don't ask"},
}

tbl := table.Marshal(data)
tbl.Columns = []string{"age","name"}
tbl.Indent = "    "
tbl.Decorations = []table.Decoration{
    {Line:0,Text:`The "who's-who" list of people`},
    {Line:1},
    {Line:2,Ruler:"+ -"},
    {Line:4,Ruler:"|==="},
}
fmt.Println(tbl.Encode())

Output

The "who's-who" list of people

+ --------- + ------- +
| age       | name    |
|=====================|
| 42        | Joe     |
| 41        | Freddie |
| don't ask | Amy     |

The table data and appearance can be set up via any of the following examples:

// convert a string into a table
tbl := table.DecodeString(string)
// all decorations are retained for re-rendering, e.g.
fmt.Println(tbl.Encode())

// convert a string into a data structure
// data returned in interface{}
table.Unmarshal(string,interface{})

// create a table from scratch (string data only)
tbl := table.NewTable()
tbl.Indent = "..."
tbl.Decorations = []*Decoration{...}
tbl.Data = [][]string{...}
fmt.Println(tbl.Encode())

// convert a data structure into a table (for rendering)
tbl := table.Marshal(interface{})
// optionally modify aspects of the table
tbl.Indent = "..."
tbl.Decorations = []*Decoration{...}
fmt.Println(tbl.Encode())

References

This package focusses on data representation, human readability and the exchange of intentions, which a computer may incidentally be able to make sense of. Mostly, each cell of a table is a simple, single string value, however, with a bit of additional work, the string values may be mapped to slices or maps of slightly more complicated (incl. custom) structures.

The lack of support for every last, complicated, nested structure is intentional.

There a large number of csv, tsv, dsv packages availble on pkg.go.dev, but they all concentrate on "machine readable data exchange". Nevertheless, they served as inspiration for this package as well.

psv always *felt* like the wrong program to write, but I was unable to find an existing program with suitable features:

- simple to use, suitable for as an editor plugin / shell pipe - align columnated data - while ignoring lines which aren't columnated

The unix tools [column] and [tbl] and go's own encoding/csv package all served as a good basis, but they all had different goals.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Decoration

type Decoration struct {
	Line   int    // 1-based number of this line's position in the resulting table
	Indent string // only used to re-construct original indentation outside a table's bounds
	Text   string // un-indented text to be inserted into the table, may be ""
	Ruler  string // ruler specification, see Ruler type
}

Decoration specifies a single non-table text line to be positioned at a specific point in the generated table string.

type Indenter

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

Indenter is used to track how a table was, and should be, indented.

When decoding tables, the Table's Indenter is used to automatically detect and track the indent of the first line of the table.

When encoding tables, the Table's Indenter provides the desired indentation for each table row.

In addition, the Indenter is also responsible for detecting and reconstructing a consistent 'comment style', if it was provided with a non-whitespace indent pattern.

Example
package main

import (
	"fmt"

	"codeberg.org/japh/psv"
)

func main() {

	inputLines := []string{
		``,
		`hello`,
		`    hello`,
		`// hello`,
		`// // hello`,
		`    //    hello`,
	}

	patterns := []struct {
		pattern string
		isFinal bool
	}{
		{pattern: ""},
		{pattern: ``, isFinal: true},
		{pattern: `0`},
		{pattern: `-1`},
		{pattern: `-0`},
		{pattern: `+1`},
		{pattern: `1`},
		{pattern: `63`},
		{pattern: `64`},
		{pattern: `  `},
		{pattern: `//`},
		{pattern: ` //`},
		{pattern: ` // `},
		{pattern: `> >`},
		{pattern: ` > >`},
		{pattern: ` > > `},
	}

PATTERNS:
	for _, p := range patterns {
		for _, l := range inputLines {
			i := psv.NewIndenter()

			fmt.Printf("indent option:    %q\n", p.pattern)

			if p.pattern != "" || p.isFinal {
				err := i.SetIndent(p.pattern)
				if err != nil {
					fmt.Printf("failed to set indent: %q\n\n", err)
					continue PATTERNS
				}
			}

			fmt.Printf("indenter:         %s\n", i.String())

			found := i.FindIndent(l)
			i.FinalizeIndent()

			fmt.Printf("input line:       %q\n", l)
			fmt.Printf("detected  indent: %q\n", found)
			fmt.Printf("finalised indent: %q\n", i.Indent()+l[len(found):])
			fmt.Println()
		}
		fmt.Println("----")
	}

}
Output:

indent option:    ""
indenter:         "" (default)
input line:       ""
detected  indent: ""
finalised indent: ""

indent option:    ""
indenter:         "" (default)
input line:       "hello"
detected  indent: ""
finalised indent: "hello"

indent option:    ""
indenter:         "" (default)
input line:       "    hello"
detected  indent: "    "
finalised indent: "    hello"

indent option:    ""
indenter:         "" (default)
input line:       "// hello"
detected  indent: ""
finalised indent: "// hello"

indent option:    ""
indenter:         "" (default)
input line:       "// // hello"
detected  indent: ""
finalised indent: "// // hello"

indent option:    ""
indenter:         "" (default)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: "    //    hello"

----
indent option:    ""
indenter:         "" (finalised)
input line:       ""
detected  indent: ""
finalised indent: ""

indent option:    ""
indenter:         "" (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: "hello"

indent option:    ""
indenter:         "" (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: "hello"

indent option:    ""
indenter:         "" (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: "// hello"

indent option:    ""
indenter:         "" (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: "// // hello"

indent option:    ""
indenter:         "" (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: "//    hello"

----
indent option:    "0"
indenter:         "" (finalised)
input line:       ""
detected  indent: ""
finalised indent: ""

indent option:    "0"
indenter:         "" (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: "hello"

indent option:    "0"
indenter:         "" (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: "hello"

indent option:    "0"
indenter:         "" (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: "// hello"

indent option:    "0"
indenter:         "" (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: "// // hello"

indent option:    "0"
indenter:         "" (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: "//    hello"

----
indent option:    "-1"
failed to set indent: "indent must be an unsigned integer <64 or non-numeric string"

indent option:    "-0"
failed to set indent: "indent must be an unsigned integer <64 or non-numeric string"

indent option:    "+1"
failed to set indent: "indent must be an unsigned integer <64 or non-numeric string"

indent option:    "1"
indenter:         " " (finalised)
input line:       ""
detected  indent: ""
finalised indent: " "

indent option:    "1"
indenter:         " " (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: " hello"

indent option:    "1"
indenter:         " " (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: " hello"

indent option:    "1"
indenter:         " " (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: " // hello"

indent option:    "1"
indenter:         " " (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: " // // hello"

indent option:    "1"
indenter:         " " (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: " //    hello"

----
indent option:    "63"
indenter:         "                                                               " (finalised)
input line:       ""
detected  indent: ""
finalised indent: "                                                               "

indent option:    "63"
indenter:         "                                                               " (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: "                                                               hello"

indent option:    "63"
indenter:         "                                                               " (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: "                                                               hello"

indent option:    "63"
indenter:         "                                                               " (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: "                                                               // hello"

indent option:    "63"
indenter:         "                                                               " (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: "                                                               // // hello"

indent option:    "63"
indenter:         "                                                               " (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: "                                                               //    hello"

----
indent option:    "64"
failed to set indent: "indent must be an unsigned integer <64 or non-numeric string"

indent option:    "  "
indenter:         "  " (finalised)
input line:       ""
detected  indent: ""
finalised indent: "  "

indent option:    "  "
indenter:         "  " (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: "  hello"

indent option:    "  "
indenter:         "  " (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: "  hello"

indent option:    "  "
indenter:         "  " (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: "  // hello"

indent option:    "  "
indenter:         "  " (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: "  // // hello"

indent option:    "  "
indenter:         "  " (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: "  //    hello"

----
indent option:    "//"
indenter:         "//" (default)
input line:       ""
detected  indent: ""
finalised indent: "// "

indent option:    "//"
indenter:         "//" (default)
input line:       "hello"
detected  indent: ""
finalised indent: "// hello"

indent option:    "//"
indenter:         "//" (default)
input line:       "    hello"
detected  indent: "    "
finalised indent: "//    hello"

indent option:    "//"
indenter:         "//" (default)
input line:       "// hello"
detected  indent: "// "
finalised indent: "// hello"

indent option:    "//"
indenter:         "//" (default)
input line:       "// // hello"
detected  indent: "// // "
finalised indent: "// // hello"

indent option:    "//"
indenter:         "//" (default)
input line:       "    //    hello"
detected  indent: "    //    "
finalised indent: "    //    hello"

----
indent option:    " //"
indenter:         " //" (finalised)
input line:       ""
detected  indent: ""
finalised indent: " //"

indent option:    " //"
indenter:         " //" (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: " //hello"

indent option:    " //"
indenter:         " //" (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: " //hello"

indent option:    " //"
indenter:         " //" (finalised)
input line:       "// hello"
detected  indent: "// "
finalised indent: " //hello"

indent option:    " //"
indenter:         " //" (finalised)
input line:       "// // hello"
detected  indent: "// // "
finalised indent: " //hello"

indent option:    " //"
indenter:         " //" (finalised)
input line:       "    //    hello"
detected  indent: "    //    "
finalised indent: " //hello"

----
indent option:    " // "
indenter:         " // " (finalised)
input line:       ""
detected  indent: ""
finalised indent: " // "

indent option:    " // "
indenter:         " // " (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: " // hello"

indent option:    " // "
indenter:         " // " (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: " // hello"

indent option:    " // "
indenter:         " // " (finalised)
input line:       "// hello"
detected  indent: "// "
finalised indent: " // hello"

indent option:    " // "
indenter:         " // " (finalised)
input line:       "// // hello"
detected  indent: "// // "
finalised indent: " // hello"

indent option:    " // "
indenter:         " // " (finalised)
input line:       "    //    hello"
detected  indent: "    //    "
finalised indent: " // hello"

----
indent option:    "> >"
indenter:         "> >" (default)
input line:       ""
detected  indent: ""
finalised indent: "> > "

indent option:    "> >"
indenter:         "> >" (default)
input line:       "hello"
detected  indent: ""
finalised indent: "> > hello"

indent option:    "> >"
indenter:         "> >" (default)
input line:       "    hello"
detected  indent: "    "
finalised indent: "> >    hello"

indent option:    "> >"
indenter:         "> >" (default)
input line:       "// hello"
detected  indent: ""
finalised indent: "> > // hello"

indent option:    "> >"
indenter:         "> >" (default)
input line:       "// // hello"
detected  indent: ""
finalised indent: "> > // // hello"

indent option:    "> >"
indenter:         "> >" (default)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: "> >    //    hello"

----
indent option:    " > >"
indenter:         " > >" (finalised)
input line:       ""
detected  indent: ""
finalised indent: " > >"

indent option:    " > >"
indenter:         " > >" (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: " > >hello"

indent option:    " > >"
indenter:         " > >" (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: " > >hello"

indent option:    " > >"
indenter:         " > >" (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: " > >// hello"

indent option:    " > >"
indenter:         " > >" (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: " > >// // hello"

indent option:    " > >"
indenter:         " > >" (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: " > >//    hello"

----
indent option:    " > > "
indenter:         " > > " (finalised)
input line:       ""
detected  indent: ""
finalised indent: " > > "

indent option:    " > > "
indenter:         " > > " (finalised)
input line:       "hello"
detected  indent: ""
finalised indent: " > > hello"

indent option:    " > > "
indenter:         " > > " (finalised)
input line:       "    hello"
detected  indent: "    "
finalised indent: " > > hello"

indent option:    " > > "
indenter:         " > > " (finalised)
input line:       "// hello"
detected  indent: ""
finalised indent: " > > // hello"

indent option:    " > > "
indenter:         " > > " (finalised)
input line:       "// // hello"
detected  indent: ""
finalised indent: " > > // // hello"

indent option:    " > > "
indenter:         " > > " (finalised)
input line:       "    //    hello"
detected  indent: "    "
finalised indent: " > > //    hello"

----

func NewIndenter

func NewIndenter() *Indenter

NewIndenter creates a new Indenter

func (*Indenter) FinalizeIndent

func (i *Indenter) FinalizeIndent()

FinalizeIndent is called while decoding, to indicate that the first table row has been detected.

The most recently detected indent will then be retained and used for table row encoding.

func (*Indenter) FindIndent

func (i *Indenter) FindIndent(text string) string

FindIndent checks if the beginning of a line of text matches the current indent pattern and returns the indent string.

The returned string is used to then split the line into <indent> and <text>, and may be used to re-construct the original line if it lies before or after the table's rows

func (*Indenter) Indent

func (i *Indenter) Indent() string

Indent returns the indent to be used when encoding a table row

func (*Indenter) Prefix

func (i *Indenter) Prefix() string

Prefix returns the non-whitespace part of the indent pattern provided

func (*Indenter) SetIndent

func (i *Indenter) SetIndent(pattern string) error

SetIndent explicitly sets a desired indent to use.

If SetIndent has been called, the Indenter will match indents according to the provided pattern, and will re-create indents using the provided pattern.

Special situations:

  • the pattern is just whitesspace (including "")

  • leading whitespace will be removed when decoding

  • the indent string will be used, as provided, when encoding

  • the pattern provided is just a number `n`

  • leading whitespace will be removed when decoding

  • the encoding indent will be `n` of spaces

  • the pattern contains non-whitespace characters

  • any instances of the non-whitespace pattern will be skipped at the beginning of each line

  • if the pattern has no leading or trailing whitespace

  • the skipped indent from the first table row will be re-used to indent all table rows

  • if the pattern has a leading whitespace

  • the indent string will be used as provided

  • the whitespace padding between the indent and the start of text will be retained from the first table row

  • if the pattern has a trailing whitespace

  • only the whitespace before the first non-whitespace character found will be re-used for indenting table rows

  • the indent string will then be used as provided

  • if the pattern has leading and trailing whitespace

  • then the indent will be used, unmodified, for each table row

Example (Remove_indents)
package main

import (
	"codeberg.org/japh/psv"
)

func main() {

	testCases := []string{
		"",  // explicitly request an empty-string indent
		"0", // explicitly set the indent width to 0
	}

	for _, testIndent := range testCases {
		tbl := psv.NewTable()
		tbl.SetIndent(testIndent)
		tbl.DecodeString(``)
	}

}
Output:

func (*Indenter) String

func (i *Indenter) String() string

String is only used for 'nice output' while debugging

type Options

type Options struct {
	Squash        bool   // squash multiple blank lines into a single blank line when encoding
	IndentPattern string // user-provided indent to use
	IndentIsFinal bool   // flag to force the use of Options.Indent (required to force the use of "")
}

Options is used by Table to store user specified configuration settings

type Ruler

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

Ruler represents a horizontal separator to be placed within a table.

Rulers only contain the characters |, :, *, +, - and optional spaces.

When a table is rendered, rulers are generated to fit the column widths of the table.

If the input string contains 4 runes or less, it is intepreted as a format:

e.g.   '| -:'
        |||+--  internal column separator
        ||+---  horizonal line
        |+----  padding
        +-----  outer border

If the input string is more than 4 runes, it is assumed to be a previously-generated ruler, and is analysed to determine the format.

If the format of a ruler cannot be determined, the 'border' rune will be 0 and the original input is used. (non-destructive failure)

func NewRuler

func NewRuler(input string) (*Ruler, error)

NewRuler creates a new ruler based on a format or previously rendered ruler. Note: a returned error is really only a warning! The returned *Ruler is still valid and usable!

func (*Ruler) Encode

func (r *Ruler) Encode(widths []int) string

Encode generates a string using the ruler's format to match the column widths provided in 'widths'

Example
columnWidths := []int{1, 2, 3, 4}
ruler, _ := NewRuler("| -:")

fmt.Println(ruler.Encode(columnWidths))
Output:

| - : -- : --- : ---- |

type Scanner

type Scanner interface {
	Scan() bool
	Text() string
}

Scanner is the interface required to read an input source, 1 line at a time. Subset of bufio.Scanner

type Table

type Table struct {
	Options                   // configuration options
	Data        [][]string    // 2-Dimensional array of string cells indexed by [row][column]
	Decorations []*Decoration // an array of decorations to add to the table

	*Indenter // indent matcher and producer
	// contains filtered or unexported fields
}

Table is a structure used to encapsulate a table of string data, which can be re-rendered with correct indentation and column alignment. The exported fields of Table may be used to customize the rendered result.

func NewTable

func NewTable() *Table

NewTable creates a new, empty table. Use its public fields to setup the data to be printed.

See also: encode_test.go

func TableFromString added in v0.1.0

func TableFromString(input string) (*Table, error)

TableFromString Creates a new table from a string containing a psv table. This is the recommended way to read in-situ tables. e.g.

tbl, err := psv.FromString(`
                           | 1 | one |
                           | 2 | two |
                           :         :
                           `)

func (*Table) AnalyseData

func (t *Table) AnalyseData() (rows, cols int, width []int)

AnalyseData was only created for testing (to allow access to the internal workings of encode()).

Never call this - it provides nothing of use.

func (*Table) DecodeString added in v0.1.0

func (t *Table) DecodeString(input string) error

DecodeString extracts data from a block of text containing a PSV table

func (*Table) Encode

func (t *Table) Encode() string

Encode formats a Table struct into a multi-line string

Table.Data is converted into a formatted table Table.Decorations are interspersed and indented according to the formatting rules.

Example (Encoding_decorations)
package main

import (
	"fmt"

	"codeberg.org/japh/psv"
)

func main() {

	tbl := psv.NewTable()

	tbl.SetIndent("....")
	tbl.Decorations = []*psv.Decoration{
		// column headers
		{Line: 2, Ruler: "+ ="},
		{Line: 3},
		{Line: 4, Text: "Empty rows are kept"},
		{Line: 5, Text: "Decorations appear between rows"},
		{Line: 7},
		{Line: 7, Text: "# Comments are always good"},
		{Line: 7, Text: "# repeating line numbers is not good, but works"},
		// data...
		{Line: 12},
		{Line: 12, Text: "leading and trailing spaces need extra quotes"},
		{Line: 15},
		{Line: 15, Text: "Rulers can be placed anywhere"},
		{Line: 15, Ruler: "+ -"},
		{Line: 20, Text: "and trailing text lines are no problem either"},
	}
	tbl.Data = [][]string{
		{"one", "foo", "two", "three"}, // first row are headers only
		{},                             // empty row
		{"the first", "", "the second", "the third"}, // full row
		{"more data"},                             // partial row
		{`" "`, `" x "`, `'"'`, `" x"`},           // handling of leading and trailing spaces
		{"lorem", "", "ipsum", "upsum", "oopsum"}, // extra column!
	}

	fmt.Print(tbl.Encode())

}
Output:


....| one       | foo   | two        | three     |        |
....+ ========= + ===== + ========== + ========= + ====== +

....Empty rows are kept
....Decorations appear between rows
....|           |       |            |           |        |

....# Comments are always good
....# repeating line numbers is not good, but works
....| the first |       | the second | the third |        |
....| more data |       |            |           |        |

....leading and trailing spaces need extra quotes
....| " "       | " x " | '"'        | " x"      |        |

....Rulers can be placed anywhere
....+ --------- + ----- + ---------- + --------- + ------ +
....| lorem     |       | ipsum      | upsum     | oopsum |

and trailing text lines are no problem either

func (*Table) Read

func (t *Table) Read(in Scanner) error

Read reads and decodes text lines from a Scanner which provides individual lines from some text source.

func (*Table) Write

func (t *Table) Write(b Writer)

Encode converts the 2-Dimensional slice of string data in the table into a pretty, pipe-separated table as a single, printable string.

type Writer

type Writer interface {
	WriteString(string) (int, error) // e.g. bytes.Builder or strings.Builder
}

Writer interface used to produce new PSV tables. Warning: WriteString will be called many times per line.

Notes

Bugs

  • 2022-02-14 it should be possible to just use a slice over the underlying []byte array instead of having to peice the indent and text of a decoration line back together

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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