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 }