You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-zero/tools/goctl/pkg/parser/api/ast/writer.go

402 lines
8.5 KiB
Go

package ast
import (
"bytes"
"fmt"
"io"
"strings"
"text/tabwriter"
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
"github.com/zeromicro/go-zero/tools/goctl/util"
)
const (
NilIndent = ""
WhiteSpace = " "
Indent = "\t"
NewLine = "\n"
)
const (
_ WriteMode = 1 << iota
// ModeAuto is the default mode, which will automatically
//determine whether to write a newline.
ModeAuto
// ModeExpectInSameLine will write in the same line.
ModeExpectInSameLine
)
type Option func(o *option)
type option struct {
prefix string
infix string
mode WriteMode
nodes []Node
rawText bool
}
type tokenNodeOption func(o *tokenNodeOpt)
type tokenNodeOpt struct {
prefix string
infix string
ignoreHeadComment bool
ignoreLeadingComment bool
}
// WriteMode is the mode of writing.
type WriteMode int
// Writer is the writer of ast.
type Writer struct {
tw *tabwriter.Writer
writer io.Writer
}
func transfer2TokenNode(node Node, isChild bool, opt ...tokenNodeOption) *TokenNode {
option := new(tokenNodeOpt)
for _, o := range opt {
o(option)
}
var copyOpt = append([]tokenNodeOption(nil), opt...)
var tn *TokenNode
switch val := node.(type) {
case *AnyDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.Any, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.Any = tn
case *ArrayDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.LBrack, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.LBrack = tn
case *BaseDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.Base, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.Base = tn
case *InterfaceDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.Interface, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.Interface = tn
case *MapDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.Map, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.Map = tn
case *PointerDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.Star, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.Star = tn
case *SliceDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.LBrack, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.LBrack = tn
case *StructDataType:
copyOpt = append(copyOpt, withTokenNodePrefix(NilIndent))
tn = transferTokenNode(val.LBrace, copyOpt...)
if option.ignoreHeadComment {
tn.HeadCommentGroup = nil
}
if option.ignoreLeadingComment {
tn.LeadingCommentGroup = nil
}
val.isChild = isChild
val.LBrace = tn
default:
}
return &TokenNode{
headFlag: node.HasHeadCommentGroup(),
leadingFlag: node.HasLeadingCommentGroup(),
Token: token.Token{
Text: node.Format(option.prefix),
Position: node.Pos(),
},
LeadingCommentGroup: CommentGroup{
{
token.Token{Position: node.End()},
},
},
}
}
func transferNilInfixNode(nodes []*TokenNode, opt ...tokenNodeOption) *TokenNode {
result := &TokenNode{}
var option = new(tokenNodeOpt)
for _, o := range opt {
o(option)
}
var list []string
for _, n := range nodes {
list = append(list, n.Token.Text)
}
result.Token = token.Token{
Text: option.prefix + strings.Join(list, option.infix),
Position: nodes[0].Pos(),
}
if !option.ignoreHeadComment {
result.HeadCommentGroup = nodes[0].HeadCommentGroup
}
if !option.ignoreLeadingComment {
result.LeadingCommentGroup = nodes[len(nodes)-1].LeadingCommentGroup
}
return result
}
func transferTokenNode(node *TokenNode, opt ...tokenNodeOption) *TokenNode {
result := &TokenNode{}
var option = new(tokenNodeOpt)
for _, o := range opt {
o(option)
}
result.Token = token.Token{
Type: node.Token.Type,
Text: option.prefix + node.Token.Text,
Position: node.Token.Position,
}
if !option.ignoreHeadComment {
for _, v := range node.HeadCommentGroup {
result.HeadCommentGroup = append(result.HeadCommentGroup,
&CommentStmt{Comment: token.Token{
Type: v.Comment.Type,
Text: option.prefix + v.Comment.Text,
Position: v.Comment.Position,
}})
}
}
if !option.ignoreLeadingComment {
result.LeadingCommentGroup = append(result.LeadingCommentGroup, node.LeadingCommentGroup...)
}
return result
}
func ignoreHeadComment() tokenNodeOption {
return func(o *tokenNodeOpt) {
o.ignoreHeadComment = true
}
}
func ignoreLeadingComment() tokenNodeOption {
return func(o *tokenNodeOpt) {
o.ignoreLeadingComment = true
}
}
func ignoreComment() tokenNodeOption {
return func(o *tokenNodeOpt) {
o.ignoreHeadComment = true
o.ignoreLeadingComment = true
}
}
func withTokenNodePrefix(prefix ...string) tokenNodeOption {
return func(o *tokenNodeOpt) {
for _, p := range prefix {
o.prefix = p
}
}
}
func withTokenNodeInfix(infix string) tokenNodeOption {
return func(o *tokenNodeOpt) {
o.infix = infix
}
}
func expectSameLine() Option {
return func(o *option) {
o.mode = ModeExpectInSameLine
}
}
func expectIndentInfix() Option {
return func(o *option) {
o.infix = Indent
}
}
func withNode(nodes ...Node) Option {
return func(o *option) {
o.nodes = nodes
}
}
func withMode(mode WriteMode) Option {
return func(o *option) {
o.mode = mode
}
}
func withPrefix(prefix ...string) Option {
return func(o *option) {
for _, p := range prefix {
o.prefix = p
}
}
}
func withInfix(infix string) Option {
return func(o *option) {
o.infix = infix
}
}
func withRawText() Option {
return func(o *option) {
o.rawText = true
}
}
// NewWriter returns a new Writer.
func NewWriter(writer io.Writer) *Writer {
return &Writer{
tw: tabwriter.NewWriter(writer, 1, 8, 1, ' ', tabwriter.TabIndent),
writer: writer,
}
}
// NewBufferWriter returns a new buffer Writer.
func NewBufferWriter() *Writer {
writer := bytes.NewBuffer(nil)
return &Writer{
tw: tabwriter.NewWriter(writer, 1, 8, 1, ' ', tabwriter.TabIndent),
writer: writer,
}
}
// String returns the string of the buffer.
func (w *Writer) String() string {
buffer, ok := w.writer.(*bytes.Buffer)
if !ok {
return ""
}
w.Flush()
return buffer.String()
}
// Flush flushes the buffer.
func (w *Writer) Flush() {
_ = w.tw.Flush()
}
// NewLine writes a new line.
func (w *Writer) NewLine() {
_, _ = fmt.Fprint(w.tw, NewLine)
}
// Write writes the node.
func (w *Writer) Write(opts ...Option) {
if len(opts) == 0 {
return
}
var opt = new(option)
opt.mode = ModeAuto
opt.prefix = NilIndent
opt.infix = WhiteSpace
for _, v := range opts {
v(opt)
}
w.write(opt)
}
// WriteText writes the text.
func (w *Writer) WriteText(text string) {
_, _ = fmt.Fprintf(w.tw, text)
}
func (w *Writer) write(opt *option) {
if len(opt.nodes) == 0 {
return
}
var textList []string
line := opt.nodes[0].End().Line
for idx, node := range opt.nodes {
mode := opt.mode
preIdx := idx - 1
var preNodeHasLeading bool
if preIdx > -1 && preIdx < len(opt.nodes) {
preNode := opt.nodes[preIdx]
preNodeHasLeading = preNode.HasLeadingCommentGroup()
}
if node.HasHeadCommentGroup() || preNodeHasLeading {
mode = ModeAuto
}
if mode == ModeAuto && node.Pos().Line > line {
textList = append(textList, NewLine)
}
line = node.End().Line
if util.TrimWhiteSpace(node.Format()) == "" {
continue
}
textList = append(textList, node.Format(opt.prefix))
}
text := strings.Join(textList, opt.infix)
text = strings.ReplaceAll(text, " \n", "\n")
text = strings.ReplaceAll(text, "\n ", "\n")
if opt.rawText {
_, _ = fmt.Fprint(w.writer, text)
return
}
_, _ = fmt.Fprint(w.tw, text)
}