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

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