status-go/vendor/olympos.io/encoding/edn/pprint.go

246 lines
6.6 KiB
Go

// Copyright 2015 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"bytes"
"io"
"unicode/utf8"
)
var (
// we can't call it spaceBytes not to conflict with decode.go's spaceBytes.
spaceOutputBytes = []byte(" ")
commaOutputBytes = []byte(",")
)
func newline(dst io.Writer, prefix, indent string, depth int) {
dst.Write([]byte{'\n'})
dst.Write([]byte(prefix))
for i := 0; i < depth; i++ {
dst.Write([]byte(indent))
}
}
// Indent writes to dst an indented form of the EDN-encoded src. Each EDN
// collection begins on a new, indented line beginning with prefix followed by
// one or more copies of indent according to the indentation nesting. The data
// written to dst does not begin with the prefix nor any indentation, and has
// no trailing newline, to make it easier to embed inside other formatted EDN
// data.
//
// Indent filters away whitespace, including comments and discards.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
origLen := dst.Len()
err := IndentStream(dst, bytes.NewBuffer(src), prefix, indent)
if err != nil {
dst.Truncate(origLen)
}
return err
}
// IndentStream is an implementation of PPrint for generic readers and writers
func IndentStream(dst io.Writer, src io.Reader, prefix, indent string) error {
var lex lexer
lex.reset()
tokStack := newTokenStack()
curType := tokenError
curSize := 0
d := NewDecoder(src)
depth := 0
for {
bs, tt, err := d.nextToken()
if err != nil {
return err
}
err = tokStack.push(tt)
if err != nil {
return err
}
prevType := curType
prevSize := curSize
if len(tokStack.toks) > 0 {
curType = tokStack.peek()
curSize = tokStack.peekCount()
}
switch tt {
case tokenMapStart, tokenVectorStart, tokenListStart, tokenSetStart:
if prevType == tokenMapStart {
dst.Write([]byte{' '})
} else if depth > 0 {
newline(dst, prefix, indent, depth)
}
dst.Write(bs)
depth++
case tokenVectorEnd, tokenListEnd, tokenMapEnd: // tokenSetEnd == tokenMapEnd
depth--
if prevSize > 0 { // suppress indent for empty collections
newline(dst, prefix, indent, depth)
}
// all of these are of length 1 in bytes, so utilise this for perf
dst.Write(bs)
case tokenTag:
// need to know what the previous type was.
switch prevType {
case tokenMapStart:
if prevSize%2 == 0 { // If previous size modulo 2 is equal to 0, we're a key
if prevSize > 0 {
dst.Write(commaOutputBytes)
}
newline(dst, prefix, indent, depth)
} else { // We're a value, add a space after the key
dst.Write(spaceOutputBytes)
}
dst.Write(bs)
dst.Write(spaceOutputBytes)
case tokenSetStart, tokenVectorStart, tokenListStart:
newline(dst, prefix, indent, depth)
dst.Write(bs)
dst.Write(spaceOutputBytes)
default: // tokenError or nested tag
dst.Write(bs)
dst.Write(spaceOutputBytes)
}
default:
switch prevType {
case tokenMapStart:
if prevSize%2 == 0 { // If previous size modulo 2 is equal to 0, we're a key
if prevSize > 0 {
dst.Write(commaOutputBytes)
}
newline(dst, prefix, indent, depth)
} else { // We're a value, add a space after the key
dst.Write(spaceOutputBytes)
}
dst.Write(bs)
case tokenSetStart, tokenVectorStart, tokenListStart:
newline(dst, prefix, indent, depth)
dst.Write(bs)
default: // toplevel or nested tag. This should collapse the whole tag tower
dst.Write(bs)
}
}
if tokStack.done() {
break
}
}
return nil
}
// PPrintOpts is a configuration map for PPrint. The values in this struct has
// no effect as of now.
type PPrintOpts struct {
RightMargin int
MiserWidth int
}
func pprintIndent(dst io.Writer, shift int) {
spaces := make([]byte, shift+1)
spaces[0] = '\n'
// TODO: This may be slower than caching the size as a byte slice
for i := 1; i <= shift; i++ {
spaces[i] = ' '
}
dst.Write(spaces)
}
// PPrint writes to dst an indented form of the EDN-encoded src. This
// implementation attempts to write idiomatic/readable EDN values, in a fashion
// close to (but not quite equal to) clojure.pprint/pprint.
//
// PPrint filters away whitespace, including comments and discards.
func PPrint(dst *bytes.Buffer, src []byte, opt *PPrintOpts) error {
origLen := dst.Len()
err := PPrintStream(dst, bytes.NewBuffer(src), opt)
if err != nil {
dst.Truncate(origLen)
}
return err
}
// PPrintStream is an implementation of PPrint for generic readers and writers
func PPrintStream(dst io.Writer, src io.Reader, opt *PPrintOpts) error {
var lex lexer
var col, prevCollStart, curSize int
var prevColl bool
lex.reset()
tokStack := newTokenStack()
shift := make([]int, 1, 8) // pre-allocate some space
curType := tokenError
d := NewDecoder(src)
for {
bs, tt, err := d.nextToken()
if err != nil {
return err
}
err = tokStack.push(tt)
if err != nil {
return err
}
prevType := curType
prevSize := curSize
if len(tokStack.toks) > 0 {
curType = tokStack.peek()
curSize = tokStack.peekCount()
}
// Indentation
switch tt {
case tokenVectorEnd, tokenListEnd, tokenMapEnd:
default:
switch prevType {
case tokenMapStart:
if prevSize%2 == 0 && prevSize > 0 {
dst.Write(commaOutputBytes)
pprintIndent(dst, shift[len(shift)-1])
col = shift[len(shift)-1]
} else if prevSize%2 == 1 { // We're a value, add a space after the key
dst.Write(spaceOutputBytes)
col++
}
case tokenSetStart, tokenVectorStart, tokenListStart:
if prevColl {
// begin on new line where prevColl started
// This will look so strange for heterogenous maps.
pprintIndent(dst, prevCollStart)
col = prevCollStart
} else if prevSize > 0 {
dst.Write(spaceOutputBytes)
col++
}
}
}
switch tt {
case tokenMapStart, tokenVectorStart, tokenListStart, tokenSetStart:
dst.Write(bs)
col += len(bs) // either 2 or 1
shift = append(shift, col) // we only use maps for now, but we'll utilise this more thoroughly later on
case tokenVectorEnd, tokenListEnd, tokenMapEnd: // tokenSetEnd == tokenMapEnd
dst.Write(bs) // all of these are of length 1 in bytes, so this is ok
prevCollStart = shift[len(shift)-1] - 1
shift = shift[:len(shift)-1]
case tokenTag:
bslen := utf8.RuneCount(bs)
dst.Write(bs)
dst.Write(spaceOutputBytes)
col += bslen + 1
default:
bslen := utf8.RuneCount(bs)
dst.Write(bs)
col += bslen
}
prevColl = (tt == tokenMapEnd || tt == tokenVectorEnd || tt == tokenListEnd)
if tokStack.done() {
break
}
}
return nil
}