cockroach: github.com/cockroachdb/cockroach/pkg/util/pretty Index | Examples | Files

package pretty

import "github.com/cockroachdb/cockroach/pkg/util/pretty"

Package pretty prints documents based on a target line width.

See: https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf

The paper describes a simple algorithm for printing a document tree with a single layout defined by text, newlines, and indentation. This concept is then expanded for documents that have more than one possible layout using a union type of two documents where both documents reduce to the same document, but one document has been flattened to a single line. A method ("best") is described that chooses the best of these two layouts (i.e., it removes all union types from a document tree). It works by tracking the desired and current line length and choosing either the flattened side of a union if it fits on the current line, or else the non-flattened side. The paper then describes various performance improvements that reduce the search space of the best function such that it can complete in O(n) instead of O(n^2) time, where n is the number of nodes.

For example code with SQL to experiment further, refer to https://github.com/knz/prettier/

Example_align demonstrates alignment.

Code:

testData := []pretty.Doc{
    pretty.JoinGroupAligned("SELECT", ",",
        pretty.Text("aaa"),
        pretty.Text("bbb"),
        pretty.Text("ccc")),
    pretty.Table(pretty.TableRightAlignFirstColumn, pretty.Text,
        pretty.TableRow{Label: "SELECT",
            Doc: pretty.Join(",",
                pretty.Text("aaa"),
                pretty.Text("bbb"),
                pretty.Text("ccc")),
        },
        pretty.TableRow{Label: "FROM",
            Doc: pretty.Join(",",
                pretty.Text("t"),
                pretty.Text("u"),
                pretty.Text("v")),
        }),
    pretty.Table(pretty.TableLeftAlignFirstColumn, pretty.Text,
        pretty.TableRow{Label: "SELECT",
            Doc: pretty.Join(",",
                pretty.Text("aaa"),
                pretty.Text("bbb"),
                pretty.Text("ccc")),
        },
        pretty.TableRow{Label: "FROM",
            Doc: pretty.Join(",",
                pretty.Text("t"),
                pretty.Text("u"),
                pretty.Text("v")),
        }),
    pretty.Table(pretty.TableRightAlignFirstColumn, pretty.Text,
        pretty.TableRow{Label: "woo", Doc: nil}, // check nil rows are omitted
        pretty.TableRow{Label: "", Doc: pretty.Nil},
        pretty.TableRow{Label: "KEY", Doc: pretty.Text("VALUE")},
        pretty.TableRow{Label: "", Doc: pretty.Text("OTHERVALUE")},
        pretty.TableRow{Label: "AAA", Doc: pretty.Nil}, // check no extra space is added
    ),
}
for _, n := range []int{1, 15, 30, 80} {
    fmt.Printf("%d:\n", n)
    for _, doc := range testData {
        p := pretty.Pretty(doc, n, true /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/)
        fmt.Printf("%s\n\n", p)
    }
}

Output:

1:
SELECT
	aaa,
	bbb,
	ccc

SELECT
	aaa,
	bbb,
	ccc
FROM
	t,
	u,
	v

SELECT
	aaa,
	bbb,
	ccc
FROM
	t,
	u,
	v

KEY
	VALUE
OTHERVALUE
AAA

15:
SELECT aaa,
       bbb,
       ccc

SELECT aaa,
       bbb,
       ccc
  FROM t, u, v

SELECT aaa,
       bbb,
       ccc
FROM   t, u, v

KEY VALUE
    OTHERVALUE
AAA

30:
SELECT aaa, bbb, ccc

SELECT aaa, bbb, ccc
  FROM t, u, v

SELECT aaa, bbb, ccc
FROM   t, u, v

KEY VALUE OTHERVALUE AAA

80:
SELECT aaa, bbb, ccc

SELECT aaa, bbb, ccc FROM t, u, v

SELECT aaa, bbb, ccc FROM t, u, v

KEY VALUE OTHERVALUE AAA

ExampleTree demonstrates the Tree example from the paper.

Code:

type Tree struct {
    s   string
    n   []Tree
    op  string
}
tree := Tree{
    s:  "aaa",
    n: []Tree{
        {
            s:  "bbbbb",
            n: []Tree{
                {s: "ccc"},
                {s: "dd"},
                {s: "ee", op: "*", n: []Tree{
                    {s: "some"},
                    {s: "another", n: []Tree{{s: "2a"}, {s: "2b"}}},
                    {s: "final"},
                }},
            },
        },
        {s: "eee"},
        {
            s:  "ffff",
            n: []Tree{
                {s: "gg"},
                {s: "hhh"},
                {s: "ii"},
            },
        },
    },
}
var (
    showTree    func(Tree) pretty.Doc
    showTrees   func([]Tree) pretty.Doc
    showBracket func([]Tree) pretty.Doc
)
showTrees = func(ts []Tree) pretty.Doc {
    if len(ts) == 1 {
        return showTree(ts[0])
    }
    return pretty.Fold(pretty.Concat,
        showTree(ts[0]),
        pretty.Text(","),
        pretty.Line,
        showTrees(ts[1:]),
    )
}
showBracket = func(ts []Tree) pretty.Doc {
    if len(ts) == 0 {
        return pretty.Nil
    }
    return pretty.Fold(pretty.Concat,
        pretty.Text("["),
        pretty.NestT(showTrees(ts)),
        pretty.Text("]"),
    )
}
showTree = func(t Tree) pretty.Doc {
    var doc pretty.Doc
    if t.op != "" {
        var operands []pretty.Doc
        for _, o := range t.n {
            operands = append(operands, showTree(o))
        }
        doc = pretty.Fold(pretty.Concat,
            pretty.Text("("),
            pretty.JoinNestedRight(
                pretty.Text(t.op), operands...),
            pretty.Text(")"),
        )
    } else {
        doc = showBracket(t.n)
    }
    return pretty.Group(pretty.Concat(
        pretty.Text(t.s),
        pretty.NestS(int16(len(t.s)), doc),
    ))
}
for _, n := range []int{1, 30, 80} {
    p := pretty.Pretty(showTree(tree), n, false /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/)
    fmt.Printf("%d:\n%s\n\n", n, p)
}

Output:

1:
aaa[bbbbb[ccc,
            dd,
            ee(some
              * another[2a,
                        2b]
              * final)],
    eee,
    ffff[gg,
            hhh,
            ii]]

30:
aaa[bbbbb[ccc,
            dd,
            ee(some
              * another[2a,
                        2b]
              * final)],
    eee,
    ffff[gg, hhh, ii]]

80:
aaa[bbbbb[ccc, dd, ee(some * another[2a, 2b] * final)], eee, ffff[gg, hhh, ii]]

Index

Examples

Package Files

document.go pretty.go util.go

func Pretty Uses

func Pretty(d Doc, n int, useTabs bool, tabWidth int, keywordTransform func(string) string) string

Pretty returns a pretty-printed string for the Doc d at line length n and tab width t. Keyword Docs are filtered through keywordTransform if not nil. keywordTransform must not change the visible length of its argument. It can, for example, add invisible characters like control codes (colors, etc.).

type Doc Uses

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

Doc represents a document as described by the type "DOC" in the referenced paper. This is the abstract representation constructed by the pretty-printing code.

var Line Doc = line{}

Line is the LINE constructor.

var Nil Doc = nilDoc{}

Nil is the NIL constructor.

var SoftBreak Doc = softbreak{}

SoftBreak is the softbreak constructor.

func Align Uses

func Align(d Doc) Doc

Align renders document d with the space-based nesting level set to the current column.

func AlignUnder Uses

func AlignUnder(head, nested Doc) Doc

AlignUnder aligns nested to the right of head, and, if this does not fit on the line, nests nested under head.

func BracketDoc Uses

func BracketDoc(l, x, r Doc) Doc

BracketDoc is like Bracket except it accepts Docs instead of strings.

func Concat Uses

func Concat(a, b Doc) Doc

Concat is the <> constructor. This uses simplifyNil to avoid actually inserting NIL docs in the abstract tree.

func ConcatLine Uses

func ConcatLine(a, b Doc) Doc

ConcatLine concatenates two Docs with a Line.

func ConcatSpace Uses

func ConcatSpace(a, b Doc) Doc

ConcatSpace concatenates two Docs with a space.

func Fold Uses

func Fold(f func(a, b Doc) Doc, d ...Doc) Doc

Fold applies f recursively to all Docs in d.

func FoldMap Uses

func FoldMap(f func(a, b Doc) Doc, g func(Doc) Doc, d ...Doc) Doc

FoldMap applies f recursively to all Docs in d processed through g.

func Group Uses

func Group(d Doc) Doc

Group will format d on one line if possible.

func Join Uses

func Join(s string, d ...Doc) Doc

Join joins Docs d with string s and Line. There is no space between each item in d and the subsequent instance of s.

func JoinDoc Uses

func JoinDoc(s Doc, d ...Doc) Doc

JoinDoc joins Docs d with Doc s.

func JoinGroupAligned Uses

func JoinGroupAligned(head, divider string, d ...Doc) Doc

JoinGroupAligned nests joined d with divider under head.

func JoinNestedOuter Uses

func JoinNestedOuter(lbl string, docFn func(string) Doc, d ...Doc) Doc

JoinNestedOuter attempts to de-indent the items after the first so that the operator appears in the right margin. This replacement is only done if there are enough "simple spaces" (as per previous uses of Align) to de-indent. No attempt is made to de-indent hard tabs, otherwise alignment may break on output devices with a different physical tab width. docFn should be set to Text or Keyword and will be used when converting lbl to a Doc.

func JoinNestedRight Uses

func JoinNestedRight(sep Doc, nested ...Doc) Doc

JoinNestedRight nests nested with string s. Every item after the first is indented. For example: aaaa <sep> bbb

bbb

<sep> ccc

ccc

func Keyword Uses

func Keyword(s string) Doc

Keyword is identical to Text except they are filtered by keywordTransform. The computed width is always len(s), regardless of the result of the result of the transform. This allows for things like coloring and other control characters in the output.

func NestS Uses

func NestS(n int16, d Doc) Doc

NestS is the NESTS constructor.

func NestT Uses

func NestT(d Doc) Doc

NestT is the NESTT constructor.

func NestUnder Uses

func NestUnder(head, nested Doc) Doc

NestUnder nests nested under head.

func Stack Uses

func Stack(d ...Doc) Doc

Stack concats Docs with a Line between each.

func Table Uses

func Table(alignment TableAlignment, docFn func(string) Doc, rows ...TableRow) Doc

Table defines a document that formats a list of pairs of items either:

- as a 2-column table, with the two columns aligned for example:
     SELECT aaa
            bbb
       FROM ccc
- as sections, for example:
     SELECT
         aaa
         bbb
     FROM
         ccc

We restrict the left value in each list item to be a one-line string to make the width computation efficient.

For convenience, the function also skips over rows with a nil pointer as doc.

docFn should be set to Text or Keyword and will be used when converting TableRow label's to Docs.

func Text Uses

func Text(s string) Doc

Text is the TEXT constructor.

type TableAlignment Uses

type TableAlignment int

TableAlignment should be used as first argument to Table().

const (
    // TableNoAlign does not use alignment and instead uses NestUnder.
    TableNoAlign TableAlignment = iota
    // TableRightAlignFirstColumn right-aligns (left-pads) the first column.
    TableRightAlignFirstColumn
    // TableLeftAlignFirstColumn left-aligns (right-pads) the first column.
    TableLeftAlignFirstColumn
)

type TableRow Uses

type TableRow struct {
    Label string
    Doc   Doc
}

TableRow is the data for one row of a RLTable (see below).

Package pretty imports 2 packages (graph) and is imported by 1 packages. Updated 2019-06-30. Refresh now. Tools for package owners.