drone-yaml/yaml/pretty/writer.go

357 lines
6.0 KiB
Go
Raw Normal View History

// Copyright (c), the Drone Authors.
// Copyright (c) 2021, Robert Kaussow <mail@thegeeklab.de>
2019-02-10 19:00:16 +00:00
2019-01-22 23:44:17 +00:00
package pretty
import (
"bytes"
"fmt"
"sort"
"strconv"
"github.com/drone/drone-yaml/yaml"
)
// TODO rename WriteTag to WriteKey
// TODO rename WriteTagValue to WriteKeyValue
// ESCAPING:
//
// The string starts with a special character:
// One of !#%@&*`?|>{[ or -.
// The string starts or ends with whitespace characters.
// The string contains : or # character sequences.
// The string ends with a colon.
// The value looks like a number or boolean (123, 1.23, true, false, null) but should be a string.
// Implement state pooling. See:
// https://golang.org/src/fmt/print.go#L131
type writer interface {
// Indent appends padding to the buffer.
Indent()
2019-09-24 21:31:55 +00:00
// IndentIncrease increases indentation.
2019-01-22 23:44:17 +00:00
IndentIncrease()
2019-09-24 21:31:55 +00:00
// IndentDecrease decreases indentation.
2019-01-22 23:44:17 +00:00
IndentDecrease()
2019-09-24 21:31:55 +00:00
// IncludeZero includes zero value values
IncludeZero()
// ExcludeZero excludes zero value values.
ExcludeZero()
2019-01-22 23:44:17 +00:00
// Write appends the contents of p to the buffer.
Write(p []byte) (n int, err error)
// WriteByte appends the contents of b to the buffer.
WriteByte(b byte) error
// WriteString appends the contents of s to the buffer.
WriteString(s string) (n int, err error)
// WriteTag appends the key to the buffer.
WriteTag(v interface{})
// WriteTag appends the keypair to the buffer.
WriteTagValue(k, v interface{})
}
//
// node writer
//
type baseWriter struct {
bytes.Buffer
depth int
2019-09-24 21:31:55 +00:00
zero bool
2019-01-22 23:44:17 +00:00
}
func (w *baseWriter) Indent() {
for i := 0; i < w.depth; i++ {
_, _ = w.WriteString(" ")
2019-01-22 23:44:17 +00:00
}
}
func (w *baseWriter) IndentIncrease() {
w.depth++
}
func (w *baseWriter) IndentDecrease() {
w.depth--
}
2019-09-24 21:31:55 +00:00
func (w *baseWriter) IncludeZero() {
w.zero = true
}
func (w *baseWriter) ExcludeZero() {
w.zero = false
}
2019-01-22 23:44:17 +00:00
func (w *baseWriter) WriteTag(v interface{}) {
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
writeValue(w, v)
_ = w.WriteByte(':')
2019-01-22 23:44:17 +00:00
}
func (w *baseWriter) WriteTagValue(k, v interface{}) {
if isZero(v) && !w.zero {
2019-01-22 23:44:17 +00:00
return
}
w.WriteTag(k)
switch {
case isPrimative(v):
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
writeValue(w, v)
case isSlice(v):
_ = w.WriteByte('\n')
w.IndentIncrease()
2019-01-22 23:44:17 +00:00
w.Indent()
writeValue(w, v)
w.IndentDecrease()
default:
2019-01-22 23:44:17 +00:00
w.depth++
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
writeValue(w, v)
w.depth--
}
}
//
// sequence writer
//
type indexWriter struct {
writer
index int
2019-09-24 21:31:55 +00:00
zero bool
}
func (w *indexWriter) IncludeZero() {
w.zero = true
}
func (w *indexWriter) ExcludeZero() {
w.zero = false
2019-01-22 23:44:17 +00:00
}
func (w *indexWriter) WriteTag(v interface{}) {
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
if w.index == 0 {
w.IndentDecrease()
w.Indent()
w.IndentIncrease()
_ = w.WriteByte('-')
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
} else {
w.Indent()
}
writeValue(w, v)
_ = w.WriteByte(':')
2019-01-22 23:44:17 +00:00
w.index++
}
func (w *indexWriter) WriteTagValue(k, v interface{}) {
if isZero(v) && !w.zero {
2019-01-22 23:44:17 +00:00
return
}
w.WriteTag(k)
switch {
case isPrimative(v):
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
writeValue(w, v)
case isSlice(v):
_ = w.WriteByte('\n')
w.IndentIncrease()
2019-01-22 23:44:17 +00:00
w.Indent()
writeValue(w, v)
w.IndentDecrease()
default:
2019-01-22 23:44:17 +00:00
w.IndentIncrease()
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
writeValue(w, v)
w.IndentDecrease()
}
}
//
// helper functions
//
func writeBool(w writer, v bool) {
_, _ = w.WriteString(
2019-01-22 23:44:17 +00:00
strconv.FormatBool(v),
)
}
func writeFloat(w writer, v float64) {
_, _ = w.WriteString(
2019-01-22 23:44:17 +00:00
strconv.FormatFloat(v, 'g', -1, 64),
)
}
func writeInt(w writer, v int) {
_, _ = w.WriteString(
2019-01-22 23:44:17 +00:00
strconv.Itoa(v),
)
}
2019-03-18 03:03:27 +00:00
func writeInt64(w writer, v int64) {
_, _ = w.WriteString(
2019-03-18 03:03:27 +00:00
strconv.FormatInt(v, 10),
)
}
2019-01-22 23:44:17 +00:00
func writeEncode(w writer, v string) {
if len(v) == 0 {
_ = w.WriteByte('"')
_ = w.WriteByte('"')
2019-01-22 23:44:17 +00:00
return
}
2019-06-07 02:09:32 +00:00
if isQuoted(v) {
2019-05-30 02:35:30 +00:00
fmt.Fprintf(w, "%q", v)
2019-06-07 02:09:32 +00:00
} else {
_, _ = w.WriteString(v)
2019-01-22 23:44:17 +00:00
}
}
func writeValue(w writer, v interface{}) {
if v == nil {
_ = w.WriteByte('~')
2019-01-22 23:44:17 +00:00
return
}
switch v := v.(type) {
2019-03-18 03:03:27 +00:00
case bool, int, int64, float64, string:
2019-01-22 23:44:17 +00:00
writeScalar(w, v)
case []interface{}:
writeSequence(w, v)
case []string:
writeSequenceStr(w, v)
case map[interface{}]interface{}:
writeMapping(w, v)
case map[string]string:
writeMappingStr(w, v)
case yaml.BytesSize:
writeValue(w, v.String())
}
}
func writeScalar(w writer, v interface{}) {
switch v := v.(type) {
case bool:
writeBool(w, v)
case int:
writeInt(w, v)
2019-03-18 03:03:27 +00:00
case int64:
writeInt64(w, v)
2019-01-22 23:44:17 +00:00
case float64:
writeFloat(w, v)
case string:
writeEncode(w, v)
}
}
func writeSequence(w writer, v []interface{}) {
if len(v) == 0 {
_ = w.WriteByte('[')
_ = w.WriteByte(']')
2019-01-22 23:44:17 +00:00
return
}
for i, v := range v {
if i != 0 {
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
}
_ = w.WriteByte('-')
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
w.IndentIncrease()
writeValue(w, v)
w.IndentDecrease()
}
}
func writeSequenceStr(w writer, v []string) {
if len(v) == 0 {
_ = w.WriteByte('[')
_ = w.WriteByte(']')
2019-01-22 23:44:17 +00:00
return
}
for i, v := range v {
if i != 0 {
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
}
_ = w.WriteByte('-')
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
writeEncode(w, v)
}
}
func writeMapping(w writer, v map[interface{}]interface{}) {
if len(v) == 0 {
_ = w.WriteByte('{')
_ = w.WriteByte('}')
2019-01-22 23:44:17 +00:00
return
}
var keys []string
for k := range v {
s := fmt.Sprint(k)
keys = append(keys, s)
}
sort.Strings(keys)
for i, k := range keys {
v := v[k]
if i != 0 {
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
}
writeEncode(w, k)
_ = w.WriteByte(':')
2019-01-22 23:44:17 +00:00
if v == nil || isPrimative(v) || isZero(v) {
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
writeValue(w, v)
} else {
slice := isSlice(v)
if !slice {
w.IndentIncrease()
}
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
writeValue(w, v)
if !slice {
w.IndentDecrease()
}
}
}
}
func writeMappingStr(w writer, v map[string]string) {
if len(v) == 0 {
_ = w.WriteByte('{')
_ = w.WriteByte('}')
2019-01-22 23:44:17 +00:00
return
}
var keys []string
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
v := v[k]
if i != 0 {
_ = w.WriteByte('\n')
2019-01-22 23:44:17 +00:00
w.Indent()
}
writeEncode(w, k)
_ = w.WriteByte(':')
_ = w.WriteByte(' ')
2019-01-22 23:44:17 +00:00
writeEncode(w, v)
}
}