github.com/go-swagger/go-swagger@v0.31.0/generator/template_repo.go (about)

     1  package generator
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"math"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"text/template"
    17  	"text/template/parse"
    18  	"unicode"
    19  
    20  	"github.com/Masterminds/sprig/v3"
    21  	"github.com/go-openapi/inflect"
    22  	"github.com/go-openapi/runtime"
    23  	"github.com/go-openapi/swag"
    24  	"github.com/kr/pretty"
    25  )
    26  
    27  var (
    28  	assets             map[string][]byte
    29  	protectedTemplates map[string]bool
    30  
    31  	// FuncMapFunc yields a map with all functions for templates
    32  	FuncMapFunc func(*LanguageOpts) template.FuncMap
    33  
    34  	templates *Repository
    35  
    36  	docFormat map[string]string
    37  )
    38  
    39  func initTemplateRepo() {
    40  	FuncMapFunc = DefaultFuncMap
    41  
    42  	// this makes the ToGoName func behave with the special
    43  	// prefixing rule above
    44  	swag.GoNamePrefixFunc = prefixForName
    45  
    46  	assets = defaultAssets()
    47  	protectedTemplates = defaultProtectedTemplates()
    48  	templates = NewRepository(FuncMapFunc(DefaultLanguageFunc()))
    49  
    50  	docFormat = map[string]string{
    51  		"binary": "binary (byte stream)",
    52  		"byte":   "byte (base64 string)",
    53  	}
    54  }
    55  
    56  // DefaultFuncMap yields a map with default functions for use in the templates.
    57  // These are available in every template
    58  func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
    59  	f := sprig.TxtFuncMap()
    60  	extra := template.FuncMap{
    61  		"pascalize": pascalize,
    62  		"camelize":  swag.ToJSONName,
    63  		"varname":   lang.MangleVarName,
    64  		"humanize":  swag.ToHumanNameLower,
    65  		"snakize":   lang.MangleFileName,
    66  		"toPackagePath": func(name string) string {
    67  			return filepath.FromSlash(lang.ManglePackagePath(name, ""))
    68  		},
    69  		"toPackage": func(name string) string {
    70  			return lang.ManglePackagePath(name, "")
    71  		},
    72  		"toPackageName": func(name string) string {
    73  			return lang.ManglePackageName(name, "")
    74  		},
    75  		"dasherize":          swag.ToCommandName,
    76  		"pluralizeFirstWord": pluralizeFirstWord,
    77  		"json":               asJSON,
    78  		"prettyjson":         asPrettyJSON,
    79  		"hasInsecure": func(arg []string) bool {
    80  			return swag.ContainsStringsCI(arg, "http") || swag.ContainsStringsCI(arg, "ws")
    81  		},
    82  		"hasSecure": func(arg []string) bool {
    83  			return swag.ContainsStringsCI(arg, "https") || swag.ContainsStringsCI(arg, "wss")
    84  		},
    85  		"dropPackage":      dropPackage,
    86  		"containsPkgStr":   containsPkgStr,
    87  		"contains":         swag.ContainsStrings,
    88  		"padSurround":      padSurround,
    89  		"joinFilePath":     filepath.Join,
    90  		"joinPath":         path.Join,
    91  		"comment":          padComment,
    92  		"blockcomment":     blockComment,
    93  		"inspect":          pretty.Sprint,
    94  		"cleanPath":        path.Clean,
    95  		"mediaTypeName":    mediaMime,
    96  		"mediaGoName":      mediaGoName,
    97  		"arrayInitializer": lang.arrayInitializer,
    98  		"hasPrefix":        strings.HasPrefix,
    99  		"stringContains":   strings.Contains,
   100  		"imports":          lang.imports,
   101  		"dict":             dict,
   102  		"isInteger":        isInteger,
   103  		"escapeBackticks": func(arg string) string {
   104  			return strings.ReplaceAll(arg, "`", "`+\"`\"+`")
   105  		},
   106  		"paramDocType": func(param GenParameter) string {
   107  			return resolvedDocType(param.SwaggerType, param.SwaggerFormat, param.Child)
   108  		},
   109  		"headerDocType": func(header GenHeader) string {
   110  			return resolvedDocType(header.SwaggerType, header.SwaggerFormat, header.Child)
   111  		},
   112  		"schemaDocType": func(in interface{}) string {
   113  			switch schema := in.(type) {
   114  			case GenSchema:
   115  				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
   116  			case *GenSchema:
   117  				if schema == nil {
   118  					return ""
   119  				}
   120  				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
   121  			case GenDefinition:
   122  				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
   123  			case *GenDefinition:
   124  				if schema == nil {
   125  					return ""
   126  				}
   127  				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
   128  			default:
   129  				panic("dev error: schemaDocType should be called with GenSchema or GenDefinition")
   130  			}
   131  		},
   132  		"schemaDocMapType": func(schema GenSchema) string {
   133  			return resolvedDocElemType("object", schema.SwaggerFormat, &schema.resolvedType)
   134  		},
   135  		"docCollectionFormat": resolvedDocCollectionFormat,
   136  		"trimSpace":           strings.TrimSpace,
   137  		"mdBlock":             markdownBlock, // markdown block
   138  		"httpStatus":          httpStatus,
   139  		"cleanupEnumVariant":  cleanupEnumVariant,
   140  		"gt0":                 gt0,
   141  		"path":                errorPath,
   142  		"cmdName": func(in interface{}) (string, error) {
   143  			// builds the name of a CLI command for a single operation
   144  			op, isOperation := in.(GenOperation)
   145  			if !isOperation {
   146  				ptr, ok := in.(*GenOperation)
   147  				if !ok {
   148  					return "", fmt.Errorf("cmdName should be called on a GenOperation, but got: %T", in)
   149  				}
   150  				op = *ptr
   151  			}
   152  			name := "Operation" + pascalize(op.Package) + pascalize(op.Name) + "Cmd"
   153  
   154  			return name, nil // TODO
   155  		},
   156  		"cmdGroupName": func(in interface{}) (string, error) {
   157  			// builds the name of a group of CLI commands
   158  			opGroup, ok := in.(GenOperationGroup)
   159  			if !ok {
   160  				return "", fmt.Errorf("cmdGroupName should be called on a GenOperationGroup, but got: %T", in)
   161  			}
   162  			name := "GroupOfOperations" + pascalize(opGroup.Name) + "Cmd"
   163  
   164  			return name, nil // TODO
   165  		},
   166  		"flagNameVar": func(in string) string {
   167  			// builds a flag name variable in CLI commands
   168  			return fmt.Sprintf("flag%sName", pascalize(in))
   169  		},
   170  		"flagValueVar": func(in string) string {
   171  			// builds a flag value variable in CLI commands
   172  			return fmt.Sprintf("flag%sValue", pascalize(in))
   173  		},
   174  		"flagDefaultVar": func(in string) string {
   175  			// builds a flag default value variable in CLI commands
   176  			return fmt.Sprintf("flag%sDefault", pascalize(in))
   177  		},
   178  		"flagModelVar": func(in string) string {
   179  			// builds a flag model variable in CLI commands
   180  			return fmt.Sprintf("flag%sModel", pascalize(in))
   181  		},
   182  		"flagDescriptionVar": func(in string) string {
   183  			// builds a flag description variable in CLI commands
   184  			return fmt.Sprintf("flag%sDescription", pascalize(in))
   185  		},
   186  	}
   187  
   188  	for k, v := range extra {
   189  		f[k] = v
   190  	}
   191  
   192  	return f
   193  }
   194  
   195  func defaultAssets() map[string][]byte {
   196  	return map[string][]byte{
   197  		// schema validation templates
   198  		"validation/primitive.gotmpl":    MustAsset("templates/validation/primitive.gotmpl"),
   199  		"validation/customformat.gotmpl": MustAsset("templates/validation/customformat.gotmpl"),
   200  		"validation/structfield.gotmpl":  MustAsset("templates/validation/structfield.gotmpl"),
   201  		"structfield.gotmpl":             MustAsset("templates/structfield.gotmpl"),
   202  		"schemavalidator.gotmpl":         MustAsset("templates/schemavalidator.gotmpl"),
   203  		"schemapolymorphic.gotmpl":       MustAsset("templates/schemapolymorphic.gotmpl"),
   204  		"schemaembedded.gotmpl":          MustAsset("templates/schemaembedded.gotmpl"),
   205  		"validation/minimum.gotmpl":      MustAsset("templates/validation/minimum.gotmpl"),
   206  		"validation/maximum.gotmpl":      MustAsset("templates/validation/maximum.gotmpl"),
   207  		"validation/multipleOf.gotmpl":   MustAsset("templates/validation/multipleOf.gotmpl"),
   208  
   209  		// schema serialization templates
   210  		"additionalpropertiesserializer.gotmpl": MustAsset("templates/serializers/additionalpropertiesserializer.gotmpl"),
   211  		"aliasedserializer.gotmpl":              MustAsset("templates/serializers/aliasedserializer.gotmpl"),
   212  		"allofserializer.gotmpl":                MustAsset("templates/serializers/allofserializer.gotmpl"),
   213  		"basetypeserializer.gotmpl":             MustAsset("templates/serializers/basetypeserializer.gotmpl"),
   214  		"marshalbinaryserializer.gotmpl":        MustAsset("templates/serializers/marshalbinaryserializer.gotmpl"),
   215  		"schemaserializer.gotmpl":               MustAsset("templates/serializers/schemaserializer.gotmpl"),
   216  		"subtypeserializer.gotmpl":              MustAsset("templates/serializers/subtypeserializer.gotmpl"),
   217  		"tupleserializer.gotmpl":                MustAsset("templates/serializers/tupleserializer.gotmpl"),
   218  
   219  		// schema generation template
   220  		"docstring.gotmpl":  MustAsset("templates/docstring.gotmpl"),
   221  		"schematype.gotmpl": MustAsset("templates/schematype.gotmpl"),
   222  		"schemabody.gotmpl": MustAsset("templates/schemabody.gotmpl"),
   223  		"schema.gotmpl":     MustAsset("templates/schema.gotmpl"),
   224  		"model.gotmpl":      MustAsset("templates/model.gotmpl"),
   225  		"header.gotmpl":     MustAsset("templates/header.gotmpl"),
   226  
   227  		// simple schema generation helpers templates
   228  		"simpleschema/defaultsvar.gotmpl":  MustAsset("templates/simpleschema/defaultsvar.gotmpl"),
   229  		"simpleschema/defaultsinit.gotmpl": MustAsset("templates/simpleschema/defaultsinit.gotmpl"),
   230  
   231  		"swagger_json_embed.gotmpl": MustAsset("templates/swagger_json_embed.gotmpl"),
   232  
   233  		// server templates
   234  		"server/parameter.gotmpl":        MustAsset("templates/server/parameter.gotmpl"),
   235  		"server/urlbuilder.gotmpl":       MustAsset("templates/server/urlbuilder.gotmpl"),
   236  		"server/responses.gotmpl":        MustAsset("templates/server/responses.gotmpl"),
   237  		"server/operation.gotmpl":        MustAsset("templates/server/operation.gotmpl"),
   238  		"server/builder.gotmpl":          MustAsset("templates/server/builder.gotmpl"),
   239  		"server/server.gotmpl":           MustAsset("templates/server/server.gotmpl"),
   240  		"server/configureapi.gotmpl":     MustAsset("templates/server/configureapi.gotmpl"),
   241  		"server/autoconfigureapi.gotmpl": MustAsset("templates/server/autoconfigureapi.gotmpl"),
   242  		"server/main.gotmpl":             MustAsset("templates/server/main.gotmpl"),
   243  		"server/doc.gotmpl":              MustAsset("templates/server/doc.gotmpl"),
   244  
   245  		// client templates
   246  		"client/parameter.gotmpl": MustAsset("templates/client/parameter.gotmpl"),
   247  		"client/response.gotmpl":  MustAsset("templates/client/response.gotmpl"),
   248  		"client/client.gotmpl":    MustAsset("templates/client/client.gotmpl"),
   249  		"client/facade.gotmpl":    MustAsset("templates/client/facade.gotmpl"),
   250  
   251  		"markdown/docs.gotmpl": MustAsset("templates/markdown/docs.gotmpl"),
   252  
   253  		// cli templates
   254  		"cli/cli.gotmpl":          MustAsset("templates/cli/cli.gotmpl"),
   255  		"cli/main.gotmpl":         MustAsset("templates/cli/main.gotmpl"),
   256  		"cli/modelcli.gotmpl":     MustAsset("templates/cli/modelcli.gotmpl"),
   257  		"cli/operation.gotmpl":    MustAsset("templates/cli/operation.gotmpl"),
   258  		"cli/registerflag.gotmpl": MustAsset("templates/cli/registerflag.gotmpl"),
   259  		"cli/retrieveflag.gotmpl": MustAsset("templates/cli/retrieveflag.gotmpl"),
   260  		"cli/schema.gotmpl":       MustAsset("templates/cli/schema.gotmpl"),
   261  		"cli/completion.gotmpl":   MustAsset("templates/cli/completion.gotmpl"),
   262  	}
   263  }
   264  
   265  func defaultProtectedTemplates() map[string]bool {
   266  	return map[string]bool{
   267  		"dereffedSchemaType":          true,
   268  		"docstring":                   true,
   269  		"header":                      true,
   270  		"mapvalidator":                true,
   271  		"model":                       true,
   272  		"modelvalidator":              true,
   273  		"objectvalidator":             true,
   274  		"primitivefieldvalidator":     true,
   275  		"privstructfield":             true,
   276  		"privtuplefield":              true,
   277  		"propertyValidationDocString": true,
   278  		"propertyvalidator":           true,
   279  		"schema":                      true,
   280  		"schemaBody":                  true,
   281  		"schemaType":                  true,
   282  		"schemabody":                  true,
   283  		"schematype":                  true,
   284  		"schemavalidator":             true,
   285  		"serverDoc":                   true,
   286  		"slicevalidator":              true,
   287  		"structfield":                 true,
   288  		"structfieldIface":            true,
   289  		"subTypeBody":                 true,
   290  		"swaggerJsonEmbed":            true,
   291  		"tuplefield":                  true,
   292  		"tuplefieldIface":             true,
   293  		"typeSchemaType":              true,
   294  		"simpleschemaDefaultsvar":     true,
   295  		"simpleschemaDefaultsinit":    true,
   296  
   297  		// validation helpers
   298  		"validationCustomformat": true,
   299  		"validationPrimitive":    true,
   300  		"validationStructfield":  true,
   301  		"withBaseTypeBody":       true,
   302  		"withoutBaseTypeBody":    true,
   303  		"validationMinimum":      true,
   304  		"validationMaximum":      true,
   305  		"validationMultipleOf":   true,
   306  
   307  		// all serializers
   308  		"additionalPropertiesSerializer": true,
   309  		"tupleSerializer":                true,
   310  		"schemaSerializer":               true,
   311  		"hasDiscriminatedSerializer":     true,
   312  		"discriminatedSerializer":        true,
   313  	}
   314  }
   315  
   316  // AddFile adds a file to the default repository. It will create a new template based on the filename.
   317  // It trims the .gotmpl from the end and converts the name using swag.ToJSONName. This will strip
   318  // directory separators and Camelcase the next letter.
   319  // e.g validation/primitive.gotmpl will become validationPrimitive
   320  //
   321  // If the file contains a definition for a template that is protected the whole file will not be added
   322  func AddFile(name, data string) error {
   323  	return templates.addFile(name, data, false)
   324  }
   325  
   326  // NewRepository creates a new template repository with the provided functions defined
   327  func NewRepository(funcs template.FuncMap) *Repository {
   328  	repo := Repository{
   329  		files:     make(map[string]string),
   330  		templates: make(map[string]*template.Template),
   331  		funcs:     funcs,
   332  	}
   333  
   334  	if repo.funcs == nil {
   335  		repo.funcs = make(template.FuncMap)
   336  	}
   337  
   338  	return &repo
   339  }
   340  
   341  // Repository is the repository for the generator templates
   342  type Repository struct {
   343  	files         map[string]string
   344  	templates     map[string]*template.Template
   345  	funcs         template.FuncMap
   346  	allowOverride bool
   347  	mux           sync.Mutex
   348  }
   349  
   350  // ShallowClone a repository.
   351  //
   352  // Clones the maps of files and templates, so as to be able to use
   353  // the cloned repo concurrently.
   354  func (t *Repository) ShallowClone() *Repository {
   355  	clone := &Repository{
   356  		files:         make(map[string]string, len(t.files)),
   357  		templates:     make(map[string]*template.Template, len(t.templates)),
   358  		funcs:         t.funcs,
   359  		allowOverride: t.allowOverride,
   360  	}
   361  
   362  	t.mux.Lock()
   363  	defer t.mux.Unlock()
   364  
   365  	for k, file := range t.files {
   366  		clone.files[k] = file
   367  	}
   368  	for k, tpl := range t.templates {
   369  		clone.templates[k] = tpl
   370  	}
   371  	return clone
   372  }
   373  
   374  // LoadDefaults will load the embedded templates
   375  func (t *Repository) LoadDefaults() {
   376  	for name, asset := range assets {
   377  		if err := t.addFile(name, string(asset), true); err != nil {
   378  			log.Fatal(err)
   379  		}
   380  	}
   381  }
   382  
   383  // LoadDir will walk the specified path and add each .gotmpl file it finds to the repository
   384  func (t *Repository) LoadDir(templatePath string) error {
   385  	err := filepath.Walk(templatePath, func(path string, _ os.FileInfo, err error) error {
   386  		if strings.HasSuffix(path, ".gotmpl") {
   387  			if assetName, e := filepath.Rel(templatePath, path); e == nil {
   388  				if data, e := os.ReadFile(path); e == nil {
   389  					if ee := t.AddFile(assetName, string(data)); ee != nil {
   390  						return fmt.Errorf("could not add template: %w", ee)
   391  					}
   392  				}
   393  				// Non-readable files are skipped
   394  			}
   395  		}
   396  
   397  		if err != nil {
   398  			return err
   399  		}
   400  
   401  		// Non-template files are skipped
   402  		return nil
   403  	})
   404  	if err != nil {
   405  		return fmt.Errorf("could not complete template processing in directory \"%s\": %w", templatePath, err)
   406  	}
   407  	return nil
   408  }
   409  
   410  // LoadContrib loads template from contrib directory
   411  func (t *Repository) LoadContrib(name string) error {
   412  	log.Printf("loading contrib %s", name)
   413  	const pathPrefix = "templates/contrib/"
   414  	basePath := pathPrefix + name
   415  	filesAdded := 0
   416  	for _, aname := range AssetNames() {
   417  		if !strings.HasSuffix(aname, ".gotmpl") {
   418  			continue
   419  		}
   420  		if strings.HasPrefix(aname, basePath) {
   421  			target := aname[len(basePath)+1:]
   422  			err := t.addFile(target, string(MustAsset(aname)), true)
   423  			if err != nil {
   424  				return err
   425  			}
   426  			log.Printf("added contributed template %s from %s", target, aname)
   427  			filesAdded++
   428  		}
   429  	}
   430  	if filesAdded == 0 {
   431  		return fmt.Errorf("no files added from template: %s", name)
   432  	}
   433  	return nil
   434  }
   435  
   436  func (t *Repository) addFile(name, data string, allowOverride bool) error {
   437  	fileName := name
   438  	name = swag.ToJSONName(strings.TrimSuffix(name, ".gotmpl"))
   439  
   440  	templ, err := template.New(name).Funcs(t.funcs).Parse(data)
   441  	if err != nil {
   442  		return fmt.Errorf("failed to load template %s: %w", name, err)
   443  	}
   444  
   445  	// check if any protected templates are defined
   446  	if !allowOverride && !t.allowOverride {
   447  		for _, template := range templ.Templates() {
   448  			if protectedTemplates[template.Name()] {
   449  				return fmt.Errorf("cannot overwrite protected template %s", template.Name())
   450  			}
   451  		}
   452  	}
   453  
   454  	// Add each defined template into the cache
   455  	for _, template := range templ.Templates() {
   456  
   457  		t.files[template.Name()] = fileName
   458  		t.templates[template.Name()] = template.Lookup(template.Name())
   459  	}
   460  
   461  	return nil
   462  }
   463  
   464  // MustGet a template by name, panics when fails
   465  func (t *Repository) MustGet(name string) *template.Template {
   466  	tpl, err := t.Get(name)
   467  	if err != nil {
   468  		panic(err)
   469  	}
   470  	return tpl
   471  }
   472  
   473  // AddFile adds a file to the repository. It will create a new template based on the filename.
   474  // It trims the .gotmpl from the end and converts the name using swag.ToJSONName. This will strip
   475  // directory separators and Camelcase the next letter.
   476  // e.g validation/primitive.gotmpl will become validationPrimitive
   477  //
   478  // If the file contains a definition for a template that is protected the whole file will not be added
   479  func (t *Repository) AddFile(name, data string) error {
   480  	return t.addFile(name, data, false)
   481  }
   482  
   483  // SetAllowOverride allows setting allowOverride after the Repository was initialized
   484  func (t *Repository) SetAllowOverride(value bool) {
   485  	t.allowOverride = value
   486  }
   487  
   488  func findDependencies(n parse.Node) []string {
   489  	var deps []string
   490  	depMap := make(map[string]bool)
   491  
   492  	if n == nil {
   493  		return deps
   494  	}
   495  
   496  	switch node := n.(type) {
   497  	case *parse.ListNode:
   498  		if node != nil && node.Nodes != nil {
   499  			for _, nn := range node.Nodes {
   500  				for _, dep := range findDependencies(nn) {
   501  					depMap[dep] = true
   502  				}
   503  			}
   504  		}
   505  	case *parse.IfNode:
   506  		for _, dep := range findDependencies(node.BranchNode.List) {
   507  			depMap[dep] = true
   508  		}
   509  		for _, dep := range findDependencies(node.BranchNode.ElseList) {
   510  			depMap[dep] = true
   511  		}
   512  
   513  	case *parse.RangeNode:
   514  		for _, dep := range findDependencies(node.BranchNode.List) {
   515  			depMap[dep] = true
   516  		}
   517  		for _, dep := range findDependencies(node.BranchNode.ElseList) {
   518  			depMap[dep] = true
   519  		}
   520  
   521  	case *parse.WithNode:
   522  		for _, dep := range findDependencies(node.BranchNode.List) {
   523  			depMap[dep] = true
   524  		}
   525  		for _, dep := range findDependencies(node.BranchNode.ElseList) {
   526  			depMap[dep] = true
   527  		}
   528  
   529  	case *parse.TemplateNode:
   530  		depMap[node.Name] = true
   531  	}
   532  
   533  	for dep := range depMap {
   534  		deps = append(deps, dep)
   535  	}
   536  
   537  	return deps
   538  }
   539  
   540  func (t *Repository) flattenDependencies(templ *template.Template, dependencies map[string]bool) map[string]bool {
   541  	if dependencies == nil {
   542  		dependencies = make(map[string]bool)
   543  	}
   544  
   545  	deps := findDependencies(templ.Tree.Root)
   546  
   547  	for _, d := range deps {
   548  		if _, found := dependencies[d]; !found {
   549  
   550  			dependencies[d] = true
   551  
   552  			if tt := t.templates[d]; tt != nil {
   553  				dependencies = t.flattenDependencies(tt, dependencies)
   554  			}
   555  		}
   556  
   557  		dependencies[d] = true
   558  
   559  	}
   560  
   561  	return dependencies
   562  }
   563  
   564  func (t *Repository) addDependencies(templ *template.Template) (*template.Template, error) {
   565  	name := templ.Name()
   566  
   567  	deps := t.flattenDependencies(templ, nil)
   568  
   569  	for dep := range deps {
   570  
   571  		if dep == "" {
   572  			continue
   573  		}
   574  
   575  		tt := templ.Lookup(dep)
   576  
   577  		// Check if we have it
   578  		if tt == nil {
   579  			tt = t.templates[dep]
   580  
   581  			// Still don't have it, return an error
   582  			if tt == nil {
   583  				return templ, fmt.Errorf("could not find template %s", dep)
   584  			}
   585  			var err error
   586  
   587  			// Add it to the parse tree
   588  			templ, err = templ.AddParseTree(dep, tt.Tree)
   589  			if err != nil {
   590  				return templ, fmt.Errorf("dependency error: %w", err)
   591  			}
   592  
   593  		}
   594  	}
   595  	return templ.Lookup(name), nil
   596  }
   597  
   598  // Get will return the named template from the repository, ensuring that all dependent templates are loaded.
   599  // It will return an error if a dependent template is not defined in the repository.
   600  func (t *Repository) Get(name string) (*template.Template, error) {
   601  	templ, found := t.templates[name]
   602  
   603  	if !found {
   604  		return templ, fmt.Errorf("template doesn't exist %s", name)
   605  	}
   606  
   607  	return t.addDependencies(templ)
   608  }
   609  
   610  // DumpTemplates prints out a dump of all the defined templates, where they are defined and what their dependencies are.
   611  func (t *Repository) DumpTemplates() {
   612  	buf := bytes.NewBuffer(nil)
   613  	fmt.Fprintln(buf, "\n# Templates")
   614  	for name, templ := range t.templates {
   615  		fmt.Fprintf(buf, "## %s\n", name)
   616  		fmt.Fprintf(buf, "Defined in `%s`\n", t.files[name])
   617  
   618  		if deps := findDependencies(templ.Tree.Root); len(deps) > 0 {
   619  			fmt.Fprintf(buf, "####requires \n - %v\n\n\n", strings.Join(deps, "\n - "))
   620  		}
   621  		fmt.Fprintln(buf, "\n---")
   622  	}
   623  	log.Println(buf.String())
   624  }
   625  
   626  // FuncMap functions
   627  
   628  func asJSON(data interface{}) (string, error) {
   629  	b, err := json.Marshal(data)
   630  	if err != nil {
   631  		return "", err
   632  	}
   633  	return string(b), nil
   634  }
   635  
   636  func asPrettyJSON(data interface{}) (string, error) {
   637  	b, err := json.MarshalIndent(data, "", "  ")
   638  	if err != nil {
   639  		return "", err
   640  	}
   641  	return string(b), nil
   642  }
   643  
   644  func pluralizeFirstWord(arg string) string {
   645  	sentence := strings.Split(arg, " ")
   646  	if len(sentence) == 1 {
   647  		return inflect.Pluralize(arg)
   648  	}
   649  
   650  	return inflect.Pluralize(sentence[0]) + " " + strings.Join(sentence[1:], " ")
   651  }
   652  
   653  func dropPackage(str string) string {
   654  	parts := strings.Split(str, ".")
   655  	return parts[len(parts)-1]
   656  }
   657  
   658  // return true if the GoType str contains pkg. For example "model.MyType" -> true, "MyType" -> false
   659  func containsPkgStr(str string) bool {
   660  	dropped := dropPackage(str)
   661  	return !(dropped == str)
   662  }
   663  
   664  func padSurround(entry, padWith string, i, ln int) string {
   665  	var res []string
   666  	if i > 0 {
   667  		for j := 0; j < i; j++ {
   668  			res = append(res, padWith)
   669  		}
   670  	}
   671  	res = append(res, entry)
   672  	tot := ln - i - 1
   673  	for j := 0; j < tot; j++ {
   674  		res = append(res, padWith)
   675  	}
   676  	return strings.Join(res, ",")
   677  }
   678  
   679  func padComment(str string, pads ...string) string {
   680  	// pads specifes padding to indent multi line comments.Defaults to one space
   681  	pad := " "
   682  	lines := strings.Split(str, "\n")
   683  	if len(pads) > 0 {
   684  		pad = strings.Join(pads, "")
   685  	}
   686  	return (strings.Join(lines, "\n//"+pad))
   687  }
   688  
   689  func blockComment(str string) string {
   690  	return strings.ReplaceAll(str, "*/", "[*]/")
   691  }
   692  
   693  func pascalize(arg string) string {
   694  	runes := []rune(arg)
   695  	switch len(runes) {
   696  	case 0:
   697  		return "Empty"
   698  	case 1: // handle special case when we have a single rune that is not handled by swag.ToGoName
   699  		switch runes[0] {
   700  		case '+', '-', '#', '_', '*', '/', '=': // those cases are handled differently than swag utility
   701  			return prefixForName(arg)
   702  		}
   703  	}
   704  	return swag.ToGoName(swag.ToGoName(arg)) // want to remove spaces
   705  }
   706  
   707  func prefixForName(arg string) string {
   708  	first := []rune(arg)[0]
   709  	if len(arg) == 0 || unicode.IsLetter(first) {
   710  		return ""
   711  	}
   712  	switch first {
   713  	case '+':
   714  		return "Plus"
   715  	case '-':
   716  		return "Minus"
   717  	case '#':
   718  		return "HashTag"
   719  	case '*':
   720  		return "Asterisk"
   721  	case '/':
   722  		return "ForwardSlash"
   723  	case '=':
   724  		return "EqualSign"
   725  		// other cases ($,@ etc..) handled by swag.ToGoName
   726  	}
   727  	return "Nr"
   728  }
   729  
   730  func replaceSpecialChar(in rune) string {
   731  	switch in {
   732  	case '.':
   733  		return "-Dot-"
   734  	case '+':
   735  		return "-Plus-"
   736  	case '-':
   737  		return "-Dash-"
   738  	case '#':
   739  		return "-Hashtag-"
   740  	}
   741  	return string(in)
   742  }
   743  
   744  func cleanupEnumVariant(in string) string {
   745  	replaced := ""
   746  	for _, char := range in {
   747  		replaced += replaceSpecialChar(char)
   748  	}
   749  	return replaced
   750  }
   751  
   752  func dict(values ...interface{}) (map[string]interface{}, error) {
   753  	if len(values)%2 != 0 {
   754  		return nil, fmt.Errorf("expected even number of arguments, got %d", len(values))
   755  	}
   756  	dict := make(map[string]interface{}, len(values)/2)
   757  	for i := 0; i < len(values); i += 2 {
   758  		key, ok := values[i].(string)
   759  		if !ok {
   760  			return nil, fmt.Errorf("expected string key, got %+v", values[i])
   761  		}
   762  		dict[key] = values[i+1]
   763  	}
   764  	return dict, nil
   765  }
   766  
   767  func isInteger(arg interface{}) bool {
   768  	// is integer determines if a value may be represented by an integer
   769  	switch val := arg.(type) {
   770  	case int8, int16, int32, int, int64, uint8, uint16, uint32, uint, uint64:
   771  		return true
   772  	case *int8, *int16, *int32, *int, *int64, *uint8, *uint16, *uint32, *uint, *uint64:
   773  		v := reflect.ValueOf(arg)
   774  		return !v.IsNil()
   775  	case float64:
   776  		return math.Round(val) == val
   777  	case *float64:
   778  		return val != nil && math.Round(*val) == *val
   779  	case float32:
   780  		return math.Round(float64(val)) == float64(val)
   781  	case *float32:
   782  		return val != nil && math.Round(float64(*val)) == float64(*val)
   783  	case string:
   784  		_, err := strconv.ParseInt(val, 10, 64)
   785  		return err == nil
   786  	case *string:
   787  		if val == nil {
   788  			return false
   789  		}
   790  		_, err := strconv.ParseInt(*val, 10, 64)
   791  		return err == nil
   792  	default:
   793  		return false
   794  	}
   795  }
   796  
   797  func resolvedDocCollectionFormat(cf string, child *GenItems) string {
   798  	if child == nil {
   799  		return cf
   800  	}
   801  	ccf := cf
   802  	if ccf == "" {
   803  		ccf = "csv"
   804  	}
   805  	rcf := resolvedDocCollectionFormat(child.CollectionFormat, child.Child)
   806  	if rcf == "" {
   807  		return ccf
   808  	}
   809  	return ccf + "|" + rcf
   810  }
   811  
   812  func resolvedDocType(tn, ft string, child *GenItems) string {
   813  	if tn == "array" {
   814  		if child == nil {
   815  			return "[]any"
   816  		}
   817  		return "[]" + resolvedDocType(child.SwaggerType, child.SwaggerFormat, child.Child)
   818  	}
   819  
   820  	if ft != "" {
   821  		if doc, ok := docFormat[ft]; ok {
   822  			return doc
   823  		}
   824  		return fmt.Sprintf("%s (formatted %s)", ft, tn)
   825  	}
   826  
   827  	return tn
   828  }
   829  
   830  func resolvedDocSchemaType(tn, ft string, child *GenSchema) string {
   831  	if tn == "array" {
   832  		if child == nil {
   833  			return "[]any"
   834  		}
   835  		return "[]" + resolvedDocSchemaType(child.SwaggerType, child.SwaggerFormat, child.Items)
   836  	}
   837  
   838  	if tn == "object" {
   839  		if child == nil || child.ElemType == nil {
   840  			return "map of any"
   841  		}
   842  		if child.IsMap {
   843  			return "map of " + resolvedDocElemType(child.SwaggerType, child.SwaggerFormat, &child.resolvedType)
   844  		}
   845  
   846  		return child.GoType
   847  	}
   848  
   849  	if ft != "" {
   850  		if doc, ok := docFormat[ft]; ok {
   851  			return doc
   852  		}
   853  		return fmt.Sprintf("%s (formatted %s)", ft, tn)
   854  	}
   855  
   856  	return tn
   857  }
   858  
   859  func resolvedDocElemType(tn, ft string, schema *resolvedType) string {
   860  	if schema == nil {
   861  		return ""
   862  	}
   863  	if schema.IsMap {
   864  		return "map of " + resolvedDocElemType(schema.ElemType.SwaggerType, schema.ElemType.SwaggerFormat, schema.ElemType)
   865  	}
   866  
   867  	if schema.IsArray {
   868  		return "[]" + resolvedDocElemType(schema.ElemType.SwaggerType, schema.ElemType.SwaggerFormat, schema.ElemType)
   869  	}
   870  
   871  	if ft != "" {
   872  		if doc, ok := docFormat[ft]; ok {
   873  			return doc
   874  		}
   875  		return fmt.Sprintf("%s (formatted %s)", ft, tn)
   876  	}
   877  
   878  	return tn
   879  }
   880  
   881  func httpStatus(code int) string {
   882  	if name, ok := runtime.Statuses[code]; ok {
   883  		return name
   884  	}
   885  	// non-standard codes deserve some name
   886  	return fmt.Sprintf("Status %d", code)
   887  }
   888  
   889  func gt0(in *int64) bool {
   890  	// gt0 returns true if the *int64 points to a value > 0
   891  	// NOTE: plain {{ gt .MinProperties 0 }} just refuses to work normally
   892  	// with a pointer
   893  	return in != nil && *in > 0
   894  }
   895  
   896  func errorPath(in interface{}) (string, error) {
   897  	// For schemas:
   898  	// errorPath returns an empty string litteral when the schema path is empty.
   899  	// It provides a shorthand for template statements such as:
   900  	// {{ if .Path }}{{ .Path }}{{ else }}" "{{ end }},
   901  	// which becomes {{ path . }}
   902  	//
   903  	// When called for a GenParameter, GenResponse or GenOperation object, it just
   904  	// returns Path.
   905  	//
   906  	// Extra behavior for schemas, when the generation option RootedErroPath is enabled:
   907  	// In the case of arrays with an empty path, it adds the type name as the path "root",
   908  	// so consumers of reported errors get an idea of the originator.
   909  
   910  	var pth string
   911  	rooted := func(schema GenSchema) string {
   912  		if schema.WantsRootedErrorPath && schema.Path == "" && (schema.IsArray || schema.IsMap) {
   913  			return `"[` + schema.Name + `]"`
   914  		}
   915  
   916  		return schema.Path
   917  	}
   918  
   919  	switch schema := in.(type) {
   920  	case GenSchema:
   921  		pth = rooted(schema)
   922  	case *GenSchema:
   923  		if schema == nil {
   924  			break
   925  		}
   926  		pth = rooted(*schema)
   927  	case GenDefinition:
   928  		pth = rooted(schema.GenSchema)
   929  	case *GenDefinition:
   930  		if schema == nil {
   931  			break
   932  		}
   933  		pth = rooted(schema.GenSchema)
   934  	case GenParameter:
   935  		pth = schema.Path
   936  
   937  	// unchanged Path if called with other types
   938  	case *GenParameter:
   939  		if schema == nil {
   940  			break
   941  		}
   942  		pth = schema.Path
   943  	case GenResponse:
   944  		pth = schema.Path
   945  	case *GenResponse:
   946  		if schema == nil {
   947  			break
   948  		}
   949  		pth = schema.Path
   950  	case GenOperation:
   951  		pth = schema.Path
   952  	case *GenOperation:
   953  		if schema == nil {
   954  			break
   955  		}
   956  		pth = schema.Path
   957  	case GenItems:
   958  		pth = schema.Path
   959  	case *GenItems:
   960  		if schema == nil {
   961  			break
   962  		}
   963  		pth = schema.Path
   964  	case GenHeader:
   965  		pth = schema.Path
   966  	case *GenHeader:
   967  		if schema == nil {
   968  			break
   969  		}
   970  		pth = schema.Path
   971  	default:
   972  		return "", fmt.Errorf("errorPath should be called with GenSchema or GenDefinition, but got %T", schema)
   973  	}
   974  
   975  	if pth == "" {
   976  		return `""`, nil
   977  	}
   978  
   979  	return pth, nil
   980  }
   981  
   982  const mdNewLine = "</br>"
   983  
   984  var mdNewLineReplacer = strings.NewReplacer("\r\n", mdNewLine, "\n", mdNewLine, "\r", mdNewLine)
   985  
   986  func markdownBlock(in string) string {
   987  	in = strings.TrimSpace(in)
   988  
   989  	return mdNewLineReplacer.Replace(in)
   990  }