Skip to content

Commit

Permalink
Add option to set template delimiters (#1499)
Browse files Browse the repository at this point in the history
* Add template action delimiter cli flag

* Add delims to generator config and template

Also adds tests using the "quote" test as a base. This has to have a
custom Instance name or it will clash with the "quotes" one and panic
since it will have registered two "swagger" instances in the package
test.

* Add testdata for custom delim flags

Based on the "quote" testdata.

* Add delims to the spec, with tests.

Make sure we don't add delims if they are empty. This shouldn't be
possible, but might as well be safe.

* Go mod tidy and sum update

* Make the CLI experience a bit cleaner

* Revert go.mod and sum

* Update readme
  • Loading branch information
leosunmo authored Apr 17, 2023
1 parent 3c5e486 commit 01d20cc
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 31 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ dist
testdata/simple*/docs
testdata/quotes/docs
testdata/quotes/quotes.so
testdata/delims/docs
testdata/delims/delims.so
example/basic/docs/*
example/celler/docs/*
cover.out
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ OPTIONS:
--overridesFile value File to read global type overrides from. (default: ".swaggo")
--parseGoList Parse dependency via 'go list' (default: true)
--tags value, -t value A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded
--templateDelims value, --td value Provide custom delimeters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]"
--collectionFormat value, --cf value Set default collection format (default: "csv")
--help, -h show help (default: false)
```
Expand Down Expand Up @@ -908,6 +909,18 @@ By default `swag` command generates Swagger specification in three different fil
If you would like to limit a set of file types which should be generated you can use `--outputTypes` (short `-ot`) flag. Default value is `go,json,yaml` - output types separated with comma. To limit output only to `go` and `yaml` files, you would write `go,yaml`. With complete command that would be `swag init --outputTypes go,yaml`.
### Change the default Go Template action delimiters
[#980](https://github.com/swaggo/swag/issues/980)
[#1177](https://github.com/swaggo/swag/issues/1177)
If your swagger annotations or struct fields contain "{{" or "}}", the template generation will most likely fail, as these are the default delimiters for [go templates](https://pkg.go.dev/text/template#Template.Delims).
To make the generation work properly, you can change the default delimiters with `-td`. For example:
```console
swag init -g http/api.go -td "[[,]]"
```
The new delimiter is a string with the format "`<left delimiter>`,`<right delimiter>`".
## About the Project
This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
## Contributors
Expand Down
21 changes: 21 additions & 0 deletions cmd/swag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
quietFlag = "quiet"
tagsFlag = "tags"
parseExtensionFlag = "parseExtension"
templateDelimsFlag = "templateDelims"
packageName = "packageName"
collectionFormatFlag = "collectionFormat"
)
Expand Down Expand Up @@ -143,6 +144,12 @@ var initFlags = []cli.Flag{
Value: "",
Usage: "A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded",
},
&cli.StringFlag{
Name: templateDelimsFlag,
Aliases: []string{"td"},
Value: "",
Usage: "Provide custom delimeters for Go template generation. The format is leftDelim,rightDelim. For example: \"[[,]]\"",
},
&cli.StringFlag{
Name: packageName,
Value: "",
Expand All @@ -165,6 +172,18 @@ func initAction(ctx *cli.Context) error {
return fmt.Errorf("not supported %s propertyStrategy", strategy)
}

leftDelim, rightDelim := "{{", "}}"

if ctx.IsSet(templateDelimsFlag) {
delims := strings.Split(ctx.String(templateDelimsFlag), ",")
if len(delims) != 2 {
return fmt.Errorf("exactly two template delimeters must be provided, comma separated")
} else if delims[0] == delims[1] {
return fmt.Errorf("template delimiters must be different")
}
leftDelim, rightDelim = strings.TrimSpace(delims[0]), strings.TrimSpace(delims[1])
}

outputTypes := strings.Split(ctx.String(outputTypesFlag), ",")
if len(outputTypes) == 0 {
return fmt.Errorf("no output types specified")
Expand Down Expand Up @@ -199,6 +218,8 @@ func initAction(ctx *cli.Context) error {
OverridesFile: ctx.String(overridesFileFlag),
ParseGoList: ctx.Bool(parseGoListFlag),
Tags: ctx.String(tagsFlag),
LeftTemplateDelim: leftDelim,
RightTemplateDelim: rightDelim,
PackageName: ctx.String(packageName),
Debugger: logger,
CollectionFormat: collectionFormat,
Expand Down
76 changes: 48 additions & 28 deletions gen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ type Config struct {
// include only tags mentioned when searching, comma separated
Tags string

// LeftTemplateDelim defines the left delimiter for the template generation
LeftTemplateDelim string

// RightTemplateDelim defines the right delimiter for the template generation
RightTemplateDelim string

// PackageName defines package name of generated `docs.go`
PackageName string

Expand All @@ -150,6 +156,14 @@ func (g *Gen) Build(config *Config) error {
}
}

if config.LeftTemplateDelim == "" {
config.LeftTemplateDelim = "{{"
}

if config.RightTemplateDelim == "" {
config.RightTemplateDelim = "}}"
}

var overrides map[string]string

if config.OverridesFile != "" {
Expand Down Expand Up @@ -377,7 +391,7 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swa
generator, err := template.New("swagger_info").Funcs(template.FuncMap{
"printDoc": func(v string) string {
// Add schemes
v = "{\n \"schemes\": {{ marshal .Schemes }}," + v[1:]
v = "{\n \"schemes\": " + config.LeftTemplateDelim + " marshal .Schemes " + config.RightTemplateDelim + "," + v[1:]
// Sanitize backticks
return strings.Replace(v, "`", "`+\"`\"+`", -1)
},
Expand All @@ -396,16 +410,16 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swa
Info: &spec.Info{
VendorExtensible: swagger.Info.VendorExtensible,
InfoProps: spec.InfoProps{
Description: "{{escape .Description}}",
Title: "{{.Title}}",
Description: config.LeftTemplateDelim + "escape .Description" + config.RightTemplateDelim,
Title: config.LeftTemplateDelim + ".Title" + config.RightTemplateDelim,
TermsOfService: swagger.Info.TermsOfService,
Contact: swagger.Info.Contact,
License: swagger.Info.License,
Version: "{{.Version}}",
Version: config.LeftTemplateDelim + ".Version" + config.RightTemplateDelim,
},
},
Host: "{{.Host}}",
BasePath: "{{.BasePath}}",
Host: config.LeftTemplateDelim + ".Host" + config.RightTemplateDelim,
BasePath: config.LeftTemplateDelim + ".BasePath" + config.RightTemplateDelim,
Paths: swagger.Paths,
Definitions: swagger.Definitions,
Parameters: swagger.Parameters,
Expand All @@ -426,29 +440,33 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swa
buffer := &bytes.Buffer{}

err = generator.Execute(buffer, struct {
Timestamp time.Time
Doc string
Host string
PackageName string
BasePath string
Title string
Description string
Version string
InstanceName string
Schemes []string
GeneratedTime bool
Timestamp time.Time
Doc string
Host string
PackageName string
BasePath string
Title string
Description string
Version string
InstanceName string
Schemes []string
GeneratedTime bool
LeftTemplateDelim string
RightTemplateDelim string
}{
Timestamp: time.Now(),
GeneratedTime: config.GeneratedTime,
Doc: string(buf),
Host: swagger.Host,
PackageName: packageName,
BasePath: swagger.BasePath,
Schemes: swagger.Schemes,
Title: swagger.Info.Title,
Description: swagger.Info.Description,
Version: swagger.Info.Version,
InstanceName: config.InstanceName,
Timestamp: time.Now(),
GeneratedTime: config.GeneratedTime,
Doc: string(buf),
Host: swagger.Host,
PackageName: packageName,
BasePath: swagger.BasePath,
Schemes: swagger.Schemes,
Title: swagger.Info.Title,
Description: swagger.Info.Description,
Version: swagger.Info.Version,
InstanceName: config.InstanceName,
LeftTemplateDelim: config.LeftTemplateDelim,
RightTemplateDelim: config.RightTemplateDelim,
})
if err != nil {
return err
Expand Down Expand Up @@ -480,6 +498,8 @@ var SwaggerInfo{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}
Description: {{ printf "%q" .Description}},
InfoInstanceName: {{ printf "%q" .InstanceName }},
SwaggerTemplate: docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }},
LeftDelim: {{ printf "%q" .LeftTemplateDelim}},
RightDelim: {{ printf "%q" .RightTemplateDelim}},
}
func init() {
Expand Down
61 changes: 61 additions & 0 deletions gen/gen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,67 @@ func TestGen_BuildDescriptionWithQuotes(t *testing.T) {
assert.JSONEq(t, string(expectedJSON), jsonOutput)
}

func TestGen_BuildDocCustomDelims(t *testing.T) {
config := &Config{
SearchDir: "../testdata/delims",
MainAPIFile: "./main.go",
OutputDir: "../testdata/delims/docs",
OutputTypes: outputTypes,
MarkdownFilesDir: "../testdata/delims",
InstanceName: "CustomDelims",
LeftTemplateDelim: "{%",
RightTemplateDelim: "%}",
}

require.NoError(t, New().Build(config))

expectedFiles := []string{
filepath.Join(config.OutputDir, "CustomDelims_docs.go"),
filepath.Join(config.OutputDir, "CustomDelims_swagger.json"),
filepath.Join(config.OutputDir, "CustomDelims_swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
}

cmd := exec.Command("go", "build", "-buildmode=plugin", "github.com/swaggo/swag/testdata/delims")

cmd.Dir = config.SearchDir

output, err := cmd.CombinedOutput()
if err != nil {
require.NoError(t, err, string(output))
}

p, err := plugin.Open(filepath.Join(config.SearchDir, "delims.so"))
if err != nil {
require.NoError(t, err)
}

defer os.Remove("delims.so")

readDoc, err := p.Lookup("ReadDoc")
if err != nil {
require.NoError(t, err)
}

jsonOutput := readDoc.(func() string)()

var jsonDoc interface{}
if err := json.Unmarshal([]byte(jsonOutput), &jsonDoc); err != nil {
require.NoError(t, err)
}

expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "expected.json"))
if err != nil {
require.NoError(t, err)
}

assert.JSONEq(t, string(expectedJSON), jsonOutput)
}

func TestGen_jsonIndent(t *testing.T) {
config := &Config{
SearchDir: searchDir,
Expand Down
14 changes: 11 additions & 3 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ type Spec struct {
Description string
InfoInstanceName string
SwaggerTemplate string
LeftDelim string
RightDelim string
}

// ReadDoc parses SwaggerTemplate into swagger document.
func (i *Spec) ReadDoc() string {
i.Description = strings.ReplaceAll(i.Description, "\n", "\\n")

tpl, err := template.New("swagger_info").Funcs(template.FuncMap{
tpl := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)

Expand All @@ -37,13 +39,19 @@ func (i *Spec) ReadDoc() string {

return strings.ReplaceAll(str, "\\\\\"", "\\\\\\\"")
},
}).Parse(i.SwaggerTemplate)
})

if i.LeftDelim != "" && i.RightDelim != "" {
tpl = tpl.Delims(i.LeftDelim, i.RightDelim)
}

parsed, err := tpl.Parse(i.SwaggerTemplate)
if err != nil {
return i.SwaggerTemplate
}

var doc bytes.Buffer
if err = tpl.Execute(&doc, i); err != nil {
if err = parsed.Execute(&doc, i); err != nil {
return i.SwaggerTemplate
}

Expand Down
35 changes: 35 additions & 0 deletions spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ func TestSpec_ReadDoc(t *testing.T) {
Description string
InfoInstanceName string
SwaggerTemplate string
LeftDelim string
RightDelim string
}

tests := []struct {
Expand Down Expand Up @@ -132,6 +134,37 @@ func TestSpec_ReadDoc(t *testing.T) {
},
want: "{{ .Schemesa }}",
},
{
name: "TestReadDocCustomDelims",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName",
SwaggerTemplate: `{
"swagger": "2.0",
"info": {
"description": "{%escape .Description%}",
"title": "{%.Title%}",
"version": "{%.Version%}"
},
"host": "{%.Host%}",
"basePath": "{%.BasePath%}",
}`,
LeftDelim: "{%",
RightDelim: "%}",
},
want: "{" +
"\n\t\t\t\"swagger\": \"2.0\"," +
"\n\t\t\t\"info\": {" +
"\n\t\t\t\t\"description\": \"\",\n\t\t\t\t\"" +
"title\": \"\"," +
"\n\t\t\t\t\"version\": \"1.0\"" +
"\n\t\t\t}," +
"\n\t\t\t\"host\": \"localhost:8080\"," +
"\n\t\t\t\"basePath\": \"/\"," +
"\n\t\t}",
},
}

for _, tt := range tests {
Expand All @@ -145,6 +178,8 @@ func TestSpec_ReadDoc(t *testing.T) {
Description: tt.fields.Description,
InfoInstanceName: tt.fields.InfoInstanceName,
SwaggerTemplate: tt.fields.SwaggerTemplate,
LeftDelim: tt.fields.LeftDelim,
RightDelim: tt.fields.RightDelim,
}

assert.Equal(t, tt.want, doc.ReadDoc())
Expand Down
11 changes: 11 additions & 0 deletions testdata/delims/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package api

// MyFunc godoc
// @Description My Function
// @Success 200 {object} MyStruct
// @Router /myfunc [get]
func MyFunc() {}

type MyStruct struct {
URLTemplate string `json:"urltemplate" example:"http://example.org/{{ path }}" swaggertype:"string"`
}
Loading

0 comments on commit 01d20cc

Please sign in to comment.