1// Copyright (c) 2017 Ernest Micklei
2//
3// MIT License
4//
5// Permission is hereby granted, free of charge, to any person obtaining
6// a copy of this software and associated documentation files (the
7// "Software"), to deal in the Software without restriction, including
8// without limitation the rights to use, copy, modify, merge, publish,
9// distribute, sublicense, and/or sell copies of the Software, and to
10// permit persons to whom the Software is furnished to do so, subject to
11// the following conditions:
12//
13// The above copyright notice and this permission notice shall be
14// included in all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24package proto
25
26import (
27	"fmt"
28	"io"
29)
30
31// Formatter visits a Proto and writes formatted source.
32type Formatter struct {
33	w               io.Writer
34	indentSeparator string
35	indentLevel     int
36	lastStmt        string
37	lastLevel       int
38}
39
40// NewFormatter returns a new Formatter. Only the indentation separator is configurable.
41func NewFormatter(writer io.Writer, indentSeparator string) *Formatter {
42	return &Formatter{w: writer, indentSeparator: indentSeparator}
43}
44
45// Format visits all proto elements and writes formatted source.
46func (f *Formatter) Format(p *Proto) {
47	for _, each := range p.Elements {
48		each.Accept(f)
49	}
50}
51
52// VisitComment formats a Comment and writes a newline.
53func (f *Formatter) VisitComment(c *Comment) {
54	f.printComment(c)
55	f.nl()
56}
57
58// VisitEnum formats a Enum.
59func (f *Formatter) VisitEnum(e *Enum) {
60	f.begin("enum", e)
61	fmt.Fprintf(f.w, "enum %s {", e.Name)
62	if len(e.Elements) > 0 {
63		f.nl()
64		f.level(1)
65		f.printAsGroups(e.Elements)
66		f.indent(-1)
67	}
68	io.WriteString(f.w, "}\n")
69	f.end("enum")
70}
71
72// VisitEnumField formats a EnumField.
73func (f *Formatter) VisitEnumField(e *EnumField) {
74	f.printAsGroups([]Visitee{e})
75}
76
77// VisitImport formats a Import.
78func (f *Formatter) VisitImport(i *Import) {
79	f.printAsGroups([]Visitee{i})
80}
81
82// VisitMessage formats a Message.
83func (f *Formatter) VisitMessage(m *Message) {
84	f.begin("message", m)
85	if m.IsExtend {
86		fmt.Fprintf(f.w, "extend ")
87	} else {
88		fmt.Fprintf(f.w, "message ")
89	}
90	fmt.Fprintf(f.w, "%s {", m.Name)
91	if len(m.Elements) > 0 {
92		f.nl()
93		f.level(1)
94		f.printAsGroups(m.Elements)
95		f.indent(-1)
96	}
97	io.WriteString(f.w, "}\n")
98	f.end("message")
99}
100
101// VisitOption formats a Option.
102func (f *Formatter) VisitOption(o *Option) {
103	f.begin("option", o)
104	fmt.Fprintf(f.w, "option %s = ", o.Name)
105	if o.AggregatedConstants != nil {
106		fmt.Fprintf(f.w, "{\n")
107		f.level(1)
108		for _, each := range o.AggregatedConstants {
109			f.indent(0)
110			fmt.Fprintf(f.w, "%s: %s\n", each.Name, each.Literal.String())
111		}
112		f.indent(-1)
113		fmt.Fprintf(f.w, "}")
114	} else {
115		// TODO printAs groups with fixed length
116		fmt.Fprintf(f.w, o.Constant.String())
117	}
118	fmt.Fprintf(f.w, ";")
119	if o.InlineComment != nil {
120		fmt.Fprintf(f.w, " //%s", o.InlineComment.Message())
121	}
122	f.nl()
123}
124
125// VisitPackage formats a Package.
126func (f *Formatter) VisitPackage(p *Package) {
127	f.nl()
128	f.printAsGroups([]Visitee{p})
129}
130
131// VisitService formats a Service.
132func (f *Formatter) VisitService(s *Service) {
133	f.begin("service", s)
134	fmt.Fprintf(f.w, "service %s {", s.Name)
135	if len(s.Elements) > 0 {
136		f.nl()
137		f.level(1)
138		f.printAsGroups(s.Elements)
139		f.indent(-1)
140	}
141	io.WriteString(f.w, "}\n")
142	f.end("service")
143}
144
145// VisitSyntax formats a Syntax.
146func (f *Formatter) VisitSyntax(s *Syntax) {
147	f.begin("syntax", s)
148	fmt.Fprintf(f.w, "syntax = %q", s.Value)
149	f.endWithComment(s.InlineComment)
150}
151
152// VisitOneof formats a Oneof.
153func (f *Formatter) VisitOneof(o *Oneof) {
154	f.begin("oneof", o)
155	fmt.Fprintf(f.w, "oneof %s {", o.Name)
156	if len(o.Elements) > 0 {
157		f.nl()
158		f.level(1)
159		f.printAsGroups(o.Elements)
160		f.indent(-1)
161	}
162	io.WriteString(f.w, "}\n")
163	f.end("oneof")
164}
165
166// VisitOneofField formats a OneofField.
167func (f *Formatter) VisitOneofField(o *OneOfField) {
168	f.printAsGroups([]Visitee{o})
169}
170
171// VisitReserved formats a Reserved.
172func (f *Formatter) VisitReserved(r *Reserved) {
173	f.begin("reserved", r)
174	io.WriteString(f.w, "reserved ")
175	if len(r.Ranges) > 0 {
176		for i, each := range r.Ranges {
177			if i > 0 {
178				io.WriteString(f.w, ", ")
179			}
180			fmt.Fprintf(f.w, "%s", each.String())
181		}
182	} else {
183		for i, each := range r.FieldNames {
184			if i > 0 {
185				io.WriteString(f.w, ", ")
186			}
187			fmt.Fprintf(f.w, "%q", each)
188		}
189	}
190	f.endWithComment(r.InlineComment)
191}
192
193// VisitRPC formats a RPC.
194func (f *Formatter) VisitRPC(r *RPC) {
195	f.printAsGroups([]Visitee{r})
196}
197
198// VisitMapField formats a MapField.
199func (f *Formatter) VisitMapField(m *MapField) {
200	f.printAsGroups([]Visitee{m})
201}
202
203// VisitNormalField formats a NormalField.
204func (f *Formatter) VisitNormalField(f1 *NormalField) {
205	f.printAsGroups([]Visitee{f1})
206}
207
208// VisitGroup formats a proto2 Group.
209func (f *Formatter) VisitGroup(g *Group) {
210	f.begin("group", g)
211	if g.Optional {
212		io.WriteString(f.w, "optional ")
213	}
214	fmt.Fprintf(f.w, "group %s = %d {", g.Name, g.Sequence)
215	if len(g.Elements) > 0 {
216		f.nl()
217		f.level(1)
218		f.printAsGroups(g.Elements)
219		f.indent(-1)
220	}
221	io.WriteString(f.w, "}\n")
222	f.end("group")
223}
224
225// VisitExtensions formats a proto2 Extensions.
226func (f *Formatter) VisitExtensions(e *Extensions) {
227	f.begin("extensions", e)
228	io.WriteString(f.w, "extensions ")
229	for i, each := range e.Ranges {
230		if i > 0 {
231			io.WriteString(f.w, ", ")
232		}
233		fmt.Fprintf(f.w, "%s", each.String())
234	}
235	f.endWithComment(e.InlineComment)
236}
237