302 lines
8.2 KiB
Go
302 lines
8.2 KiB
Go
|
package jen
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type tokenType string
|
||
|
|
||
|
const (
|
||
|
packageToken tokenType = "package"
|
||
|
identifierToken tokenType = "identifier"
|
||
|
qualifiedToken tokenType = "qualified"
|
||
|
keywordToken tokenType = "keyword"
|
||
|
operatorToken tokenType = "operator"
|
||
|
delimiterToken tokenType = "delimiter"
|
||
|
literalToken tokenType = "literal"
|
||
|
literalRuneToken tokenType = "literal_rune"
|
||
|
literalByteToken tokenType = "literal_byte"
|
||
|
nullToken tokenType = "null"
|
||
|
layoutToken tokenType = "layout"
|
||
|
)
|
||
|
|
||
|
type token struct {
|
||
|
typ tokenType
|
||
|
content interface{}
|
||
|
}
|
||
|
|
||
|
func (t token) isNull(f *File) bool {
|
||
|
if t.typ == packageToken {
|
||
|
// package token is null if the path is a dot-import or the local package path
|
||
|
return f.isDotImport(t.content.(string)) || f.isLocal(t.content.(string))
|
||
|
}
|
||
|
return t.typ == nullToken
|
||
|
}
|
||
|
|
||
|
func (t token) render(f *File, w io.Writer, s *Statement) error {
|
||
|
switch t.typ {
|
||
|
case literalToken:
|
||
|
var out string
|
||
|
switch t.content.(type) {
|
||
|
case bool, string, int, complex128:
|
||
|
// default constant types can be left bare
|
||
|
out = fmt.Sprintf("%#v", t.content)
|
||
|
case float64:
|
||
|
out = fmt.Sprintf("%#v", t.content)
|
||
|
if !strings.Contains(out, ".") && !strings.Contains(out, "e") {
|
||
|
// If the formatted value is not in scientific notation, and does not have a dot, then
|
||
|
// we add ".0". Otherwise it will be interpreted as an int.
|
||
|
// See:
|
||
|
// https://github.com/dave/jennifer/issues/39
|
||
|
// https://github.com/golang/go/issues/26363
|
||
|
out += ".0"
|
||
|
}
|
||
|
case float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr:
|
||
|
// other built-in types need specific type info
|
||
|
out = fmt.Sprintf("%T(%#v)", t.content, t.content)
|
||
|
case complex64:
|
||
|
// fmt package already renders parenthesis for complex64
|
||
|
out = fmt.Sprintf("%T%#v", t.content, t.content)
|
||
|
default:
|
||
|
panic(fmt.Sprintf("unsupported type for literal: %T", t.content))
|
||
|
}
|
||
|
if _, err := w.Write([]byte(out)); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case literalRuneToken:
|
||
|
if _, err := w.Write([]byte(strconv.QuoteRune(t.content.(rune)))); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case literalByteToken:
|
||
|
if _, err := w.Write([]byte(fmt.Sprintf("byte(%#v)", t.content))); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case keywordToken, operatorToken, layoutToken, delimiterToken:
|
||
|
if _, err := w.Write([]byte(fmt.Sprintf("%s", t.content))); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if t.content.(string) == "default" {
|
||
|
// Special case for Default, which must always be followed by a colon
|
||
|
if _, err := w.Write([]byte(":")); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
case packageToken:
|
||
|
path := t.content.(string)
|
||
|
alias := f.register(path)
|
||
|
if _, err := w.Write([]byte(alias)); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case identifierToken:
|
||
|
if _, err := w.Write([]byte(t.content.(string))); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case nullToken: // notest
|
||
|
// do nothing (should never render a null token)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Null adds a null item. Null items render nothing and are not followed by a
|
||
|
// separator in lists.
|
||
|
func Null() *Statement {
|
||
|
return newStatement().Null()
|
||
|
}
|
||
|
|
||
|
// Null adds a null item. Null items render nothing and are not followed by a
|
||
|
// separator in lists.
|
||
|
func (g *Group) Null() *Statement {
|
||
|
s := Null()
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Null adds a null item. Null items render nothing and are not followed by a
|
||
|
// separator in lists.
|
||
|
func (s *Statement) Null() *Statement {
|
||
|
t := token{
|
||
|
typ: nullToken,
|
||
|
}
|
||
|
*s = append(*s, t)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Empty adds an empty item. Empty items render nothing but are followed by a
|
||
|
// separator in lists.
|
||
|
func Empty() *Statement {
|
||
|
return newStatement().Empty()
|
||
|
}
|
||
|
|
||
|
// Empty adds an empty item. Empty items render nothing but are followed by a
|
||
|
// separator in lists.
|
||
|
func (g *Group) Empty() *Statement {
|
||
|
s := Empty()
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Empty adds an empty item. Empty items render nothing but are followed by a
|
||
|
// separator in lists.
|
||
|
func (s *Statement) Empty() *Statement {
|
||
|
t := token{
|
||
|
typ: operatorToken,
|
||
|
content: "",
|
||
|
}
|
||
|
*s = append(*s, t)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Op renders the provided operator / token.
|
||
|
func Op(op string) *Statement {
|
||
|
return newStatement().Op(op)
|
||
|
}
|
||
|
|
||
|
// Op renders the provided operator / token.
|
||
|
func (g *Group) Op(op string) *Statement {
|
||
|
s := Op(op)
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Op renders the provided operator / token.
|
||
|
func (s *Statement) Op(op string) *Statement {
|
||
|
t := token{
|
||
|
typ: operatorToken,
|
||
|
content: op,
|
||
|
}
|
||
|
*s = append(*s, t)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Dot renders a period followed by an identifier. Use for fields and selectors.
|
||
|
func Dot(name string) *Statement {
|
||
|
// notest
|
||
|
// don't think this can be used in valid code?
|
||
|
return newStatement().Dot(name)
|
||
|
}
|
||
|
|
||
|
// Dot renders a period followed by an identifier. Use for fields and selectors.
|
||
|
func (g *Group) Dot(name string) *Statement {
|
||
|
// notest
|
||
|
// don't think this can be used in valid code?
|
||
|
s := Dot(name)
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Dot renders a period followed by an identifier. Use for fields and selectors.
|
||
|
func (s *Statement) Dot(name string) *Statement {
|
||
|
d := token{
|
||
|
typ: delimiterToken,
|
||
|
content: ".",
|
||
|
}
|
||
|
t := token{
|
||
|
typ: identifierToken,
|
||
|
content: name,
|
||
|
}
|
||
|
*s = append(*s, d, t)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Id renders an identifier.
|
||
|
func Id(name string) *Statement {
|
||
|
return newStatement().Id(name)
|
||
|
}
|
||
|
|
||
|
// Id renders an identifier.
|
||
|
func (g *Group) Id(name string) *Statement {
|
||
|
s := Id(name)
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Id renders an identifier.
|
||
|
func (s *Statement) Id(name string) *Statement {
|
||
|
t := token{
|
||
|
typ: identifierToken,
|
||
|
content: name,
|
||
|
}
|
||
|
*s = append(*s, t)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Qual renders a qualified identifier. Imports are automatically added when
|
||
|
// used with a File. If the path matches the local path, the package name is
|
||
|
// omitted. If package names conflict they are automatically renamed. Note that
|
||
|
// it is not possible to reliably determine the package name given an arbitrary
|
||
|
// package path, so a sensible name is guessed from the path and added as an
|
||
|
// alias. The names of all standard library packages are known so these do not
|
||
|
// need to be aliased. If more control is needed of the aliases, see
|
||
|
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
|
||
|
func Qual(path, name string) *Statement {
|
||
|
return newStatement().Qual(path, name)
|
||
|
}
|
||
|
|
||
|
// Qual renders a qualified identifier. Imports are automatically added when
|
||
|
// used with a File. If the path matches the local path, the package name is
|
||
|
// omitted. If package names conflict they are automatically renamed. Note that
|
||
|
// it is not possible to reliably determine the package name given an arbitrary
|
||
|
// package path, so a sensible name is guessed from the path and added as an
|
||
|
// alias. The names of all standard library packages are known so these do not
|
||
|
// need to be aliased. If more control is needed of the aliases, see
|
||
|
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
|
||
|
func (g *Group) Qual(path, name string) *Statement {
|
||
|
s := Qual(path, name)
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Qual renders a qualified identifier. Imports are automatically added when
|
||
|
// used with a File. If the path matches the local path, the package name is
|
||
|
// omitted. If package names conflict they are automatically renamed. Note that
|
||
|
// it is not possible to reliably determine the package name given an arbitrary
|
||
|
// package path, so a sensible name is guessed from the path and added as an
|
||
|
// alias. The names of all standard library packages are known so these do not
|
||
|
// need to be aliased. If more control is needed of the aliases, see
|
||
|
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
|
||
|
func (s *Statement) Qual(path, name string) *Statement {
|
||
|
g := &Group{
|
||
|
close: "",
|
||
|
items: []Code{
|
||
|
token{
|
||
|
typ: packageToken,
|
||
|
content: path,
|
||
|
},
|
||
|
token{
|
||
|
typ: identifierToken,
|
||
|
content: name,
|
||
|
},
|
||
|
},
|
||
|
name: "qual",
|
||
|
open: "",
|
||
|
separator: ".",
|
||
|
}
|
||
|
*s = append(*s, g)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Line inserts a blank line.
|
||
|
func Line() *Statement {
|
||
|
return newStatement().Line()
|
||
|
}
|
||
|
|
||
|
// Line inserts a blank line.
|
||
|
func (g *Group) Line() *Statement {
|
||
|
s := Line()
|
||
|
g.items = append(g.items, s)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Line inserts a blank line.
|
||
|
func (s *Statement) Line() *Statement {
|
||
|
t := token{
|
||
|
typ: layoutToken,
|
||
|
content: "\n",
|
||
|
}
|
||
|
*s = append(*s, t)
|
||
|
return s
|
||
|
}
|