first commit
This commit is contained in:
commit
993f06cb18
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.idea
|
51
aligned.go
Normal file
51
aligned.go
Normal file
@ -0,0 +1,51 @@
|
||||
package protofmt
|
||||
|
||||
import "strings"
|
||||
import "bytes"
|
||||
|
||||
type aligned struct {
|
||||
source string
|
||||
left bool
|
||||
padding bool
|
||||
}
|
||||
|
||||
var (
|
||||
alignedEquals = leftAligned(" = ")
|
||||
alignedShortEquals = leftAligned("=")
|
||||
alignedSpace = leftAligned(" ")
|
||||
alignedComma = leftAligned(", ")
|
||||
alignedEmpty = leftAligned("")
|
||||
alignedSemicolon = notAligned(";")
|
||||
alignedColon = notAligned(":")
|
||||
)
|
||||
|
||||
func leftAligned(src string) aligned { return aligned{src, true, true} }
|
||||
func rightAligned(src string) aligned { return aligned{src, false, true} }
|
||||
func notAligned(src string) aligned { return aligned{src, false, false} }
|
||||
|
||||
func (a aligned) preferredWidth() int {
|
||||
if !a.hasAlignment() {
|
||||
return 0 // means do not force padding because of this source
|
||||
}
|
||||
return len(a.source)
|
||||
}
|
||||
|
||||
func (a aligned) formatted(indentSeparator string, indentLevel, width int) string {
|
||||
if !a.padding {
|
||||
// if the source has newlines then make sure the correct indent level is applied
|
||||
buf := new(bytes.Buffer)
|
||||
for _, each := range a.source {
|
||||
buf.WriteRune(each)
|
||||
if '\n' == each {
|
||||
buf.WriteString(strings.Repeat(indentSeparator, indentLevel))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
if a.left {
|
||||
return a.source + strings.Repeat(" ", width-len(a.source))
|
||||
}
|
||||
return strings.Repeat(" ", width-len(a.source)) + a.source
|
||||
}
|
||||
|
||||
func (a aligned) hasAlignment() bool { return a.left || a.padding }
|
206
columns.go
Normal file
206
columns.go
Normal file
@ -0,0 +1,206 @@
|
||||
package protofmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/emicklei/proto"
|
||||
)
|
||||
|
||||
func columns(v proto.Visitee) []aligned {
|
||||
return asColumnsPrintable(v).columns()
|
||||
}
|
||||
|
||||
// columnsPrintable is for elements that can be printed in aligned columns.
|
||||
type columnsPrintable interface {
|
||||
columns() (cols []aligned)
|
||||
}
|
||||
|
||||
type columnsPrinter struct {
|
||||
cols []aligned
|
||||
visitee proto.Visitee
|
||||
}
|
||||
|
||||
func asColumnsPrintable(v proto.Visitee) *columnsPrinter {
|
||||
p := new(columnsPrinter)
|
||||
p.visitee = v
|
||||
return p
|
||||
}
|
||||
|
||||
// columns is part of columnsPrintable
|
||||
func (p *columnsPrinter) columns() []aligned {
|
||||
p.visitee.Accept(p)
|
||||
return p.cols
|
||||
}
|
||||
|
||||
func (p *columnsPrinter) VisitMessage(m *proto.Message) {}
|
||||
func (p *columnsPrinter) VisitService(v *proto.Service) {}
|
||||
func (p *columnsPrinter) VisitSyntax(s *proto.Syntax) {}
|
||||
func (p *columnsPrinter) VisitPackage(pkg *proto.Package) {
|
||||
p.cols = append(p.cols, notAligned("package "), notAligned(pkg.Name), alignedSemicolon)
|
||||
if pkg.InlineComment != nil {
|
||||
p.cols = append(p.cols, notAligned(" //"), notAligned(pkg.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
func (p *columnsPrinter) VisitOption(o *proto.Option) {
|
||||
if !o.IsEmbedded {
|
||||
p.cols = append(p.cols, leftAligned("option "))
|
||||
} else {
|
||||
p.cols = append(p.cols, leftAligned(" ["))
|
||||
}
|
||||
p.cols = append(p.cols, keyValuePair(o, o.IsEmbedded)...)
|
||||
if o.IsEmbedded {
|
||||
p.cols = append(p.cols, leftAligned("]"))
|
||||
}
|
||||
if !o.IsEmbedded {
|
||||
p.cols = append(p.cols, alignedSemicolon)
|
||||
if o.InlineComment != nil {
|
||||
p.cols = append(p.cols, notAligned(" //"), notAligned(o.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
}
|
||||
func (p *columnsPrinter) VisitImport(i *proto.Import) {
|
||||
p.cols = append(p.cols, leftAligned("import"), alignedSpace)
|
||||
if len(i.Kind) > 0 {
|
||||
p.cols = append(p.cols, leftAligned(i.Kind), alignedSpace)
|
||||
}
|
||||
p.cols = append(p.cols, notAligned(fmt.Sprintf("%q", i.Filename)), alignedSemicolon)
|
||||
if i.InlineComment != nil {
|
||||
p.cols = append(p.cols, notAligned(" //"), notAligned(i.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
|
||||
// VisitNormalField
|
||||
// [|repeated][|optional][space][name][equals][sequence][|option]
|
||||
func (p *columnsPrinter) VisitNormalField(f *proto.NormalField) {
|
||||
if f.Repeated {
|
||||
p.cols = append(p.cols, leftAligned("repeated "))
|
||||
} else if f.Optional {
|
||||
p.cols = append(p.cols, leftAligned("optional "))
|
||||
} else if f.Required {
|
||||
p.cols = append(p.cols, leftAligned("required "))
|
||||
} else {
|
||||
p.cols = append(p.cols, alignedEmpty)
|
||||
}
|
||||
p.cols = append(p.cols, leftAligned(f.Type), alignedSpace, leftAligned(f.Name), alignedEquals, rightAligned(strconv.Itoa(f.Sequence)))
|
||||
if len(f.Options) > 0 {
|
||||
p.cols = append(p.cols, leftAligned(" ["))
|
||||
for i, each := range f.Options {
|
||||
if i > 0 {
|
||||
p.cols = append(p.cols, alignedComma)
|
||||
}
|
||||
p.cols = append(p.cols, keyValuePair(each, true)...)
|
||||
}
|
||||
p.cols = append(p.cols, leftAligned("]"))
|
||||
}
|
||||
p.cols = append(p.cols, alignedSemicolon)
|
||||
if f.InlineComment != nil {
|
||||
p.cols = append(p.cols, alignedInlinePrefix(f.InlineComment), notAligned(f.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *columnsPrinter) VisitEnumField(f *proto.EnumField) {
|
||||
p.cols = append(p.cols, leftAligned(f.Name), alignedEquals, rightAligned(strconv.Itoa(f.Integer)))
|
||||
if f.ValueOption != nil {
|
||||
p.cols = append(p.cols, columns(f.ValueOption)...)
|
||||
}
|
||||
p.cols = append(p.cols, alignedSemicolon)
|
||||
if f.InlineComment != nil {
|
||||
p.cols = append(p.cols, alignedInlinePrefix(f.InlineComment), notAligned(f.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
func (p *columnsPrinter) VisitEnum(e *proto.Enum) {}
|
||||
func (p *columnsPrinter) VisitComment(e *proto.Comment) {}
|
||||
func (p *columnsPrinter) VisitOneof(o *proto.Oneof) {}
|
||||
func (p *columnsPrinter) VisitOneofField(o *proto.OneOfField) {
|
||||
p.cols = append(p.cols,
|
||||
leftAligned(o.Type),
|
||||
alignedSpace,
|
||||
leftAligned(o.Name),
|
||||
alignedEquals,
|
||||
rightAligned(strconv.Itoa(o.Sequence)))
|
||||
if len(o.Options) > 0 {
|
||||
p.cols = append(p.cols, leftAligned(" ["))
|
||||
for i, each := range o.Options {
|
||||
if i > 0 {
|
||||
p.cols = append(p.cols, alignedComma)
|
||||
}
|
||||
p.cols = append(p.cols, keyValuePair(each, true)...)
|
||||
}
|
||||
p.cols = append(p.cols, leftAligned("]"))
|
||||
}
|
||||
p.cols = append(p.cols, alignedSemicolon)
|
||||
if o.InlineComment != nil {
|
||||
p.cols = append(p.cols, notAligned(" //"), notAligned(o.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
func (p *columnsPrinter) VisitReserved(rs *proto.Reserved) {}
|
||||
func (p *columnsPrinter) VisitRPC(r *proto.RPC) {
|
||||
p.cols = append(p.cols,
|
||||
leftAligned("rpc "),
|
||||
leftAligned(r.Name),
|
||||
leftAligned(" ("))
|
||||
if r.StreamsRequest {
|
||||
p.cols = append(p.cols, leftAligned("stream "))
|
||||
} else {
|
||||
p.cols = append(p.cols, alignedEmpty)
|
||||
}
|
||||
p.cols = append(p.cols,
|
||||
leftAligned(r.RequestType),
|
||||
leftAligned(") "),
|
||||
leftAligned("returns"),
|
||||
leftAligned(" ("))
|
||||
if r.StreamsReturns {
|
||||
p.cols = append(p.cols, leftAligned("stream "))
|
||||
} else {
|
||||
p.cols = append(p.cols, alignedEmpty)
|
||||
}
|
||||
p.cols = append(p.cols,
|
||||
leftAligned(r.ReturnsType),
|
||||
leftAligned(")"))
|
||||
if len(r.Elements) > 0 {
|
||||
buf := new(bytes.Buffer)
|
||||
io.WriteString(buf, " {\n")
|
||||
f := NewFormatter(buf, " ") // TODO get separator, now 2 spaces
|
||||
f.level(1)
|
||||
for _, each := range r.Elements {
|
||||
each.Accept(f)
|
||||
io.WriteString(buf, "\n")
|
||||
}
|
||||
f.indent(-1)
|
||||
io.WriteString(buf, "}")
|
||||
p.cols = append(p.cols, notAligned(buf.String()))
|
||||
} else {
|
||||
p.cols = append(p.cols, alignedSemicolon)
|
||||
}
|
||||
if r.InlineComment != nil {
|
||||
p.cols = append(p.cols, notAligned(" //"), notAligned(r.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
func (p *columnsPrinter) VisitMapField(f *proto.MapField) {
|
||||
p.cols = append(p.cols,
|
||||
alignedEmpty, // no repeated no optional
|
||||
leftAligned(fmt.Sprintf("map <%s,%s>", f.KeyType, f.Type)),
|
||||
alignedSpace,
|
||||
leftAligned(f.Name),
|
||||
alignedEquals,
|
||||
rightAligned(strconv.Itoa(f.Sequence)))
|
||||
if len(f.Options) > 0 {
|
||||
p.cols = append(p.cols, leftAligned(" ["))
|
||||
for i, each := range f.Options {
|
||||
if i > 0 {
|
||||
p.cols = append(p.cols, alignedComma)
|
||||
}
|
||||
p.cols = append(p.cols, keyValuePair(each, true)...)
|
||||
}
|
||||
p.cols = append(p.cols, leftAligned("]"))
|
||||
}
|
||||
p.cols = append(p.cols, alignedSemicolon)
|
||||
if f.InlineComment != nil {
|
||||
p.cols = append(p.cols, alignedInlinePrefix(f.InlineComment), notAligned(f.InlineComment.Message()))
|
||||
}
|
||||
}
|
||||
func (p *columnsPrinter) VisitGroup(g *proto.Group) {}
|
||||
func (p *columnsPrinter) VisitExtensions(e *proto.Extensions) {}
|
47
extensions.go
Normal file
47
extensions.go
Normal file
@ -0,0 +1,47 @@
|
||||
package protofmt
|
||||
|
||||
import "github.com/emicklei/proto"
|
||||
|
||||
// keyValuePair returns key = value or "value"
|
||||
func keyValuePair(o *proto.Option, embedded bool) (cols []aligned) {
|
||||
equals := alignedEquals
|
||||
name := o.Name
|
||||
if len(o.Constant.OrderedMap) > 0 {
|
||||
cols = append(cols, leftAligned(name), equals)
|
||||
cols = append(cols, columnsPrintablesFromMap(o.Constant.OrderedMap)...)
|
||||
return
|
||||
}
|
||||
if embedded {
|
||||
return append(cols, leftAligned(name), equals, leftAligned(o.Constant.SourceRepresentation())) // numbers right, strings left? TODO
|
||||
}
|
||||
return append(cols, rightAligned(name), equals, rightAligned(o.Constant.SourceRepresentation()))
|
||||
}
|
||||
|
||||
func alignedInlinePrefix(c *proto.Comment) aligned {
|
||||
prefix := " //"
|
||||
if c.ExtraSlash {
|
||||
prefix = " ///"
|
||||
}
|
||||
return notAligned(prefix)
|
||||
}
|
||||
|
||||
func columnsPrintables(c *proto.Comment) (list []columnsPrintable) {
|
||||
for _, each := range c.Lines {
|
||||
list = append(list, inlineComment{each, c.ExtraSlash})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func typeAssertColumnsPrintable(v proto.Visitee) (columnsPrintable, bool) {
|
||||
return asColumnsPrintable(v), len(asColumnsPrintable(v).columns()) > 0
|
||||
}
|
||||
|
||||
func columnsPrintablesFromMap(m proto.LiteralMap) (cols []aligned) {
|
||||
cols = append(cols, leftAligned("{"), alignedSpace)
|
||||
for _, each := range m {
|
||||
// TODO only works for simple constants
|
||||
cols = append(cols, leftAligned(each.Name), alignedColon, leftAligned(each.SourceRepresentation()))
|
||||
}
|
||||
cols = append(cols, alignedSpace, leftAligned("}"))
|
||||
return
|
||||
}
|
233
formatter.go
Normal file
233
formatter.go
Normal file
@ -0,0 +1,233 @@
|
||||
package protofmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/emicklei/proto"
|
||||
)
|
||||
|
||||
// Formatter visits a Proto and writes formatted source.
|
||||
type Formatter struct {
|
||||
w io.Writer
|
||||
indentSeparator string
|
||||
indentLevel int
|
||||
lastStmt string
|
||||
lastLevel int
|
||||
}
|
||||
|
||||
// NewFormatter returns a new Formatter. Only the indentation separator is configurable.
|
||||
func NewFormatter(writer io.Writer, indentSeparator string) *Formatter {
|
||||
return &Formatter{w: writer, indentSeparator: indentSeparator}
|
||||
}
|
||||
|
||||
// Format visits all proto elements and writes formatted source.
|
||||
func (f *Formatter) Format(p *proto.Proto) {
|
||||
for _, each := range p.Elements {
|
||||
each.Accept(f)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitComment formats a Comment and writes enclosing newlines.
|
||||
func (f *Formatter) VisitComment(c *proto.Comment) {
|
||||
f.printComment(c)
|
||||
f.nl()
|
||||
}
|
||||
|
||||
// VisitEnum formats a Enum.
|
||||
func (f *Formatter) VisitEnum(e *proto.Enum) {
|
||||
f.begin("enum", e)
|
||||
if _, ok := e.Parent.(*proto.Message); !ok && (e.Comment == nil || len(e.Comment.Lines) == 0) {
|
||||
f.nl()
|
||||
}
|
||||
fmt.Fprintf(f.w, "enum %s {", e.Name)
|
||||
if len(e.Elements) > 0 {
|
||||
f.nl()
|
||||
f.level(1)
|
||||
f.printAsGroups(e.Elements)
|
||||
f.indent(-1)
|
||||
}
|
||||
io.WriteString(f.w, "}\n")
|
||||
f.end("enum")
|
||||
}
|
||||
|
||||
// VisitEnumField formats a EnumField.
|
||||
func (f *Formatter) VisitEnumField(e *proto.EnumField) {
|
||||
f.printAsGroups([]proto.Visitee{e})
|
||||
}
|
||||
|
||||
// VisitImport formats a Import.
|
||||
func (f *Formatter) VisitImport(i *proto.Import) {
|
||||
f.beginNoDoc("import", i)
|
||||
f.printAsGroups([]proto.Visitee{i})
|
||||
f.end("import")
|
||||
}
|
||||
|
||||
// VisitMessage formats a Message.
|
||||
func (f *Formatter) VisitMessage(m *proto.Message) {
|
||||
f.begin("message", m)
|
||||
if _, ok := m.Parent.(*proto.Message); !ok && (m.Comment == nil || len(m.Comment.Lines) == 0) {
|
||||
f.nl()
|
||||
}
|
||||
if m.IsExtend {
|
||||
fmt.Fprintf(f.w, "extend ")
|
||||
} else {
|
||||
fmt.Fprintf(f.w, "message ")
|
||||
}
|
||||
fmt.Fprintf(f.w, "%s {", m.Name)
|
||||
if len(m.Elements) > 0 {
|
||||
f.nl()
|
||||
f.level(1)
|
||||
f.printAsGroups(m.Elements)
|
||||
f.indent(-1)
|
||||
}
|
||||
io.WriteString(f.w, "}\n")
|
||||
f.end("message")
|
||||
}
|
||||
|
||||
// VisitOption formats a Option.
|
||||
func (f *Formatter) VisitOption(o *proto.Option) {
|
||||
f.begin("option", o)
|
||||
fmt.Fprintf(f.w, "option %s = ", o.Name)
|
||||
f.formatLiteral(&o.Constant)
|
||||
fmt.Fprintf(f.w, ";\n")
|
||||
if o.InlineComment != nil {
|
||||
fmt.Fprintf(f.w, " //%s", o.InlineComment.Message())
|
||||
}
|
||||
f.end("option")
|
||||
}
|
||||
|
||||
func (f *Formatter) formatLiteral(l *proto.Literal) {
|
||||
if len(l.OrderedMap) == 0 && len(l.Array) == 0 {
|
||||
fmt.Fprintf(f.w, "%s", l.SourceRepresentation())
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(f.w, "{\n")
|
||||
for _, other := range l.OrderedMap {
|
||||
f.indent(1)
|
||||
fmt.Fprintf(f.w, "%s", other.Name)
|
||||
if other.PrintsColon {
|
||||
fmt.Fprintf(f.w, ": ")
|
||||
}
|
||||
f.formatLiteral(other.Literal)
|
||||
f.level(-1)
|
||||
f.nl()
|
||||
}
|
||||
f.indent(0)
|
||||
fmt.Fprintf(f.w, "}")
|
||||
}
|
||||
|
||||
// VisitPackage formats a Package.
|
||||
func (f *Formatter) VisitPackage(p *proto.Package) {
|
||||
f.begin("package", p)
|
||||
f.printAsGroups([]proto.Visitee{p})
|
||||
f.end("package")
|
||||
}
|
||||
|
||||
// VisitService formats a Service.
|
||||
func (f *Formatter) VisitService(s *proto.Service) {
|
||||
f.begin("service", s)
|
||||
fmt.Fprintf(f.w, "service %s {", s.Name)
|
||||
if len(s.Elements) > 0 {
|
||||
f.nl()
|
||||
f.level(1)
|
||||
f.printAsGroups(s.Elements)
|
||||
f.indent(-1)
|
||||
}
|
||||
io.WriteString(f.w, "}\n\n")
|
||||
f.end("service")
|
||||
}
|
||||
|
||||
// VisitSyntax formats a Syntax.
|
||||
func (f *Formatter) VisitSyntax(s *proto.Syntax) {
|
||||
f.begin("syntax", s)
|
||||
fmt.Fprintf(f.w, "syntax = %q", s.Value)
|
||||
f.endWithComment(s.InlineComment)
|
||||
f.end("syntax")
|
||||
}
|
||||
|
||||
// VisitOneof formats a Oneof.
|
||||
func (f *Formatter) VisitOneof(o *proto.Oneof) {
|
||||
f.begin("oneof", o)
|
||||
fmt.Fprintf(f.w, "oneof %s {", o.Name)
|
||||
if len(o.Elements) > 0 {
|
||||
f.nl()
|
||||
f.level(1)
|
||||
f.printAsGroups(o.Elements)
|
||||
f.indent(-1)
|
||||
}
|
||||
io.WriteString(f.w, "}\n")
|
||||
f.end("oneof")
|
||||
}
|
||||
|
||||
// VisitOneofField formats a OneofField.
|
||||
func (f *Formatter) VisitOneofField(o *proto.OneOfField) {
|
||||
f.printAsGroups([]proto.Visitee{o})
|
||||
}
|
||||
|
||||
// VisitReserved formats a Reserved.
|
||||
func (f *Formatter) VisitReserved(r *proto.Reserved) {
|
||||
f.begin("reserved", r)
|
||||
io.WriteString(f.w, "reserved ")
|
||||
if len(r.Ranges) > 0 {
|
||||
for i, each := range r.Ranges {
|
||||
if i > 0 {
|
||||
io.WriteString(f.w, ", ")
|
||||
}
|
||||
fmt.Fprintf(f.w, "%s", each.SourceRepresentation())
|
||||
}
|
||||
} else {
|
||||
for i, each := range r.FieldNames {
|
||||
if i > 0 {
|
||||
io.WriteString(f.w, ", ")
|
||||
}
|
||||
fmt.Fprintf(f.w, "%q", each)
|
||||
}
|
||||
}
|
||||
f.endWithComment(r.InlineComment)
|
||||
}
|
||||
|
||||
// VisitRPC formats a RPC.
|
||||
func (f *Formatter) VisitRPC(r *proto.RPC) {
|
||||
f.printAsGroups([]proto.Visitee{r})
|
||||
}
|
||||
|
||||
// VisitMapField formats a MapField.
|
||||
func (f *Formatter) VisitMapField(m *proto.MapField) {
|
||||
f.printAsGroups([]proto.Visitee{m})
|
||||
}
|
||||
|
||||
// VisitNormalField formats a NormalField.
|
||||
func (f *Formatter) VisitNormalField(f1 *proto.NormalField) {
|
||||
f.printAsGroups([]proto.Visitee{f1})
|
||||
}
|
||||
|
||||
// VisitGroup formats a proto2 Group.
|
||||
func (f *Formatter) VisitGroup(g *proto.Group) {
|
||||
f.begin("group", g)
|
||||
if g.Optional {
|
||||
io.WriteString(f.w, "optional ")
|
||||
}
|
||||
fmt.Fprintf(f.w, "group %s = %d {", g.Name, g.Sequence)
|
||||
if len(g.Elements) > 0 {
|
||||
f.nl()
|
||||
f.level(1)
|
||||
f.printAsGroups(g.Elements)
|
||||
f.indent(-1)
|
||||
}
|
||||
io.WriteString(f.w, "}\n")
|
||||
f.end("group")
|
||||
}
|
||||
|
||||
// VisitExtensions formats a proto2 Extensions.
|
||||
func (f *Formatter) VisitExtensions(e *proto.Extensions) {
|
||||
f.begin("extensions", e)
|
||||
io.WriteString(f.w, "extensions ")
|
||||
for i, each := range e.Ranges {
|
||||
if i > 0 {
|
||||
io.WriteString(f.w, ", ")
|
||||
}
|
||||
fmt.Fprintf(f.w, "%s", each.SourceRepresentation())
|
||||
}
|
||||
f.endWithComment(e.InlineComment)
|
||||
}
|
22
formatter_test.go
Normal file
22
formatter_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package protofmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/proto"
|
||||
)
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
def, err := os.ReadFile("testdata.proto")
|
||||
assert.NoError(t, err)
|
||||
parser := proto.NewParser(bytes.NewReader(def))
|
||||
parse, err := parser.Parse()
|
||||
assert.NoError(t, err)
|
||||
var buf = new(bytes.Buffer)
|
||||
NewFormatter(buf, " ").Format(parse)
|
||||
err = os.WriteFile("testdata.proto", buf.Bytes(), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
}
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module gitter.top/common/protofmt
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/emicklei/proto v1.13.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
|
||||
github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
17
inline_comment.go
Normal file
17
inline_comment.go
Normal file
@ -0,0 +1,17 @@
|
||||
package protofmt
|
||||
|
||||
type inlineComment struct {
|
||||
line string
|
||||
extraSlash bool
|
||||
}
|
||||
|
||||
func (i inlineComment) columns() (list []aligned) {
|
||||
if len(i.line) == 0 {
|
||||
return append(list, notAligned(""))
|
||||
}
|
||||
prefix := "//"
|
||||
if i.extraSlash {
|
||||
prefix = "///"
|
||||
}
|
||||
return append(list, notAligned(prefix+i.line))
|
||||
}
|
35
reflector.go
Normal file
35
reflector.go
Normal file
@ -0,0 +1,35 @@
|
||||
package protofmt
|
||||
|
||||
import "github.com/emicklei/proto"
|
||||
|
||||
// reflector is a Visitor that can tell the short type name of a Visitee.
|
||||
type reflector struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// sole instance of reflector
|
||||
var namer = new(reflector)
|
||||
|
||||
func (r *reflector) VisitMessage(m *proto.Message) { r.name = "Message" }
|
||||
func (r *reflector) VisitService(v *proto.Service) { r.name = "Service" }
|
||||
func (r *reflector) VisitSyntax(s *proto.Syntax) { r.name = "Syntax" }
|
||||
func (r *reflector) VisitPackage(p *proto.Package) { r.name = "Package" }
|
||||
func (r *reflector) VisitOption(o *proto.Option) { r.name = "Option" }
|
||||
func (r *reflector) VisitImport(i *proto.Import) { r.name = "Import" }
|
||||
func (r *reflector) VisitNormalField(i *proto.NormalField) { r.name = "NormalField" }
|
||||
func (r *reflector) VisitEnumField(i *proto.EnumField) { r.name = "EnumField" }
|
||||
func (r *reflector) VisitEnum(e *proto.Enum) { r.name = "Enum" }
|
||||
func (r *reflector) VisitComment(e *proto.Comment) { r.name = "Comment" }
|
||||
func (r *reflector) VisitOneof(o *proto.Oneof) { r.name = "Oneof" }
|
||||
func (r *reflector) VisitOneofField(o *proto.OneOfField) { r.name = "OneOfField" }
|
||||
func (r *reflector) VisitReserved(rs *proto.Reserved) { r.name = "Reserved" }
|
||||
func (r *reflector) VisitRPC(rpc *proto.RPC) { r.name = "RPC" }
|
||||
func (r *reflector) VisitMapField(f *proto.MapField) { r.name = "NormalField" } // handle as
|
||||
func (r *reflector) VisitGroup(g *proto.Group) { r.name = "Group" }
|
||||
func (r *reflector) VisitExtensions(e *proto.Extensions) { r.name = "Extensions" }
|
||||
|
||||
// nameOfVisitee returns the short type name of a Visitee.
|
||||
func nameOfVisitee(e proto.Visitee) string {
|
||||
e.Accept(namer)
|
||||
return namer.name
|
||||
}
|
139
testdata.proto
Normal file
139
testdata.proto
Normal file
@ -0,0 +1,139 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package api_services.v1;
|
||||
|
||||
option go_package = "filesaver/gen;genv1";
|
||||
|
||||
|
||||
// @route_group: true
|
||||
// @base_url: /v1/file
|
||||
// @gen_to: ./services/controller/v1/file_controller.go
|
||||
service FileService {
|
||||
// @desc: 列表
|
||||
// @author: Young Xu
|
||||
// @method: GET
|
||||
// @api: /list
|
||||
rpc List (FileServiceListRequest) returns (FileServiceListResponse);
|
||||
// @desc: 上传
|
||||
// @author: Young Xu
|
||||
// @method: POST
|
||||
// @api: /upload
|
||||
rpc Upload (FileServiceUploadRequest) returns (FileServiceUploadResponse);
|
||||
// @desc: 删除
|
||||
// @author: Young Xu
|
||||
// @method: DELETE
|
||||
// @api: /delete
|
||||
rpc Delete (FileServiceDeleteRequest) returns (FileServiceDeleteResponse);
|
||||
// @desc: 下载
|
||||
// @author: Young Xu
|
||||
// @method: GET
|
||||
// @api: /download
|
||||
rpc Download (FileServiceDownloadRequest) returns (FileServiceDownloadResponse);
|
||||
}
|
||||
|
||||
|
||||
message FileServiceListRequest {
|
||||
string dirname = 1; // 目录
|
||||
}
|
||||
|
||||
message FileServiceListResponse {
|
||||
message Metadata {
|
||||
string domain = 1; // 域名
|
||||
}
|
||||
message Item {
|
||||
string file_id = 1; // 文件ID
|
||||
string filename = 2; // 文件名
|
||||
string file_size = 3; // 文件大小
|
||||
string created_at = 4; // 上传时间
|
||||
string dirname = 5; // 文件路径
|
||||
bool is_directory = 6; // 是否是目录
|
||||
}
|
||||
repeated Item items = 1; // 列表
|
||||
Metadata metadata = 2; // 元数据
|
||||
}
|
||||
|
||||
message FileServiceUploadRequest {}
|
||||
|
||||
message FileServiceUploadResponse {}
|
||||
|
||||
message FileServiceDeleteRequest {
|
||||
string file_id = 1; // 文件ID
|
||||
string dirname = 2; // 文件夹
|
||||
string filename = 3; // 文件名
|
||||
}
|
||||
|
||||
message FileServiceDeleteResponse {}
|
||||
|
||||
message FileServiceDownloadRequest {
|
||||
string file_id = 1; // 文件ID
|
||||
string dirname = 2; // 文件夹
|
||||
string filename = 3; // 文件名
|
||||
}
|
||||
|
||||
message FileServiceDownloadResponse {}
|
||||
|
||||
|
||||
// @route_group: true
|
||||
// @base_url: /v1/setting
|
||||
// @gen_to: ./services/controller/v1/setting_controller.go
|
||||
service SettingService {
|
||||
// @desc: 改变存储适配器
|
||||
// @author: Young Xu
|
||||
// @method: POST
|
||||
// @api: /adapter
|
||||
rpc SettingServiceChangeAdapter (SettingServiceChangeAdapterReq) returns (SettingServiceChangeAdapterResp);
|
||||
// @desc: 改变存储适配器
|
||||
// @author: Young Xu
|
||||
// @method: POST
|
||||
// @api: /cf_r2
|
||||
rpc ConfigCloudFlareR2 (SettingServiceConfigCloudFlareR2Req) returns (SettingServiceConfigCloudFlareR2Resp);
|
||||
// @desc: 获取cloudflare r2配置
|
||||
// @author: Young Xu
|
||||
// @method: GET
|
||||
// @api: /cf_r2
|
||||
rpc GetCloudFlareR2 (SettingServiceGetCloudFlareR2Req) returns (SettingServiceGetCloudFlareR2Resp);
|
||||
// @desc: 获取适配器配置
|
||||
// @author: Young Xu
|
||||
// @method: GET
|
||||
// @api: /adapter
|
||||
rpc GetAdapter (SettingServiceGetAdapterReq) returns (SettingServiceGetAdapterResp);
|
||||
}
|
||||
|
||||
|
||||
message SettingServiceChangeAdapterReq {
|
||||
string adapter = 1;
|
||||
}
|
||||
|
||||
message SettingServiceChangeAdapterResp {}
|
||||
|
||||
message SettingServiceConfigCloudFlareR2Req {
|
||||
string api_token = 1;
|
||||
string user_email = 2;
|
||||
string user_id = 3;
|
||||
string bucket = 4;
|
||||
string s3_api = 5;
|
||||
string domain = 6;
|
||||
string secret_id = 7;
|
||||
string secret_key = 8;
|
||||
}
|
||||
|
||||
message SettingServiceConfigCloudFlareR2Resp {}
|
||||
|
||||
message SettingServiceGetCloudFlareR2Req {}
|
||||
|
||||
message SettingServiceGetCloudFlareR2Resp {
|
||||
string api_token = 1;
|
||||
string user_email = 2;
|
||||
string user_id = 3;
|
||||
string bucket = 4;
|
||||
string s3_api = 5;
|
||||
string domain = 6;
|
||||
string secret_id = 7;
|
||||
string secret_key = 8;
|
||||
}
|
||||
|
||||
message SettingServiceGetAdapterReq {}
|
||||
|
||||
message SettingServiceGetAdapterResp {
|
||||
string adapter = 1;
|
||||
}
|
190
utils.go
Normal file
190
utils.go
Normal file
@ -0,0 +1,190 @@
|
||||
package protofmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emicklei/proto"
|
||||
)
|
||||
|
||||
// printDoc writes documentation is available
|
||||
func (f *Formatter) printDoc(v proto.Visitee) {
|
||||
if hasDoc, ok := v.(proto.Documented); ok {
|
||||
if doc := hasDoc.Doc(); doc != nil {
|
||||
f.printComment(doc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// printComment formats a Comment.
|
||||
func (f *Formatter) printComment(c *proto.Comment) {
|
||||
f.nl()
|
||||
if c.Cstyle {
|
||||
f.indent(0)
|
||||
fmt.Fprintln(f.w, "/*")
|
||||
}
|
||||
for i, each := range c.Lines {
|
||||
each = strings.TrimRight(each, " ")
|
||||
f.indent(0)
|
||||
if c.Cstyle {
|
||||
// only skip first and last empty lines
|
||||
skip := (i == 0 && len(each) == 0) ||
|
||||
(i == len(c.Lines)-1 && len(each) == 0)
|
||||
if !skip {
|
||||
fmt.Fprintf(f.w, "%s\n", each)
|
||||
}
|
||||
} else {
|
||||
if c.ExtraSlash {
|
||||
fmt.Fprint(f.w, "/")
|
||||
}
|
||||
fmt.Fprintf(f.w, "//%s\n", each)
|
||||
}
|
||||
}
|
||||
if c.Cstyle {
|
||||
fmt.Fprintf(f.w, "*/\n")
|
||||
}
|
||||
}
|
||||
|
||||
// begin writes a newline if the last statement kind is different. always indents.
|
||||
// if the Visitee has comment then print it.
|
||||
func (f *Formatter) begin(stmt string, v proto.Visitee) {
|
||||
// if not the first statement and different from last and on same indent level.
|
||||
if len(f.lastStmt) > 0 && f.lastStmt != stmt && f.lastLevel == f.indentLevel {
|
||||
f.nl()
|
||||
}
|
||||
f.lastStmt = stmt
|
||||
f.printDoc(v)
|
||||
f.indent(0)
|
||||
}
|
||||
|
||||
// beginNoDoc writes a newline if the last statement kind is different. always indents.
|
||||
func (f *Formatter) beginNoDoc(stmt string, v proto.Visitee) {
|
||||
// if not the first statement and different from last and on same indent level.
|
||||
if len(f.lastStmt) > 0 && f.lastStmt != stmt && f.lastLevel == f.indentLevel {
|
||||
f.nl()
|
||||
}
|
||||
f.lastStmt = stmt
|
||||
f.indent(0)
|
||||
}
|
||||
|
||||
func (f *Formatter) end(stmt string) {
|
||||
f.lastStmt = stmt
|
||||
}
|
||||
|
||||
// indent changes the indent level and writes indentation.
|
||||
func (f *Formatter) indent(diff int) {
|
||||
f.level(diff)
|
||||
for i := 0; i < f.indentLevel; i++ {
|
||||
io.WriteString(f.w, f.indentSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Formatter) printListOfColumns(list []columnsPrintable) {
|
||||
if len(list) == 0 {
|
||||
return
|
||||
}
|
||||
// collect all column values
|
||||
values := [][]aligned{}
|
||||
widths := map[int]int{}
|
||||
for _, each := range list {
|
||||
cols := each.columns()
|
||||
values = append(values, cols)
|
||||
// update max widths per column
|
||||
for i, other := range cols {
|
||||
pw := other.preferredWidth()
|
||||
w, ok := widths[i]
|
||||
if ok {
|
||||
if pw > w {
|
||||
widths[i] = pw
|
||||
}
|
||||
} else {
|
||||
widths[i] = pw
|
||||
}
|
||||
}
|
||||
}
|
||||
// now print all values
|
||||
for _, each := range values {
|
||||
f.indent(0)
|
||||
for c := 0; c < len(widths); c++ {
|
||||
pw := widths[c]
|
||||
// only print if there is a value
|
||||
if c < len(each) {
|
||||
// using space padding to match the max width
|
||||
io.WriteString(f.w, each[c].formatted(f.indentSeparator, f.indentLevel, pw))
|
||||
}
|
||||
}
|
||||
f.nl()
|
||||
}
|
||||
}
|
||||
|
||||
// nl writes a newline.
|
||||
func (f *Formatter) nl() {
|
||||
io.WriteString(f.w, "\n")
|
||||
}
|
||||
|
||||
// level changes the current indentLevel but does not print indentation.
|
||||
func (f *Formatter) level(diff int) {
|
||||
f.lastLevel = f.indentLevel
|
||||
f.indentLevel += diff
|
||||
}
|
||||
|
||||
// printAsGroups prints the list in groups of the same element type.
|
||||
func (f *Formatter) printAsGroups(list []proto.Visitee) {
|
||||
group := []columnsPrintable{}
|
||||
lastGroupName := ""
|
||||
for _, each := range list {
|
||||
groupName := nameOfVisitee(each)
|
||||
printable, isColumnsPrintable := typeAssertColumnsPrintable(each)
|
||||
if isColumnsPrintable {
|
||||
if lastGroupName != groupName {
|
||||
lastGroupName = groupName
|
||||
// print current group
|
||||
if len(group) > 0 {
|
||||
f.printListOfColumns(group)
|
||||
// begin new group
|
||||
group = []columnsPrintable{}
|
||||
}
|
||||
}
|
||||
// comment as a group entity
|
||||
if hasDoc, ok := each.(proto.Documented); ok {
|
||||
if doc := hasDoc.Doc(); doc != nil {
|
||||
f.printListOfColumns(group)
|
||||
// begin new group
|
||||
//group = []columnsPrintable{}
|
||||
//if len(doc.Lines) > 0 { // if comment then add newline before it
|
||||
// group = append(group, inlineComment{line: "", extraSlash: false})
|
||||
//}
|
||||
group = append([]columnsPrintable{}, columnsPrintables(doc)...)
|
||||
}
|
||||
}
|
||||
group = append(group, printable)
|
||||
} else {
|
||||
// not printable in group
|
||||
lastGroupName = groupName
|
||||
// print current group
|
||||
if len(group) > 0 {
|
||||
f.printListOfColumns(group)
|
||||
// begin new group
|
||||
group = []columnsPrintable{}
|
||||
}
|
||||
each.Accept(f)
|
||||
}
|
||||
}
|
||||
// print last group
|
||||
f.printListOfColumns(group)
|
||||
}
|
||||
|
||||
// endWithComment writes a statement end (;) followed by inline comment if present.
|
||||
func (f *Formatter) endWithComment(commentOrNil *proto.Comment) {
|
||||
io.WriteString(f.w, ";")
|
||||
if commentOrNil != nil {
|
||||
if commentOrNil.ExtraSlash {
|
||||
io.WriteString(f.w, " ///")
|
||||
} else {
|
||||
io.WriteString(f.w, " //")
|
||||
}
|
||||
io.WriteString(f.w, commentOrNil.Message())
|
||||
}
|
||||
io.WriteString(f.w, "\n")
|
||||
}
|
Loading…
Reference in New Issue
Block a user