148 lines
3.7 KiB
Go
148 lines
3.7 KiB
Go
|
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
|
||
|
}
|
||
|
|