Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
670 lines
17 KiB
Go
670 lines
17 KiB
Go
// Package protoutil wraps proto structs to allow easier creation, protobuf lang is small enough
|
|
// to easily allow this.
|
|
package protoutil
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/emicklei/proto"
|
|
)
|
|
|
|
// TODO: Can also support comments/inline comments? -- Probably, formatting is currently
|
|
// flaky with how it prints them, though.
|
|
|
|
// Values for the kind of import.
|
|
const (
|
|
KindWeak = "weak"
|
|
KindPublic = "public"
|
|
)
|
|
|
|
// NewLiteral creates a new Literal:
|
|
//
|
|
// // true
|
|
// l := NewLiteral("true")
|
|
//
|
|
// // 1
|
|
// l := NewLiteral("1")
|
|
//
|
|
// // "foo"
|
|
// l := NewLiteral("foo")
|
|
//
|
|
// Currently doesn't support creating compound literals (arrays/maps).
|
|
func NewLiteral(lit string) *proto.Literal {
|
|
return &proto.Literal{
|
|
Source: lit,
|
|
IsString: isString(lit),
|
|
}
|
|
}
|
|
|
|
// ImportSpec holds information relevant to the import statement.
|
|
type ImportSpec struct {
|
|
path string
|
|
kind string
|
|
}
|
|
|
|
// ImportSpecOptions is a type alias for a callable accepting an ImportSpec.
|
|
type ImportSpecOptions func(i *ImportSpec)
|
|
|
|
// Weak allows you to set the kind of the import statement to 'weak'.
|
|
func Weak() ImportSpecOptions {
|
|
return func(i *ImportSpec) {
|
|
i.kind = KindWeak
|
|
}
|
|
}
|
|
|
|
// Public allows you to set the kind of the import statement to 'public'.
|
|
func Public() ImportSpecOptions {
|
|
return func(i *ImportSpec) {
|
|
i.kind = KindPublic
|
|
}
|
|
}
|
|
|
|
// NewImport creates a new import statement node:
|
|
//
|
|
// // import "myproto.proto";
|
|
// imp := NewImport("myproto.proto")
|
|
//
|
|
// By default, no kind is assigned to it, by using Weak or Public, this can be specified:
|
|
//
|
|
// // import weak "myproto.proto";
|
|
// imp := NewImport("myproto.proto", Weak())
|
|
func NewImport(path string, opts ...ImportSpecOptions) *proto.Import {
|
|
i := ImportSpec{path: path}
|
|
for _, opt := range opts {
|
|
opt(&i)
|
|
}
|
|
|
|
return &proto.Import{
|
|
Filename: i.path,
|
|
Kind: i.kind,
|
|
}
|
|
}
|
|
|
|
// NewPackage creates a new package statement node:
|
|
//
|
|
// // package foo.bar;
|
|
// pkg := NewPackage("foo.bar")
|
|
func NewPackage(path string) *proto.Package {
|
|
return &proto.Package{
|
|
Name: path,
|
|
}
|
|
}
|
|
|
|
// OptionSpec holds information relevant to the option statement.
|
|
type OptionSpec struct {
|
|
name string
|
|
setter string
|
|
constant string
|
|
custom bool
|
|
}
|
|
|
|
// OptionSpecOptions is a function that accepts an OptionSpec.
|
|
type OptionSpecOptions func(o *OptionSpec)
|
|
|
|
// Custom denotes the option as being a custom option.
|
|
func Custom() OptionSpecOptions {
|
|
return func(f *OptionSpec) {
|
|
f.custom = true
|
|
}
|
|
}
|
|
|
|
// SetField allows setting specific fields for a given option
|
|
// that denotes a type with fields.
|
|
//
|
|
// // option (my_opt).field = "Value";
|
|
// opt := NewOption("my_opt", "Value", Custom(), Setter("field"))
|
|
func SetField(name string) OptionSpecOptions {
|
|
return func(f *OptionSpec) {
|
|
f.setter = name
|
|
}
|
|
}
|
|
|
|
// NewOption creates a new option statement node:
|
|
//
|
|
// // option foo = 1;
|
|
// opt := NewOption("foo", "1")
|
|
//
|
|
// Custom options can be marked as such by using Custom, this wraps the option name
|
|
// in parenthesis:
|
|
//
|
|
// // option (foo) = 1;
|
|
// opt := NewOption("foo", "1", Custom())
|
|
//
|
|
// Since option constants can accept a number of types, strings that require quotation
|
|
// should be passed as raw strings:
|
|
//
|
|
// // option foo = "bar";
|
|
// opt := NewOption("foo", `bar`)
|
|
func NewOption(name, constant string, opts ...OptionSpecOptions) *proto.Option {
|
|
o := OptionSpec{name: name, constant: constant}
|
|
for _, opt := range opts {
|
|
opt(&o)
|
|
}
|
|
if o.custom {
|
|
o.name = fmt.Sprintf("(%s)", o.name)
|
|
}
|
|
// add the field we are setting outside the parentheses.
|
|
if o.setter != "" {
|
|
o.name = fmt.Sprintf("%s.%s", o.name, o.setter)
|
|
}
|
|
return &proto.Option{
|
|
Name: o.name,
|
|
Constant: *NewLiteral(o.constant),
|
|
}
|
|
}
|
|
|
|
/// Service + PRC
|
|
|
|
// RPCSpec holds information relevant to the rpc statement.
|
|
type RPCSpec struct {
|
|
name, inputType, outputType string
|
|
streamsReq, streamsResp bool
|
|
options []*proto.Option
|
|
}
|
|
|
|
// RPCSpecOptions is a type alias for a callable accepting an RPCSpec.
|
|
type RPCSpecOptions func(i *RPCSpec)
|
|
|
|
// StreamRequest marks request as streaming.
|
|
func StreamRequest() RPCSpecOptions {
|
|
return func(r *RPCSpec) {
|
|
r.streamsReq = true
|
|
}
|
|
}
|
|
|
|
// StreamResponse marks response as streaming.
|
|
func StreamResponse() RPCSpecOptions {
|
|
return func(r *RPCSpec) {
|
|
r.streamsResp = true
|
|
}
|
|
}
|
|
|
|
// WithRPCOptions adds options to the RPC.
|
|
func WithRPCOptions(option ...*proto.Option) RPCSpecOptions {
|
|
return func(o *RPCSpec) {
|
|
o.options = append(o.options, option...)
|
|
}
|
|
}
|
|
|
|
// NewRPC creates a new RPC statement node:
|
|
//
|
|
// // rpc Foo(Bar) returns(Bar) {}
|
|
// rpc := NewRPC("Foo", "Bar", "Bar")
|
|
//
|
|
// No options are attached by default, use WithRPCOptions to add options as required:
|
|
//
|
|
// // rpc Foo(Bar) returns(Bar) {
|
|
// // option (foo) = 1;
|
|
// // }
|
|
// rpc := NewRPC("Foo", "Bar", "Bar", WithRPCOptions(NewOption("foo", "1")))
|
|
func NewRPC(name, inputType, outputType string, opts ...RPCSpecOptions) *proto.RPC {
|
|
r := RPCSpec{name: name, inputType: inputType, outputType: outputType}
|
|
for _, opt := range opts {
|
|
opt(&r)
|
|
}
|
|
|
|
rpc := &proto.RPC{
|
|
Name: r.name,
|
|
Comment: defaultComment(r.name, "RPC"),
|
|
RequestType: r.inputType,
|
|
ReturnsType: r.outputType,
|
|
StreamsRequest: r.streamsReq,
|
|
StreamsReturns: r.streamsResp,
|
|
}
|
|
if len(r.options) > 0 {
|
|
for _, opt := range r.options {
|
|
rpc.Elements = append(rpc.Elements, opt)
|
|
}
|
|
}
|
|
return rpc
|
|
}
|
|
|
|
// ServiceSpec holds information relevant to the service statement.
|
|
type ServiceSpec struct {
|
|
name string
|
|
rpcs []*proto.RPC
|
|
opts []*proto.Option
|
|
}
|
|
|
|
// ServiceSpecOptions is a type alias for a callable accepting a ServiceSpec.
|
|
type ServiceSpecOptions func(i *ServiceSpec)
|
|
|
|
// WithRPCs adds rpcs to the service.
|
|
func WithRPCs(rpcs ...*proto.RPC) ServiceSpecOptions {
|
|
return func(s *ServiceSpec) {
|
|
s.rpcs = append(s.rpcs, rpcs...)
|
|
}
|
|
}
|
|
|
|
// WithServiceOptions adds options to the service.
|
|
func WithServiceOptions(options ...*proto.Option) ServiceSpecOptions {
|
|
return func(s *ServiceSpec) {
|
|
s.opts = append(s.opts, options...)
|
|
}
|
|
}
|
|
|
|
// NewService creates a new service statement node:
|
|
//
|
|
// // service Foo {}
|
|
// service := NewService("Foo")
|
|
//
|
|
// No rpcs/options are attached by default, use WithRPCs and
|
|
// WithServiceOptions to add them as required:
|
|
//
|
|
// // service Foo {
|
|
// // option (foo) = 1;
|
|
// // rpc Bar(Bar) returns (Bar) {}
|
|
// // }
|
|
// opt := NewOption("foo", "1")
|
|
// rpc := NewRPC("Bar", "Bar", "Bar")
|
|
// service := NewService("Foo", WithServiceOptions(opt), WithRPCs(rpc))
|
|
//
|
|
// By default, options are added first and then the rpcs.
|
|
func NewService(name string, opts ...ServiceSpecOptions) *proto.Service {
|
|
s := ServiceSpec{name: name}
|
|
for _, opt := range opts {
|
|
opt(&s)
|
|
}
|
|
service := &proto.Service{
|
|
Name: s.name,
|
|
Comment: defaultComment(s.name, "service"),
|
|
}
|
|
for _, opt := range s.opts {
|
|
service.Elements = append(service.Elements, opt)
|
|
}
|
|
for _, rpc := range s.rpcs {
|
|
service.Elements = append(service.Elements, rpc)
|
|
}
|
|
return service
|
|
}
|
|
|
|
/// Message + NormalField
|
|
|
|
// FieldSpec holds information relevant to the field statement.
|
|
type FieldSpec struct {
|
|
name, typename string
|
|
sequence int
|
|
repeated, optional, required bool
|
|
options []*proto.Option
|
|
}
|
|
|
|
// FieldSpecOptions is a type alias for a callable accepting a FieldSpec.
|
|
type FieldSpecOptions func(f *FieldSpec)
|
|
|
|
// Repeated marks the field as repeated.
|
|
func Repeated() FieldSpecOptions {
|
|
return func(f *FieldSpec) {
|
|
f.repeated = true
|
|
}
|
|
}
|
|
|
|
// Optional marks the field as optional.
|
|
func Optional() FieldSpecOptions {
|
|
return func(f *FieldSpec) {
|
|
f.optional = true
|
|
}
|
|
}
|
|
|
|
// Required marks the field as required.
|
|
func Required() FieldSpecOptions {
|
|
return func(f *FieldSpec) {
|
|
f.required = true
|
|
}
|
|
}
|
|
|
|
// WithFieldOptions adds options to the field.
|
|
func WithFieldOptions(options ...*proto.Option) FieldSpecOptions {
|
|
return func(f *FieldSpec) {
|
|
f.options = append(f.options, options...)
|
|
}
|
|
}
|
|
|
|
// NewField creates a new field statement node:
|
|
//
|
|
// // int64 Foo = 1;
|
|
// field := NewField("Foo", "int64", 1)
|
|
//
|
|
// Fields aren't marked as repeated, required or optional. Use Repeated, Optional
|
|
// and Required to mark the field as such.
|
|
//
|
|
// // repeated int64 Foo = 1;
|
|
// field := NewField("Foo", "int64", 1, Repeated())
|
|
func NewField(name, typename string, sequence int, opts ...FieldSpecOptions) *proto.NormalField {
|
|
f := FieldSpec{name: name, typename: typename, sequence: sequence}
|
|
for _, opt := range opts {
|
|
opt(&f)
|
|
}
|
|
|
|
// Check qualifiers? Though protoc will shout if we do stupid things.
|
|
field := &proto.NormalField{
|
|
Field: &proto.Field{
|
|
Name: f.name,
|
|
Sequence: f.sequence,
|
|
Type: f.typename,
|
|
Options: []*proto.Option{},
|
|
},
|
|
Repeated: f.repeated,
|
|
Required: f.required,
|
|
Optional: f.optional,
|
|
}
|
|
if len(f.options) > 0 {
|
|
field.Options = append(field.Options, f.options...)
|
|
}
|
|
return field
|
|
}
|
|
|
|
// MessageSpec holds information relevant to the message statement.
|
|
type MessageSpec struct {
|
|
name string
|
|
fields []*proto.NormalField
|
|
enums []*proto.Enum
|
|
options []*proto.Option
|
|
isExtend bool
|
|
}
|
|
|
|
// MessageSpecOptions is a type alias for a callable accepting a MessageSpec.
|
|
type MessageSpecOptions func(i *MessageSpec)
|
|
|
|
// WithMessageOptions adds options to the message.
|
|
func WithMessageOptions(options ...*proto.Option) MessageSpecOptions {
|
|
return func(m *MessageSpec) {
|
|
m.options = append(m.options, options...)
|
|
}
|
|
}
|
|
|
|
// WithFields adds fields to the message.
|
|
func WithFields(fields ...*proto.NormalField) MessageSpecOptions {
|
|
return func(m *MessageSpec) {
|
|
m.fields = append(m.fields, fields...)
|
|
}
|
|
}
|
|
|
|
// WithEnums adds enums to the message.
|
|
func WithEnums(enum ...*proto.Enum) MessageSpecOptions {
|
|
return func(m *MessageSpec) {
|
|
m.enums = append(m.enums, enum...)
|
|
}
|
|
}
|
|
|
|
func Extend() MessageSpecOptions {
|
|
return func(m *MessageSpec) {
|
|
m.isExtend = true
|
|
}
|
|
}
|
|
|
|
// NewMessage creates a new message statement node:
|
|
//
|
|
// // message Foo {}
|
|
// message := NewMessage("Foo")
|
|
//
|
|
// No fields/enums/options are attached by default, use WithMessageFields, WithEnums,
|
|
// and WithMessageOptions to add them as required:
|
|
//
|
|
// // message Foo {
|
|
// // option (foo) = 1;
|
|
// // int64 Bar = 1;
|
|
// // }
|
|
// opt := NewOption("foo", "1")
|
|
// field := NewField("int64", "Bar", 1)
|
|
// message := NewMessage("Foo", WithMessageOptions(opt), WithFields(field))
|
|
//
|
|
// By default, options are added first, then fields and then enums.
|
|
func NewMessage(name string, opts ...MessageSpecOptions) *proto.Message {
|
|
m := MessageSpec{name: name}
|
|
for _, opt := range opts {
|
|
opt(&m)
|
|
}
|
|
message := &proto.Message{
|
|
Name: m.name,
|
|
Comment: defaultComment(name, "message"),
|
|
IsExtend: m.isExtend,
|
|
}
|
|
for _, opt := range m.options {
|
|
message.Elements = append(message.Elements, opt)
|
|
}
|
|
|
|
// Verify that fields have unique sequence? Though, again, protoc will shout if
|
|
// it isn't the case.
|
|
for _, field := range m.fields {
|
|
message.Elements = append(message.Elements, field)
|
|
}
|
|
for _, enum := range m.enums {
|
|
message.Elements = append(message.Elements, enum)
|
|
}
|
|
return message
|
|
}
|
|
|
|
// EnumFieldSpec holds information relevant to the enum field statement.
|
|
type EnumFieldSpec struct {
|
|
name string
|
|
value int
|
|
options []*proto.Option
|
|
}
|
|
|
|
// EnumFieldSpecOptions is a type alias for a callable accepting an EnumFieldSpec.
|
|
type EnumFieldSpecOptions func(f *EnumFieldSpec)
|
|
|
|
// WithEnumFieldOptions adds options to the enum field.
|
|
func WithEnumFieldOptions(options ...*proto.Option) EnumFieldSpecOptions {
|
|
return func(f *EnumFieldSpec) {
|
|
f.options = append(f.options, options...)
|
|
}
|
|
}
|
|
|
|
// NewEnumField creates a new enum field statement node:
|
|
//
|
|
// // BAR = 1;
|
|
// field := NewEnumField("BAR", 1)
|
|
//
|
|
// No options are attached by default, use WithEnumFieldOptions to add them as
|
|
// required:
|
|
//
|
|
// // BAR = 1 [option (foo) = 1];
|
|
// field := NewEnumField("BAR", 1, WithEnumFieldOptions(NewOption("foo", "1")))
|
|
func NewEnumField(name string, value int, opts ...EnumFieldSpecOptions) *proto.EnumField {
|
|
f := EnumFieldSpec{name: name, value: value}
|
|
for _, opt := range opts {
|
|
opt(&f)
|
|
}
|
|
|
|
field := &proto.EnumField{
|
|
Name: f.name,
|
|
Integer: f.value,
|
|
}
|
|
for _, opt := range f.options {
|
|
field.Elements = append(field.Elements, opt)
|
|
}
|
|
return field
|
|
}
|
|
|
|
// EnumSpec holds information relevant to the enum statement.
|
|
type EnumSpec struct {
|
|
name string
|
|
fields []*proto.EnumField
|
|
options []*proto.Option
|
|
}
|
|
|
|
// EnumSpecOpts is a type alias for a callable accepting an EnumSpec.
|
|
type EnumSpecOpts func(i *EnumSpec)
|
|
|
|
// WithEnumOptions adds options to the enum.
|
|
func WithEnumOptions(options ...*proto.Option) EnumSpecOpts {
|
|
return func(e *EnumSpec) {
|
|
e.options = append(e.options, options...)
|
|
}
|
|
}
|
|
|
|
// WithEnumFields adds fields to the enum.
|
|
func WithEnumFields(fields ...*proto.EnumField) EnumSpecOpts {
|
|
return func(e *EnumSpec) {
|
|
e.fields = append(e.fields, fields...)
|
|
}
|
|
}
|
|
|
|
// NewEnum creates a new enum statement node:
|
|
//
|
|
// // enum Foo {
|
|
// // BAR = 1;
|
|
// // }
|
|
// enum := NewEnum("Foo", WithEnumFields(NewEnumField("BAR", 1)))
|
|
//
|
|
// No options are attached by default, use WithEnumOptions to add them as
|
|
// required:
|
|
//
|
|
// // enum Foo {
|
|
// // BAR = 1 [option (foo) = 1];
|
|
// // }
|
|
// enum := NewEnum("Foo", WithEnumOptions(NewOption("foo", "1")), WithEnumFields(NewEnumField("BAR", 1)))
|
|
//
|
|
// By default, options are added first, then fields.
|
|
func NewEnum(name string, opts ...EnumSpecOpts) *proto.Enum {
|
|
e := EnumSpec{name: name}
|
|
for _, opt := range opts {
|
|
opt(&e)
|
|
}
|
|
enum := &proto.Enum{
|
|
Name: e.name,
|
|
Comment: defaultComment(name, "enum"),
|
|
}
|
|
for _, opt := range e.options {
|
|
enum.Elements = append(enum.Elements, opt)
|
|
}
|
|
for _, field := range e.fields {
|
|
enum.Elements = append(enum.Elements, field)
|
|
}
|
|
return enum
|
|
}
|
|
|
|
// OneofFieldSpec holds information relevant to the oneof field statement.
|
|
type OneofFieldSpec struct {
|
|
name, typename string
|
|
sequence int
|
|
options []*proto.Option
|
|
}
|
|
|
|
// OneofFieldOptions is a type alias for a callable accepting a OneOfField.
|
|
type OneofFieldOptions func(f *OneofFieldSpec)
|
|
|
|
// WithOneofFieldOptions adds options to the oneof field.
|
|
func WithOneofFieldOptions(options ...*proto.Option) OneofFieldOptions {
|
|
return func(f *OneofFieldSpec) {
|
|
f.options = append(f.options, options...)
|
|
}
|
|
}
|
|
|
|
// NewOneofField creates a new oneof field statement node:
|
|
//
|
|
// // Needs to placed in oneof block.
|
|
// // int32 Foo = 1;
|
|
// field := NewOneofField("Foo", "int32", 1)
|
|
//
|
|
// Additional options can be created and attached to the field to the field via
|
|
// WithOneOfFieldOptions:
|
|
//
|
|
// // int32 Foo = 1 [option (foo) = 1];
|
|
// field := NewOneofField("Foo", "int32", 1, WithOneOfFieldOptions(NewOption("foo", "1")))
|
|
func NewOneofField(name, typename string, sequence int, opts ...OneofFieldOptions) *proto.OneOfField {
|
|
f := OneofFieldSpec{name: name, typename: typename, sequence: sequence}
|
|
for _, opt := range opts {
|
|
opt(&f)
|
|
}
|
|
field := &proto.OneOfField{
|
|
Field: &proto.Field{
|
|
Name: f.name,
|
|
Sequence: f.sequence,
|
|
Type: f.typename,
|
|
Options: []*proto.Option{},
|
|
},
|
|
}
|
|
field.Options = append(field.Options, f.options...)
|
|
return field
|
|
}
|
|
|
|
// OneofSpec holds information relevant to the enum statement.
|
|
type OneofSpec struct {
|
|
name string
|
|
options []*proto.Option
|
|
fields []*proto.OneOfField
|
|
}
|
|
|
|
// OneofSpecOptions is a type alias for a callable accepting a OneOfSpec.
|
|
type OneofSpecOptions func(o *OneofSpec)
|
|
|
|
// WithOneofOptions adds options to the oneof.
|
|
func WithOneofOptions(options ...*proto.Option) OneofSpecOptions {
|
|
return func(o *OneofSpec) {
|
|
o.options = append(o.options, options...)
|
|
}
|
|
}
|
|
|
|
// WithOneofFields adds fields to the oneof.
|
|
func WithOneofFields(fields ...*proto.OneOfField) OneofSpecOptions {
|
|
return func(o *OneofSpec) {
|
|
o.fields = append(o.fields, fields...)
|
|
}
|
|
}
|
|
|
|
// NewOneof creates a new oneof statement node:
|
|
//
|
|
// // oneof Foo {
|
|
// // int32 Foo = 1;
|
|
// // }
|
|
// oneof := NewOneof("Foo", WithOneOfFields(NewOneOfField("Foo", "int32", 1)))
|
|
//
|
|
// No options are attached by default, use WithOneOfOptions to add them as required.
|
|
func NewOneof(name string, opts ...OneofSpecOptions) *proto.Oneof {
|
|
o := OneofSpec{name: name}
|
|
for _, opt := range opts {
|
|
opt(&o)
|
|
}
|
|
oneof := &proto.Oneof{
|
|
Name: o.name,
|
|
}
|
|
for _, opt := range o.options {
|
|
oneof.Elements = append(oneof.Elements, opt)
|
|
}
|
|
for _, field := range o.fields {
|
|
oneof.Elements = append(oneof.Elements, field)
|
|
}
|
|
return oneof
|
|
}
|
|
|
|
// AttachComment attaches a comment top level nodes. Currently only supports Messages, RPC's
|
|
// and Services. Silently ignores other nodes though they can easily be added by just appending
|
|
// a new case to the switch statement.
|
|
func AttachComment(n proto.Visitee, comment string) {
|
|
c := &proto.Comment{
|
|
// Attach a starting space here, i.e // text and not //text
|
|
Lines: []string{" " + comment},
|
|
}
|
|
switch n := n.(type) {
|
|
case *proto.Message:
|
|
n.Comment = c
|
|
case *proto.RPC:
|
|
n.Comment = c
|
|
case *proto.Service:
|
|
n.Comment = c
|
|
}
|
|
}
|
|
|
|
// Check if s is a string, exclude special cases of "false" and "true".
|
|
func isString(s string) bool {
|
|
if s == "true" || s == "false" {
|
|
return false
|
|
}
|
|
if _, err := strconv.ParseFloat(s, 64); err == nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// defaultComment creates a new default proto comment with name and type.
|
|
func defaultComment(name, protoType string) *proto.Comment {
|
|
return newComment(fmt.Sprintf(" %[1]v defines the %[1]v %[2]v.", name, protoType))
|
|
}
|
|
|
|
// newComment creates a new proto comment.
|
|
func newComment(text string) *proto.Comment {
|
|
return &proto.Comment{Lines: []string{text}}
|
|
}
|