github.com/ffalor/go-swagger@v0.0.0-20231011000038-9f25265ac351/generator/template_repo.go (about)

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