diff --git a/.gitignore b/.gitignore index 0a89e775..60e1c027 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ /test/*.dat /vendor /tmp/ +.idea +*.iml \ No newline at end of file diff --git a/template.go b/template.go index bea058cc..32e305a6 100644 --- a/template.go +++ b/template.go @@ -3,6 +3,7 @@ package gendoc import ( "encoding/json" "fmt" + "html/template" "sort" "strings" @@ -27,7 +28,7 @@ func NewTemplate(descs []*protokit.FileDescriptor) *Template { for _, f := range descs { file := &File{ Name: f.GetName(), - Description: description(f.GetSyntaxComments().String()), + Description: template.HTML(description(f.GetSyntaxComments().String())), Package: f.GetPackage(), HasEnums: len(f.Enums) > 0, HasExtensions: len(f.Extensions) > 0, @@ -128,9 +129,9 @@ func extractOptions(opts commonOptions) map[string]interface{} { // // In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty. type File struct { - Name string `json:"name"` - Description string `json:"description"` - Package string `json:"package"` + Name string `json:"name"` + Description template.HTML `json:"description"` + Package string `json:"package"` HasEnums bool `json:"hasEnums"` HasExtensions bool `json:"hasExtensions"` @@ -150,29 +151,29 @@ func (f File) Option(name string) interface{} { return f.Options[name] } // FileExtension contains details about top-level extensions within a proto(2) file. type FileExtension struct { - Name string `json:"name"` - LongName string `json:"longName"` - FullName string `json:"fullName"` - Description string `json:"description"` - Label string `json:"label"` - Type string `json:"type"` - LongType string `json:"longType"` - FullType string `json:"fullType"` - Number int `json:"number"` - DefaultValue string `json:"defaultValue"` - ContainingType string `json:"containingType"` - ContainingLongType string `json:"containingLongType"` - ContainingFullType string `json:"containingFullType"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description template.HTML `json:"description"` + Label string `json:"label"` + Type string `json:"type"` + LongType string `json:"longType"` + FullType string `json:"fullType"` + Number int `json:"number"` + DefaultValue string `json:"defaultValue"` + ContainingType string `json:"containingType"` + ContainingLongType string `json:"containingLongType"` + ContainingFullType string `json:"containingFullType"` } // Message contains details about a protobuf message. // // In the case of proto3 files, HasExtensions will always be false, and Extensions will be empty. type Message struct { - Name string `json:"name"` - LongName string `json:"longName"` - FullName string `json:"fullName"` - Description string `json:"description"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description template.HTML `json:"description"` HasExtensions bool `json:"hasExtensions"` HasFields bool `json:"hasFields"` @@ -226,16 +227,16 @@ func (m Message) FieldsWithOption(optionName string) []*MessageField { // In the case of proto3 files, DefaultValue will always be empty. Similarly, label will be empty unless the field is // repeated (in which case it'll be "repeated"). type MessageField struct { - Name string `json:"name"` - Description string `json:"description"` - Label string `json:"label"` - Type string `json:"type"` - LongType string `json:"longType"` - FullType string `json:"fullType"` - IsMap bool `json:"ismap"` - IsOneof bool `json:"isoneof"` - OneofDecl string `json:"oneofdecl"` - DefaultValue string `json:"defaultValue"` + Name string `json:"name"` + Description template.HTML `json:"description"` + Label string `json:"label"` + Type string `json:"type"` + LongType string `json:"longType"` + FullType string `json:"fullType"` + IsMap bool `json:"ismap"` + IsOneof bool `json:"isoneof"` + OneofDecl string `json:"oneofdecl"` + DefaultValue string `json:"defaultValue"` Options map[string]interface{} `json:"options,omitempty"` } @@ -254,11 +255,11 @@ type MessageExtension struct { // Enum contains details about enumerations. These can be either top level enums, or nested (defined within a message). type Enum struct { - Name string `json:"name"` - LongName string `json:"longName"` - FullName string `json:"fullName"` - Description string `json:"description"` - Values []*EnumValue `json:"values"` + Name string `json:"name"` + LongName string `json:"longName"` + FullName string `json:"fullName"` + Description template.HTML `json:"description"` + Values []*EnumValue `json:"values"` Options map[string]interface{} `json:"options,omitempty"` } @@ -302,9 +303,9 @@ func (e Enum) ValuesWithOption(optionName string) []*EnumValue { // EnumValue contains details about an individual value within an enumeration. type EnumValue struct { - Name string `json:"name"` - Number string `json:"number"` - Description string `json:"description"` + Name string `json:"name"` + Number string `json:"number"` + Description template.HTML `json:"description"` Options map[string]interface{} `json:"options,omitempty"` } @@ -317,7 +318,7 @@ type Service struct { Name string `json:"name"` LongName string `json:"longName"` FullName string `json:"fullName"` - Description string `json:"description"` + Description template.HTML `json:"description"` Methods []*ServiceMethod `json:"methods"` Options map[string]interface{} `json:"options,omitempty"` @@ -362,16 +363,16 @@ func (s Service) MethodsWithOption(optionName string) []*ServiceMethod { // ServiceMethod contains details about an individual method within a service. type ServiceMethod struct { - Name string `json:"name"` - Description string `json:"description"` - RequestType string `json:"requestType"` - RequestLongType string `json:"requestLongType"` - RequestFullType string `json:"requestFullType"` - RequestStreaming bool `json:"requestStreaming"` - ResponseType string `json:"responseType"` - ResponseLongType string `json:"responseLongType"` - ResponseFullType string `json:"responseFullType"` - ResponseStreaming bool `json:"responseStreaming"` + Name string `json:"name"` + Description template.HTML `json:"description"` + RequestType string `json:"requestType"` + RequestLongType string `json:"requestLongType"` + RequestFullType string `json:"requestFullType"` + RequestStreaming bool `json:"requestStreaming"` + ResponseType string `json:"responseType"` + ResponseLongType string `json:"responseLongType"` + ResponseFullType string `json:"responseFullType"` + ResponseStreaming bool `json:"responseStreaming"` Options map[string]interface{} `json:"options,omitempty"` } @@ -401,7 +402,7 @@ func parseEnum(pe *protokit.EnumDescriptor) *Enum { Name: pe.GetName(), LongName: pe.GetLongName(), FullName: pe.GetFullName(), - Description: description(pe.GetComments().String()), + Description: template.HTML(description(pe.GetComments().String())), Options: mergeOptions(extractOptions(pe.GetOptions()), extensions.Transform(pe.OptionExtensions)), } @@ -409,7 +410,7 @@ func parseEnum(pe *protokit.EnumDescriptor) *Enum { enum.Values = append(enum.Values, &EnumValue{ Name: val.GetName(), Number: fmt.Sprint(val.GetNumber()), - Description: description(val.GetComments().String()), + Description: template.HTML(description(val.GetComments().String())), Options: mergeOptions(extractOptions(val.GetOptions()), extensions.Transform(val.OptionExtensions)), }) } @@ -424,7 +425,7 @@ func parseFileExtension(pe *protokit.ExtensionDescriptor) *FileExtension { Name: pe.GetName(), LongName: pe.GetLongName(), FullName: pe.GetFullName(), - Description: description(pe.GetComments().String()), + Description: template.HTML(description(pe.GetComments().String())), Label: labelName(pe.GetLabel(), pe.IsProto3(), pe.GetProto3Optional()), Type: t, LongType: lt, @@ -442,7 +443,7 @@ func parseMessage(pm *protokit.Descriptor) *Message { Name: pm.GetName(), LongName: pm.GetLongName(), FullName: pm.GetFullName(), - Description: description(pm.GetComments().String()), + Description: template.HTML(description(pm.GetComments().String())), HasExtensions: len(pm.GetExtensions()) > 0, HasFields: len(pm.GetMessageFields()) > 0, HasOneofs: len(pm.GetOneofDecl()) > 0, @@ -476,7 +477,7 @@ func parseMessageField(pf *protokit.FieldDescriptor, oneofDecls []*descriptor.On m := &MessageField{ Name: pf.GetName(), - Description: description(pf.GetComments().String()), + Description: template.HTML(description(pf.GetComments().String())), Label: labelName(pf.GetLabel(), pf.IsProto3(), pf.GetProto3Optional()), Type: t, LongType: lt, @@ -509,7 +510,7 @@ func parseService(ps *protokit.ServiceDescriptor) *Service { Name: ps.GetName(), LongName: ps.GetLongName(), FullName: ps.GetFullName(), - Description: description(ps.GetComments().String()), + Description: template.HTML(description(ps.GetComments().String())), Options: mergeOptions(extractOptions(ps.GetOptions()), extensions.Transform(ps.OptionExtensions)), } @@ -523,7 +524,7 @@ func parseService(ps *protokit.ServiceDescriptor) *Service { func parseServiceMethod(pm *protokit.MethodDescriptor) *ServiceMethod { return &ServiceMethod{ Name: pm.GetName(), - Description: description(pm.GetComments().String()), + Description: template.HTML(description(pm.GetComments().String())), RequestType: baseName(pm.GetInputType()), RequestLongType: strings.TrimPrefix(pm.GetInputType(), "."+pm.GetPackage()+"."), RequestFullType: strings.TrimPrefix(pm.GetInputType(), "."), diff --git a/template_test.go b/template_test.go index 717919b2..7279ac9f 100644 --- a/template_test.go +++ b/template_test.go @@ -1,6 +1,7 @@ package gendoc_test import ( + html "html/template" "os" "testing" @@ -130,7 +131,7 @@ func TestTemplateProperties(t *testing.T) { func TestFileProperties(t *testing.T) { require.Equal(t, "Booking.proto", bookingFile.Name) - require.Equal(t, "Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional.", bookingFile.Description) + require.Equal(t, html.HTML("Booking related messages.\n\nThis file is really just an example. The data model is completely\nfictional."), bookingFile.Description) require.Equal(t, "com.example", bookingFile.Package) require.True(t, bookingFile.HasEnums) require.True(t, bookingFile.HasExtensions) @@ -145,7 +146,7 @@ func TestFileEnumProperties(t *testing.T) { require.Equal(t, "StatusCode", enum.Name) require.Equal(t, "BookingStatus.StatusCode", enum.LongName) require.Equal(t, "com.example.BookingStatus.StatusCode", enum.FullName) - require.Equal(t, "A flag for the status result.", enum.Description) + require.Equal(t, html.HTML("A flag for the status result."), enum.Description) require.Len(t, enum.Values, 2) expectedValues := []*EnumValue{ @@ -176,7 +177,7 @@ func TestFileExtensionProperties(t *testing.T) { require.Equal(t, "country", ext.Name) require.Equal(t, "BookingStatus.country", ext.LongName) require.Equal(t, "com.example.BookingStatus.country", ext.FullName) - require.Equal(t, "The country the booking occurred in.", ext.Description) + require.Equal(t, html.HTML("The country the booking occurred in."), ext.Description) require.Equal(t, "optional", ext.Label) require.Equal(t, "string", ext.Type) require.Equal(t, "string", ext.LongType) @@ -193,7 +194,7 @@ func TestMessageProperties(t *testing.T) { require.Equal(t, "Vehicle", msg.Name) require.Equal(t, "Vehicle", msg.LongName) require.Equal(t, "com.example.Vehicle", msg.FullName) - require.Equal(t, "Represents a vehicle that can be hired.", msg.Description) + require.Equal(t, html.HTML("Represents a vehicle that can be hired."), msg.Description) require.False(t, msg.HasExtensions) require.True(t, msg.HasFields) require.NotEmpty(t, msg.Options) @@ -207,7 +208,7 @@ func TestNestedMessageProperties(t *testing.T) { require.Equal(t, "Category", msg.Name) require.Equal(t, "Vehicle.Category", msg.LongName) require.Equal(t, "com.example.Vehicle.Category", msg.FullName) - require.Equal(t, "Represents a vehicle category. E.g. \"Sedan\" or \"Truck\".", msg.Description) + require.Equal(t, html.HTML("Represents a vehicle category. E.g. \"Sedan\" or \"Truck\"."), msg.Description) require.False(t, msg.HasExtensions) require.True(t, msg.HasFields) } @@ -225,7 +226,7 @@ func TestMessageExtensionProperties(t *testing.T) { require.Equal(t, "optional_field_1", ext.Name) require.Equal(t, "BookingStatus.optional_field_1", ext.LongName) require.Equal(t, "com.example.BookingStatus.optional_field_1", ext.FullName) - require.Equal(t, "An optional field to be used however you please.", ext.Description) + require.Equal(t, html.HTML("An optional field to be used however you please."), ext.Description) require.Equal(t, "optional", ext.Label) require.Equal(t, "string", ext.Type) require.Equal(t, "string", ext.LongType) @@ -245,7 +246,7 @@ func TestFieldProperties(t *testing.T) { field := findField("id", msg) require.Equal(t, "id", field.Name) - require.Equal(t, "Unique booking status ID.", field.Description) + require.Equal(t, html.HTML("Unique booking status ID."), field.Description) require.Equal(t, "required", field.Label) require.Equal(t, "int32", field.Type) require.Equal(t, "int32", field.LongType) @@ -257,7 +258,7 @@ func TestFieldProperties(t *testing.T) { field = findField("status_code", msg) require.Equal(t, "status_code", field.Name) - require.Equal(t, "The status of this status?", field.Description) + require.Equal(t, html.HTML("The status of this status?"), field.Description) require.Equal(t, "optional", field.Label) require.Equal(t, "StatusCode", field.Type) require.Equal(t, "BookingStatus.StatusCode", field.LongType) @@ -267,7 +268,7 @@ func TestFieldProperties(t *testing.T) { field = findField("category", findMessage("Vehicle", vehicleFile)) require.Equal(t, "category", field.Name) - require.Equal(t, "Vehicle category.", field.Description) + require.Equal(t, html.HTML("Vehicle category."), field.Description) require.Empty(t, field.Label) // proto3, neither required, nor optional are valid require.Equal(t, "Category", field.Type) require.Equal(t, "Vehicle.Category", field.LongType) @@ -320,7 +321,7 @@ func TestFieldPropertiesProto3(t *testing.T) { field := findField("id", msg) require.Equal(t, "id", field.Name) - require.Equal(t, "The unique model ID.", field.Description) + require.Equal(t, html.HTML("The unique model ID."), field.Description) require.Equal(t, "", field.Label) require.Equal(t, "string", field.Type) require.Equal(t, "string", field.LongType) @@ -330,7 +331,7 @@ func TestFieldPropertiesProto3(t *testing.T) { field = findField("model_code", msg) require.Equal(t, "model_code", field.Name) - require.Equal(t, "The car model code, e.g. \"PZ003\".", field.Description) + require.Equal(t, html.HTML("The car model code, e.g. \"PZ003\"."), field.Description) require.Equal(t, "", field.Label) require.Equal(t, "string", field.Type) require.Equal(t, "string", field.LongType) @@ -340,7 +341,7 @@ func TestFieldPropertiesProto3(t *testing.T) { field = findField("daily_hire_rate_dollars", msg) require.Equal(t, "daily_hire_rate_dollars", field.Name) - require.Equal(t, "Dollars per day.", field.Description) + require.Equal(t, html.HTML("Dollars per day."), field.Description) require.Equal(t, "", field.Label) require.Equal(t, "sint32", field.Type) require.Equal(t, "sint32", field.LongType) @@ -354,7 +355,7 @@ func TestFieldPropertiesProto3Optional(t *testing.T) { field := findField("id", msg) require.Equal(t, "id", field.Name) - require.Equal(t, "The id of the cookie.", field.Description) + require.Equal(t, html.HTML("The id of the cookie."), field.Description) require.Equal(t, "", field.Label) require.Equal(t, "string", field.Type) require.Equal(t, "string", field.LongType) @@ -364,7 +365,7 @@ func TestFieldPropertiesProto3Optional(t *testing.T) { field = findField("name", msg) require.Equal(t, "name", field.Name) - require.Equal(t, "The name of the cookie.", field.Description) + require.Equal(t, html.HTML("The name of the cookie."), field.Description) require.Equal(t, "optional", field.Label) require.Equal(t, "string", field.Type) require.Equal(t, "string", field.LongType) @@ -374,7 +375,7 @@ func TestFieldPropertiesProto3Optional(t *testing.T) { field = findField("ingredients", msg) require.Equal(t, "ingredients", field.Name) - require.Equal(t, "Ingredients in the cookie.", field.Description) + require.Equal(t, html.HTML("Ingredients in the cookie."), field.Description) require.Equal(t, "repeated", field.Label) require.Equal(t, "string", field.Type) require.Equal(t, "string", field.LongType) @@ -388,7 +389,7 @@ func TestServiceProperties(t *testing.T) { require.Equal(t, "VehicleService", service.Name) require.Equal(t, "VehicleService", service.LongName) require.Equal(t, "com.example.VehicleService", service.FullName) - require.Equal(t, "The vehicle service.\n\nManages vehicles and such...", service.Description) + require.Equal(t, html.HTML("The vehicle service.\n\nManages vehicles and such..."), service.Description) require.Len(t, service.Methods, 3) require.NotEmpty(t, service.Options) require.True(t, *service.Option(E_ExtendService.Name).(*bool)) @@ -401,7 +402,7 @@ func TestServiceMethodProperties(t *testing.T) { method := findServiceMethod("AddModels", service) require.Equal(t, "AddModels", method.Name) - require.Equal(t, "creates models", method.Description) + require.Equal(t, html.HTML("creates models"), method.Description) require.Equal(t, "Model", method.RequestType) require.Equal(t, "Model", method.RequestLongType) require.Equal(t, "com.example.Model", method.RequestFullType) @@ -413,7 +414,7 @@ func TestServiceMethodProperties(t *testing.T) { method = findServiceMethod("GetVehicle", service) require.Equal(t, "GetVehicle", method.Name) - require.Equal(t, "Looks up a vehicle by id.", method.Description) + require.Equal(t, html.HTML("Looks up a vehicle by id."), method.Description) require.Equal(t, "FindVehicleById", method.RequestType) require.Equal(t, "FindVehicleById", method.RequestLongType) require.Equal(t, "com.example.FindVehicleById", method.RequestFullType) @@ -433,7 +434,7 @@ func TestExcludedComments(t *testing.T) { require.Empty(t, findField("value", message).Description) // just checking that it doesn't exclude everything - require.Equal(t, "the id of this message.", findField("id", message).Description) + require.Equal(t, html.HTML("the id of this message."), findField("id", message).Description) } func findService(name string, f *File) *Service {