1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
package jen
import (
"bytes"
"fmt"
"go/format"
"io"
)
// Group represents a list of Code items, separated by tokens with an optional
// open and close token.
type Group struct {
name string
items []Code
open string
close string
separator string
multi bool
}
func (g *Group) isNull(f *File) bool {
if g == nil {
return true
}
if g.open != "" || g.close != "" {
return false
}
for _, c := range g.items {
if !c.isNull(f) {
return false
}
}
return true
}
func (g *Group) render(f *File, w io.Writer, s *Statement) error {
if g.name == "block" && s != nil {
// Special CaseBlock format for then the previous item in the statement
// is a Case group or the default keyword.
prev := s.previous(g)
grp, isGrp := prev.(*Group)
tkn, isTkn := prev.(token)
if isGrp && grp.name == "case" || isTkn && tkn.content == "default" {
g.open = ""
g.close = ""
}
}
if g.open != "" {
if _, err := w.Write([]byte(g.open)); err != nil {
return err
}
}
isNull, err := g.renderItems(f, w)
if err != nil {
return err
}
if !isNull && g.multi && g.close != "" {
// For multi-line blocks with a closing token, we insert a new line after the last item (but
// not if all items were null). This is to ensure that if the statement finishes with a comment,
// the closing token is not commented out.
s := "\n"
if g.separator == "," {
// We also insert add trailing comma if the separator was ",".
s = ",\n"
}
if _, err := w.Write([]byte(s)); err != nil {
return err
}
}
if g.close != "" {
if _, err := w.Write([]byte(g.close)); err != nil {
return err
}
}
return nil
}
func (g *Group) renderItems(f *File, w io.Writer) (isNull bool, err error) {
first := true
for _, code := range g.items {
if pt, ok := code.(token); ok && pt.typ == packageToken {
// Special case for package tokens in Qual groups - for dot-imports, the package token
// will be null, so will not render and will not be registered in the imports block.
// This ensures all packageTokens that are rendered are registered.
f.register(pt.content.(string))
}
if code == nil || code.isNull(f) {
// Null() token produces no output but also
// no separator. Empty() token products no
// output but adds a separator.
continue
}
if g.name == "values" {
if _, ok := code.(Dict); ok && len(g.items) > 1 {
panic("Error in Values: if Dict is used, must be one item only")
}
}
if !first && g.separator != "" {
// The separator token is added before each non-null item, but not before the first item.
if _, err := w.Write([]byte(g.separator)); err != nil {
return false, err
}
}
if g.multi {
// For multi-line blocks, we insert a new line before each non-null item.
if _, err := w.Write([]byte("\n")); err != nil {
return false, err
}
}
if err := code.render(f, w, nil); err != nil {
return false, err
}
first = false
}
return first, nil
}
// Render renders the Group to the provided writer.
func (g *Group) Render(writer io.Writer) error {
return g.RenderWithFile(writer, NewFile(""))
}
// GoString renders the Group for testing. Any error will cause a panic.
func (g *Group) GoString() string {
buf := bytes.Buffer{}
if err := g.Render(&buf); err != nil {
panic(err)
}
return buf.String()
}
// RenderWithFile renders the Group to the provided writer, using imports from the provided file.
func (g *Group) RenderWithFile(writer io.Writer, file *File) error {
buf := &bytes.Buffer{}
if err := g.render(file, buf, nil); err != nil {
return err
}
b, err := format.Source(buf.Bytes())
if err != nil {
return fmt.Errorf("Error %s while formatting source:\n%s", err, buf.String())
}
if _, err := writer.Write(b); err != nil {
return err
}
return nil
}
|