diff --git a/pkg/codegen/docs/gen.go b/pkg/codegen/docs/gen.go index 5ba0e805a..a57e3411d 100644 --- a/pkg/codegen/docs/gen.go +++ b/pkg/codegen/docs/gen.go @@ -26,7 +26,6 @@ import ( "fmt" "html" "html/template" - "io" "path" "sort" "strings" @@ -148,7 +147,8 @@ type resourceDocArgs struct { Header header // Comment represents the introductory resource comment. - Comment string + Comment string + DeprecationMessage string ConstructorParams map[string]string // ConstructorResource is the resource that is being constructed or @@ -259,7 +259,7 @@ type propertyCharacteristics struct { // getLanguageModuleName returns the module name mapped to its language-specific // equivalent if the schema for this provider has any overrides for that language. // Otherwise, returns the module name as-is. -func getLanguageModuleName(lang string, mod string) string { +func getLanguageModuleName(pkg *schema.Package, mod, lang string) string { modName := mod lookupKey := lang + "_" + modName if v, ok := langModuleNameLookup[lookupKey]; ok { @@ -281,11 +281,62 @@ func getLanguageModuleName(lang string, mod string) string { return modName } +// cleanTypeString removes any namespaces from the generated type string for all languages. +// The result of this function should be used display purposes only. +func (mod *modContext) cleanTypeString(t schema.Type, langTypeString, lang, modName string, isInput bool) string { + if lang != "csharp" { + parts := strings.Split(langTypeString, ".") + return parts[len(parts)-1] + } + + // C# types can be wrapped in enumerable types such as List<> or Dictionary<>, so we have to + // only replace the namespace between the < and the > characters. + qualifier := "Inputs" + if !isInput { + qualifier = "Outputs" + } + + cleanCSharpName := func(pkgName, objModName string) string { + var csharpNS string + // This type could be at the package-level, so it won't have a module name. + if objModName != "" { + csharpNS = fmt.Sprintf("Pulumi.%s.%s.%s.", title(pkgName, lang), title(objModName, lang), qualifier) + } else { + csharpNS = fmt.Sprintf("Pulumi.%s.%s.", title(pkgName, lang), qualifier) + } + return strings.ReplaceAll(langTypeString, csharpNS, "") + } + + if isKubernetesPackage(mod.pkg) { + switch t := t.(type) { + case *schema.ArrayType: + if schema.IsPrimitiveType(t.ElementType) { + break + } + objType := t.ElementType.(*schema.ObjectType) + return mod.cleanTypeString(objType, langTypeString, lang, modName, isInput) + case *schema.UnionType: + for _, e := range t.ElementTypes { + if schema.IsPrimitiveType(e) { + continue + } + return mod.cleanTypeString(e.(*schema.ObjectType), langTypeString, lang, modName, isInput) + } + case *schema.ObjectType: + objTypeModName := mod.pkg.TokenToModule(t.Token) + if objTypeModName != mod.mod { + modName = getLanguageModuleName(mod.pkg, objTypeModName, lang) + } + } + } + return cleanCSharpName(mod.pkg.Name, modName) +} + // typeString returns a property type suitable for docs with its display name and the anchor link to // a type if the type of the property is an array or an object. func (mod *modContext) typeString(t schema.Type, lang string, characteristics propertyCharacteristics, insertWordBreaks bool) propertyType { docLanguageHelper := getLanguageDocHelper(lang) - modName := getLanguageModuleName(lang, mod.mod) + modName := getLanguageModuleName(mod.pkg, mod.mod, lang) langTypeString := docLanguageHelper.GetLanguageTypeString(mod.pkg, modName, t, characteristics.input, characteristics.optional) // If the type is an object type, let's also wrap it with a link to the supporting type @@ -297,32 +348,14 @@ func (mod *modContext) typeString(t schema.Type, lang string, characteristics pr href = elementLangType.Link case *schema.ObjectType: tokenName := tokenToName(t.Token) - // Links to anchor targs on the same page must be lower-cased. - href = "#" + lower(tokenName) + // Links to anchor tags on the same page must be lower-cased. + href = "#" + strings.ToLower(tokenName) } // Strip the namespace/module prefix for the type's display name. - var parts []string - var displayName string - if lang == "csharp" { - // C# types can be wrapped in enumerable types such as List<> or Dictionary<>, so we have to - // only replace the namespace string within the < and the >. - qualifier := "Inputs" - if !characteristics.input { - qualifier = "Outputs" - } - - var csharpNS string - // This type could be at the package-level, so it won't have a module name. - if modName != "" { - csharpNS = fmt.Sprintf("Pulumi.%s.%s.%s.", strings.Title(mod.pkg.Name), strings.Title(modName), qualifier) - } else { - csharpNS = fmt.Sprintf("Pulumi.%s.%s.", strings.Title(mod.pkg.Name), qualifier) - } - displayName = strings.ReplaceAll(langTypeString, csharpNS, "") - } else { - parts = strings.Split(langTypeString, ".") - displayName = parts[len(parts)-1] + displayName := langTypeString + if !schema.IsPrimitiveType(t) { + displayName = mod.cleanTypeString(t, langTypeString, lang, modName, characteristics.input) } // If word-breaks need to be inserted, then the type string @@ -485,70 +518,72 @@ func (mod *modContext) genNestedTypes(member interface{}, resourceType bool) []d var objs []docNestedType for token, tyUsage := range tokens { for _, t := range mod.pkg.Types { - if obj, ok := t.(*schema.ObjectType); ok && obj.Token == token { - if len(obj.Properties) == 0 { - continue - } - - // Create maps to hold the per-language properties of this object and links to - // the API doc for each language. - props := make(map[string][]property) - apiDocLinks := make(map[string]apiTypeDocLinks) - for _, lang := range supportedLanguages { - // The nested type may be under a different package in a language. - // For example, in k8s, common types are in the core/v1 module and can appear in - // nested types elsewhere. So we use the appropriate name of that type, - // as well as its language-specific name. For example, module name for use as a C# namespace - // or as a Go package name. - modName := getLanguageModuleName(lang, mod.mod) - nestedTypeModName := mod.pkg.TokenToModule(token) - if nestedTypeModName != mod.mod { - modName = getLanguageModuleName(lang, nestedTypeModName) - } - - docLangHelper := getLanguageDocHelper(lang) - inputCharacteristics := propertyCharacteristics{ - input: true, - optional: true, - } - outputCharacteristics := propertyCharacteristics{ - input: false, - optional: true, - } - inputObjLangType := mod.typeString(t, lang, inputCharacteristics, false /*insertWordBreaks*/) - outputObjLangType := mod.typeString(t, lang, outputCharacteristics, false /*insertWordBreaks*/) - - // Get the doc link for this nested type based on whether the type is for a Function or a Resource. - var inputTypeDocLink string - var outputTypeDocLink string - if resourceType { - if tyUsage.Input { - inputTypeDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg.Name, modName, inputObjLangType.Name, true) - } - if tyUsage.Output { - outputTypeDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg.Name, modName, outputObjLangType.Name, false) - } - } else { - if tyUsage.Input { - inputTypeDocLink = docLangHelper.GetDocLinkForFunctionInputOrOutputType(mod.pkg.Name, modName, inputObjLangType.Name, true) - } - if tyUsage.Output { - outputTypeDocLink = docLangHelper.GetDocLinkForFunctionInputOrOutputType(mod.pkg.Name, modName, outputObjLangType.Name, false) - } - } - apiDocLinks[lang] = apiTypeDocLinks{ - InputType: inputTypeDocLink, - OutputType: outputTypeDocLink, - } - props[lang] = mod.getProperties(obj.Properties, lang, true, true) - } - - objs = append(objs, docNestedType{ - Name: wbr(tokenToName(obj.Token)), - APIDocLinks: apiDocLinks, - Properties: props, - }) + obj, ok := t.(*schema.ObjectType) + if !ok || obj.Token != token { + continue } + if len(obj.Properties) == 0 { + continue + } + + // Create maps to hold the per-language properties of this object and links to + // the API doc for each language. + props := make(map[string][]property) + apiDocLinks := make(map[string]apiTypeDocLinks) + for _, lang := range supportedLanguages { + // The nested type may be under a different package in a language. + // For example, in k8s, common types are in the core/v1 module and can appear in + // nested types elsewhere. So we use the appropriate name of that type, + // as well as its language-specific name. For example, module name for use as a C# namespace + // or as a Go package name. + modName := getLanguageModuleName(mod.pkg, mod.mod, lang) + nestedTypeModName := mod.pkg.TokenToModule(token) + if nestedTypeModName != mod.mod { + modName = getLanguageModuleName(mod.pkg, nestedTypeModName, lang) + } + + docLangHelper := getLanguageDocHelper(lang) + inputCharacteristics := propertyCharacteristics{ + input: true, + optional: true, + } + outputCharacteristics := propertyCharacteristics{ + input: false, + optional: true, + } + inputObjLangType := mod.typeString(t, lang, inputCharacteristics, false /*insertWordBreaks*/) + outputObjLangType := mod.typeString(t, lang, outputCharacteristics, false /*insertWordBreaks*/) + + // Get the doc link for this nested type based on whether the type is for a Function or a Resource. + var inputTypeDocLink string + var outputTypeDocLink string + if resourceType { + if tyUsage.Input { + inputTypeDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg.Name, modName, inputObjLangType.Name, true) + } + if tyUsage.Output { + outputTypeDocLink = docLangHelper.GetDocLinkForResourceInputOrOutputType(mod.pkg.Name, modName, outputObjLangType.Name, false) + } + } else { + if tyUsage.Input { + inputTypeDocLink = docLangHelper.GetDocLinkForFunctionInputOrOutputType(mod.pkg.Name, modName, inputObjLangType.Name, true) + } + if tyUsage.Output { + outputTypeDocLink = docLangHelper.GetDocLinkForFunctionInputOrOutputType(mod.pkg.Name, modName, outputObjLangType.Name, false) + } + } + apiDocLinks[lang] = apiTypeDocLinks{ + InputType: inputTypeDocLink, + OutputType: outputTypeDocLink, + } + props[lang] = mod.getProperties(obj.Properties, lang, true, true) + } + + objs = append(objs, docNestedType{ + Name: wbr(tokenToName(obj.Token)), + APIDocLinks: apiDocLinks, + Properties: props, + }) } } @@ -565,12 +600,17 @@ func (mod *modContext) getProperties(properties []*schema.Property, lang string, if len(properties) == 0 { return nil } - + isK8s := isKubernetesPackage(mod.pkg) docProperties := make([]property, 0, len(properties)) for _, prop := range properties { if prop == nil { continue } + // In k8s, apiVersion and kind are hard-coded in the SDK and not really + // user-provided input properties, so skip them. + if isK8s && (prop.Name == "apiVersion" || prop.Name == "kind") { + continue + } characteristics := propertyCharacteristics{ input: input, @@ -646,7 +686,13 @@ func (mod *modContext) genConstructors(r *schema.Resource, allOptionalInputs boo // The input properties for a resource needs to be exploded as // individual constructor params. params = make([]formalParam, 0, len(r.InputProperties)) + isK8s := isKubernetesPackage(mod.pkg) for _, p := range r.InputProperties { + // In k8s, apiVersion and kind are hard-coded in the SDK and not really + // user-provided input properties, so skip them. + if isK8s && (p.Name == "apiVersion" || p.Name == "kind") { + continue + } params = append(params, formalParam{ Name: python.PyName(p.Name), DefaultValue: "=None", @@ -685,7 +731,7 @@ func (mod *modContext) getConstructorResourceInfo(resourceTypeName string) map[s case "nodejs", "go": // Intentionally left blank. case "csharp": - resourceTypeName = fmt.Sprintf("Pulumi.%s.%s.%s", strings.Title(mod.pkg.Name), strings.Title(mod.mod), resourceTypeName) + resourceTypeName = fmt.Sprintf("Pulumi.%s.%s.%s", title(mod.pkg.Name, "csharp"), title(mod.mod, "csharp"), resourceTypeName) case "python": // Pulumi's Python language SDK does not have "types" yet, so we will skip it for now. continue @@ -787,7 +833,7 @@ func (mod *modContext) getGoLookupParams(r *schema.Resource, stateParam string) } func (mod *modContext) getCSLookupParams(r *schema.Resource, stateParam string) []formalParam { - stateParamFQDN := fmt.Sprintf("Pulumi.%s.%s.%s", strings.Title(mod.pkg.Name), strings.Title(mod.mod), stateParam) + stateParamFQDN := fmt.Sprintf("Pulumi.%s.%s.%s", title(mod.pkg.Name, "csharp"), title(mod.mod, "csharp"), stateParam) docLangHelper := getLanguageDocHelper("csharp") return []formalParam{ @@ -924,7 +970,8 @@ func (mod *modContext) genResource(r *schema.Resource) resourceDocArgs { Title: name, }, - Comment: r.Comment, + Comment: r.Comment, + DeprecationMessage: r.DeprecationMessage, ConstructorParams: mod.genConstructors(r, allOptionalInputs), ConstructorResource: mod.getConstructorResourceInfo(name), @@ -1016,34 +1063,6 @@ func (mod *modContext) getTypes(member interface{}, types nestedTypeUsageInfo) { } } -func (mod *modContext) genHeader(w io.Writer, title string, menu bool) { - // TODO: Generate the actual front matter we want for these pages. - // Example: - // title: "Package @pulumi/aws" - // title_tag: "Package @pulumi/aws | Node.js SDK" - // linktitle: "@pulumi/aws" - // meta_desc: "Explore members of the @pulumi/aws package." - // menu: - // reference: - // parent: API Reference - // identifier: Server - - fmt.Fprintf(w, "---\n") - fmt.Fprintf(w, "title: %q\n", title) - fmt.Fprintf(w, "block_external_search_index: true\n") - - // Only add the menu section for pages that should be in the TOC. - if menu { - fmt.Fprintf(w, "menu:\n") - fmt.Fprintf(w, " reference:\n") - fmt.Fprintf(w, " parent: API Reference\n") - } - - fmt.Fprintf(w, "---\n\n") - fmt.Fprintf(w, "\n", mod.tool) - fmt.Fprintf(w, "\n\n") -} - type fs map[string][]byte func (fs fs) add(path string, contents []byte) { @@ -1052,20 +1071,32 @@ func (fs fs) add(path string, contents []byte) { fs[path] = contents } +func (mod *modContext) getModuleFileName() string { + if !isKubernetesPackage(mod.pkg) { + return mod.mod + } + + if override, ok := goPkgInfo.ModuleToPackage[mod.mod]; ok { + return override + } + return mod.mod +} + func (mod *modContext) gen(fs fs) error { + modName := mod.getModuleFileName() var files []string for p := range fs { d := path.Dir(p) if d == "." { d = "" } - if d == mod.mod { + if d == modName { files = append(files, p) } } addFile := func(name, contents string) { - p := path.Join(mod.mod, name) + p := path.Join(modName, name) files = append(files, p) fs.add(p, []byte(contents)) } @@ -1078,9 +1109,10 @@ func (mod *modContext) gen(fs fs) error { buffer := &bytes.Buffer{} err := templates.ExecuteTemplate(buffer, "resource.tmpl", data) if err != nil { - panic(err) + return err } - addFile(lower(title)+".md", buffer.String()) + + addFile(strings.ToLower(title)+".md", buffer.String()) } // Functions @@ -1090,100 +1122,152 @@ func (mod *modContext) gen(fs fs) error { buffer := &bytes.Buffer{} err := templates.ExecuteTemplate(buffer, "function.tmpl", data) if err != nil { - panic(err) + return err } - addFile(lower(tokenToName(f.Token))+".md", buffer.String()) + + addFile(strings.ToLower(tokenToName(f.Token))+".md", buffer.String()) } - // Index - fs.add(path.Join(mod.mod, "_index.md"), []byte(mod.genIndex(files))) + // Generate the index files. + idxData := mod.genIndex() + buffer := &bytes.Buffer{} + err := templates.ExecuteTemplate(buffer, "index.tmpl", idxData) + if err != nil { + return err + } + + fs.add(path.Join(modName, "_index.md"), []byte(buffer.String())) return nil } +// indexEntry represents an individual entry on an index page. +type indexEntry struct { + Link string + DisplayName string +} + +// indexData represents the index file data to be rendered as _index.md. +type indexData struct { + Tool string + + Title string + PackageDescription string + // Menu indicates if an index page should be part of the TOC menu. + Menu bool + + Functions []indexEntry + Resources []indexEntry + Modules []indexEntry + PackageDetails packageDetails +} + +// indexEntrySorter implements the sort.Interface for sorting +// a slice of indexEntry struct types. +type indexEntrySorter struct { + entries []indexEntry +} + +// Len is part of sort.Interface. Returns the length of the +// entries slice. +func (s *indexEntrySorter) Len() int { + return len(s.entries) +} + +// Swap is part of sort.Interface. +func (s *indexEntrySorter) Swap(i, j int) { + s.entries[i], s.entries[j] = s.entries[j], s.entries[i] +} + +// Less is part of sort.Interface. It sorts the entries by their +// display name in an ascending order. +func (s *indexEntrySorter) Less(i, j int) bool { + return s.entries[i].DisplayName < s.entries[j].DisplayName +} + +func sortIndexEntries(entries []indexEntry) { + if len(entries) == 0 { + return + } + + sorter := &indexEntrySorter{ + entries: entries, + } + + sort.Sort(sorter) +} + // genIndex emits an _index.md file for the module. -func (mod *modContext) genIndex(exports []string) string { - w := &bytes.Buffer{} +func (mod *modContext) genIndex() indexData { + glog.V(4).Infoln("genIndex for", mod.mod) + modules := make([]indexEntry, 0, len(mod.children)) + resources := make([]indexEntry, 0, len(mod.resources)) + functions := make([]indexEntry, 0, len(mod.functions)) - name := mod.mod + modName := mod.getModuleFileName() + title := modName menu := false - if name == "" { - name = mod.pkg.Name - + if title == "" { + title = mod.pkg.Name // Flag top-level entries for inclusion in the table-of-contents menu. menu = true } - mod.genHeader(w, name, menu) + // If there are submodules, list them. + for _, mod := range mod.children { + modName := mod.getModuleFileName() + parts := strings.Split(modName, "/") + modName = parts[len(parts)-1] + modules = append(modules, indexEntry{ + Link: modName + "/", + DisplayName: modName, + }) + } + sortIndexEntries(modules) + + // If there are resources in the root, list them. + for _, r := range mod.resources { + name := resourceName(r) + resources = append(resources, indexEntry{ + Link: strings.ToLower(name), + DisplayName: name, + }) + } + sortIndexEntries(resources) + + // If there are functions in the root, list them. + for _, f := range mod.functions { + name := tokenToName(f.Token) + functions = append(functions, indexEntry{ + Link: strings.ToLower(name), + DisplayName: name, + }) + } + sortIndexEntries(functions) + + packageDetails := packageDetails{ + Repository: mod.pkg.Repository, + License: mod.pkg.License, + Notes: mod.pkg.Attribution, + } + + data := indexData{ + Tool: mod.tool, + + Title: title, + Menu: menu, + + Resources: resources, + Functions: functions, + Modules: modules, + PackageDetails: packageDetails, + } // If this is the root module, write out the package description. if mod.mod == "" { - description := mod.pkg.Description - if description != "" { - description += "\n\n" - } - fmt.Fprint(w, description) + data.PackageDescription = mod.pkg.Description } - // If there are submodules, list them. - var children []string - for _, mod := range mod.children { - children = append(children, mod.mod) - } - if len(children) > 0 { - sort.Strings(children) - fmt.Fprintf(w, "

Modules

\n") - fmt.Fprintf(w, "\n\n") - } - - // If there are resources in the root, list them. - var resources []string - for _, r := range mod.resources { - resources = append(resources, resourceName(r)) - } - if len(resources) > 0 { - sort.Strings(resources) - fmt.Fprintf(w, "

Resources

\n") - fmt.Fprintf(w, "\n\n") - } - - // If there are functions in the root, list them. - var functions []string - for _, f := range mod.functions { - functions = append(functions, tokenToName(f.Token)) - } - if len(functions) > 0 { - sort.Strings(functions) - fmt.Fprintf(w, "

Functions

\n") - fmt.Fprintf(w, "\n\n") - } - - fmt.Fprintf(w, "

Package Details

\n") - fmt.Fprintf(w, "
\n") - fmt.Fprintf(w, "
Repository
\n") - fmt.Fprintf(w, "
%s
\n", mod.pkg.Repository) - fmt.Fprintf(w, "
License
\n") - fmt.Fprintf(w, "
%s
\n", mod.pkg.License) - if mod.pkg.Attribution != "" { - fmt.Fprintf(w, "
Notes
\n") - fmt.Fprintf(w, "
%s
\n", mod.pkg.Attribution) - } - fmt.Fprintf(w, "
\n\n") - - return w.String() + return data } func decodeLangSpecificInfo(pkg *schema.Package, lang string, obj interface{}) error { @@ -1195,35 +1279,35 @@ func decodeLangSpecificInfo(pkg *schema.Package, lang string, obj interface{}) e return nil } +func getMod(pkg *schema.Package, token string, modules map[string]*modContext, tool string) *modContext { + modName := pkg.TokenToModule(token) + mod, ok := modules[modName] + if !ok { + mod = &modContext{ + pkg: pkg, + mod: modName, + tool: tool, + } + + if modName != "" { + parentName := path.Dir(modName) + // If the parent name is blank, it means this is the package-level. + if parentName == "." || parentName == "" { + parentName = ":index:" + } + parent := getMod(pkg, parentName, modules, tool) + parent.children = append(parent.children, mod) + } + + modules[modName] = mod + } + return mod +} + func generateModulesFromSchemaPackage(tool string, pkg *schema.Package) map[string]*modContext { // Group resources, types, and functions into modules. modules := map[string]*modContext{} - var getMod func(token string) *modContext - getMod = func(token string) *modContext { - modName := pkg.TokenToModule(token) - mod, ok := modules[modName] - if !ok { - mod = &modContext{ - pkg: pkg, - mod: modName, - tool: tool, - } - - if modName != "" { - parentName := path.Dir(modName) - if parentName == "." || parentName == "" { - parentName = ":index:" - } - parent := getMod(parentName) - parent.children = append(parent.children, mod) - } - - modules[modName] = mod - } - return mod - } - // Decode Go-specific language info. if err := decodeLangSpecificInfo(pkg, "go", &goPkgInfo); err != nil { panic(errors.Wrap(err, "error decoding go language info")) @@ -1242,7 +1326,7 @@ func generateModulesFromSchemaPackage(tool string, pkg *schema.Package) map[stri pyLangHelper := getLanguageDocHelper("python").(*python.DocLanguageHelper) scanResource := func(r *schema.Resource) { - mod := getMod(r.Token) + mod := getMod(pkg, r.Token, modules, tool) mod.resources = append(mod.resources, r) for _, p := range r.Properties { @@ -1255,7 +1339,7 @@ func generateModulesFromSchemaPackage(tool string, pkg *schema.Package) map[stri } scanK8SResource := func(r *schema.Resource) { - mod := getMod(r.Token) + mod := getK8SMod(pkg, r.Token, modules, tool) mod.resources = append(mod.resources, r) // For k8s, all nested properties will use a snake_case, @@ -1277,7 +1361,7 @@ func generateModulesFromSchemaPackage(tool string, pkg *schema.Package) map[stri } glog.V(3).Infoln("scanning resources") - if pkg.Name == "kubernetes" { + if isKubernetesPackage(pkg) { scanK8SResource(pkg.Provider) for _, r := range pkg.Resources { scanK8SResource(r) @@ -1291,7 +1375,7 @@ func generateModulesFromSchemaPackage(tool string, pkg *schema.Package) map[stri glog.V(3).Infoln("done scanning resources") for _, f := range pkg.Functions { - mod := getMod(f.Token) + mod := getMod(pkg, f.Token, modules, tool) mod.functions = append(mod.functions, f) } return modules diff --git a/pkg/codegen/docs/gen_function.go b/pkg/codegen/docs/gen_function.go index 942fdc300..32872a6ba 100644 --- a/pkg/codegen/docs/gen_function.go +++ b/pkg/codegen/docs/gen_function.go @@ -1,5 +1,3 @@ -//go:generate go run bundler.go - // Copyright 2016-2020, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/codegen/docs/gen_kubernetes.go b/pkg/codegen/docs/gen_kubernetes.go new file mode 100644 index 000000000..a0f782537 --- /dev/null +++ b/pkg/codegen/docs/gen_kubernetes.go @@ -0,0 +1,70 @@ +//go:generate go run bundler.go + +// Copyright 2016-2020, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the +// goconst linter's warning. +// +// nolint: lll, goconst +package docs + +import ( + "path" + "strings" + + "github.com/pulumi/pulumi/pkg/codegen/schema" +) + +func isKubernetesPackage(pkg *schema.Package) bool { + return pkg.Name == "kubernetes" +} + +func getK8SMod(pkg *schema.Package, token string, modules map[string]*modContext, tool string) *modContext { + modName := pkg.TokenToModule(token) + // Kubernetes' moduleFormat in the schema will match everything + // in the token. This prevents us from adding the "Provider" + // resource as a child module of the package level :index: module. + if modName == "providers" { + modName = "" + } else { + modName = strings.TrimSuffix(modName, ".k8s.io") + modName = strings.TrimSuffix(modName, ".apiserver") + modName = strings.TrimSuffix(modName, ".authorization") + } + + mod, ok := modules[modName] + if !ok { + mod = &modContext{ + pkg: pkg, + mod: modName, + tool: tool, + } + + if modName != "" { + parentName := path.Dir(modName) + // If the parent name is blank, it means this is the package-level. + if parentName == "." || parentName == "" { + parentName = ":index:" + } else { + parentName = ":" + parentName + ":" + } + parent := getK8SMod(pkg, parentName, modules, tool) + parent.children = append(parent.children, mod) + } + + modules[modName] = mod + } + return mod +} diff --git a/pkg/codegen/docs/templates/header.tmpl b/pkg/codegen/docs/templates/header.tmpl index 47cd87e84..abe105dbf 100644 --- a/pkg/codegen/docs/templates/header.tmpl +++ b/pkg/codegen/docs/templates/header.tmpl @@ -4,4 +4,4 @@ title: "{{ .Title }}" block_external_search_index: true --- -{{- end }} +{{ end }} diff --git a/pkg/codegen/docs/templates/index.tmpl b/pkg/codegen/docs/templates/index.tmpl new file mode 100644 index 000000000..e5302a29a --- /dev/null +++ b/pkg/codegen/docs/templates/index.tmpl @@ -0,0 +1,28 @@ +--- +title: "{{ .Title }}" +block_external_search_index: true +{{- if .Menu }} +menu: + reference: + parent: API Reference +{{- end }} +--- + +{{ htmlSafe "" }} +{{ htmlSafe "" }} + +{{ htmlSafe .PackageDescription }} + +{{- if ne (len .Modules) 0 -}} +{{ template "index_modules" .Modules }} +{{ end }} + +{{- if ne (len .Resources) 0 -}} +{{ template "index_resources" .Resources }} +{{ end }} + +{{- if ne (len .Functions) 0 -}} +{{ template "index_functions" .Functions }} +{{ end }} + +{{- template "package_details" .PackageDetails }} diff --git a/pkg/codegen/docs/templates/index_categories.tmpl b/pkg/codegen/docs/templates/index_categories.tmpl new file mode 100644 index 000000000..aef0c8d0e --- /dev/null +++ b/pkg/codegen/docs/templates/index_categories.tmpl @@ -0,0 +1,26 @@ +{{- define "index_modules" }} +

Modules

+ +{{- end }} + +{{- define "index_resources" }} +

Resources

+ +{{- end }} + +{{- define "index_functions" }} +

Functions

+ +{{- end }} \ No newline at end of file diff --git a/pkg/codegen/docs/templates/package_details.tmpl b/pkg/codegen/docs/templates/package_details.tmpl new file mode 100644 index 000000000..4bf38e245 --- /dev/null +++ b/pkg/codegen/docs/templates/package_details.tmpl @@ -0,0 +1,13 @@ +{{ define "package_details" }} +

Package Details

+
+
Repository
+
{{ htmlSafe .Repository }}
+
License
+
{{ htmlSafe .License }}
+ {{ if ne .Notes "" -}} +
Notes
+
{{ htmlSafe .Notes }}
+ {{- end }} +
+{{ end }} \ No newline at end of file diff --git a/pkg/codegen/docs/templates/resource.tmpl b/pkg/codegen/docs/templates/resource.tmpl index bfcd7d905..1686272be 100644 --- a/pkg/codegen/docs/templates/resource.tmpl +++ b/pkg/codegen/docs/templates/resource.tmpl @@ -1,5 +1,7 @@ {{ template "header" .Header }} +{{- htmlSafe .DeprecationMessage }} + {{ htmlSafe .Comment }} @@ -138,14 +140,4 @@ The following state arguments are supported: {{ end }} -

Package Details

-
-
Repository
-
{{ htmlSafe .PackageDetails.Repository }}
-
License
-
{{ htmlSafe .PackageDetails.License }}
- {{- if ne .PackageDetails.Notes "" -}} -
Notes
-
{{ htmlSafe .PackageDetails.Notes }}
- {{- end -}} -
+{{ template "package_details" .PackageDetails }} diff --git a/pkg/codegen/docs/utils.go b/pkg/codegen/docs/utils.go index 0ec8a7f58..85938ec2e 100644 --- a/pkg/codegen/docs/utils.go +++ b/pkg/codegen/docs/utils.go @@ -22,6 +22,8 @@ import ( "strings" "unicode" + "github.com/pulumi/pulumi/pkg/codegen/dotnet" + go_gen "github.com/pulumi/pulumi/pkg/codegen/go" "github.com/pulumi/pulumi/sdk/go/common/util/contract" ) @@ -53,13 +55,20 @@ func wbr(s string) string { return string(runes) } -func lower(s string) string { - return strings.ToLower(s) -} - // tokenToName returns the resource name from a Pulumi token. func tokenToName(tok string) string { components := strings.Split(tok, ":") contract.Assertf(len(components) == 3, "malformed token %v", tok) return strings.Title(components[2]) } + +func title(s, lang string) string { + switch lang { + case "go": + return go_gen.Title(s) + case "csharp": + return dotnet.Title(s) + default: + return strings.Title(s) + } +} diff --git a/pkg/codegen/dotnet/doc.go b/pkg/codegen/dotnet/doc.go index 923d2bba7..924b3e2e5 100644 --- a/pkg/codegen/dotnet/doc.go +++ b/pkg/codegen/dotnet/doc.go @@ -38,7 +38,7 @@ func (d DocLanguageHelper) GetDocLinkForResourceType(packageName, _, typeName st typeName = strings.ReplaceAll(typeName, "?", "") var packageNamespace string if packageName != "" { - packageNamespace = "." + title(packageName) + packageNamespace = "." + Title(packageName) } return fmt.Sprintf("/docs/reference/pkg/dotnet/Pulumi%s/%s.html", packageNamespace, typeName) } diff --git a/pkg/codegen/dotnet/gen.go b/pkg/codegen/dotnet/gen.go index 78f34f8a9..8558607d1 100644 --- a/pkg/codegen/dotnet/gen.go +++ b/pkg/codegen/dotnet/gen.go @@ -56,7 +56,9 @@ type typeDetails struct { functionType bool } -func title(s string) string { +// Title converts the input string to a title case +// where only the initial letter is upper-cased. +func Title(s string) string { if s == "" { return "" } @@ -105,7 +107,7 @@ func namespaceName(namespaces map[string]string, name string) string { if ns, ok := namespaces[name]; ok { return ns } - return title(name) + return Title(name) } type modContext struct { @@ -126,7 +128,7 @@ func (mod *modContext) propertyName(p *schema.Property) string { if n, ok := mod.propertyNames[p]; ok { return n } - return title(p.Name) + return Title(p.Name) } func (mod *modContext) details(t *schema.ObjectType) *typeDetails { @@ -144,7 +146,7 @@ func tokenToName(tok string) string { components := strings.Split(tok, ":") contract.Assertf(len(components) == 3, "malformed token %v", tok) - return title(components[2]) + return Title(components[2]) } func resourceName(r *schema.Resource) string { @@ -158,7 +160,7 @@ func (mod *modContext) tokenToNamespace(tok string) string { components := strings.Split(tok, ":") contract.Assertf(len(components) == 3, "malformed token %v", tok) - pkg, nsName := "Pulumi."+title(components[0]), mod.pkg.TokenToModule(tok) + pkg, nsName := "Pulumi."+Title(components[0]), mod.pkg.TokenToModule(tok) if nsName == "" { return pkg } diff --git a/pkg/codegen/go/doc.go b/pkg/codegen/go/doc.go index 63e11deb5..e313abdfa 100644 --- a/pkg/codegen/go/doc.go +++ b/pkg/codegen/go/doc.go @@ -78,6 +78,9 @@ func GetDocLinkForBuiltInType(typeName string) string { // GetLanguageTypeString returns the Go-specific type given a Pulumi schema type. func (d DocLanguageHelper) GetLanguageTypeString(pkg *schema.Package, moduleName string, t schema.Type, input, optional bool) string { + if moduleName == "" && pkg.Name == "kubernetes" { + moduleName = "providers" + } modPkg, ok := d.packages[moduleName] if !ok { glog.Errorf("cannot calculate type string for type %q. could not find a package for module %q", t.String(), moduleName) diff --git a/pkg/codegen/go/gen.go b/pkg/codegen/go/gen.go index 4dfd3dc05..84cdebade 100644 --- a/pkg/codegen/go/gen.go +++ b/pkg/codegen/go/gen.go @@ -63,7 +63,9 @@ type typeDetails struct { mapElement bool } -func title(s string) string { +// Title converts the input string to a title case +// where only the initial letter is upper-cased. +func Title(s string) string { if s == "" { return "" } @@ -136,7 +138,7 @@ func (pkg *pkgContext) tokenToType(tok string) string { // If the package containing the type's token already has a resource with the // same name, add a `Type` suffix. modPkg := pkg.getPkg(mod) - name = title(name) + name = Title(name) if modPkg.names.has(name) { name += "Type" } @@ -150,7 +152,7 @@ func (pkg *pkgContext) tokenToType(tok string) string { func tokenToName(tok string) string { components := strings.Split(tok, ":") contract.Assert(len(components) == 3) - return title(components[2]) + return Title(components[2]) } func resourceName(r *schema.Resource) string { @@ -320,8 +322,8 @@ func genInputInterface(w io.Writer, name string) { printComment(w, getInputUsage(name), false) fmt.Fprintf(w, "type %sInput interface {\n", name) fmt.Fprintf(w, "\tpulumi.Input\n\n") - fmt.Fprintf(w, "\tTo%sOutput() %sOutput\n", title(name), name) - fmt.Fprintf(w, "\tTo%sOutputWithContext(context.Context) %sOutput\n", title(name), name) + fmt.Fprintf(w, "\tTo%sOutput() %sOutput\n", Title(name), name) + fmt.Fprintf(w, "\tTo%sOutputWithContext(context.Context) %sOutput\n", Title(name), name) fmt.Fprintf(w, "}\n\n") } @@ -378,20 +380,20 @@ func genInputMethods(w io.Writer, name, receiverType, elementType string, ptrMet fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil)).Elem()\n", elementType) fmt.Fprintf(w, "}\n\n") - fmt.Fprintf(w, "func (i %s) To%sOutput() %sOutput {\n", receiverType, title(name), name) - fmt.Fprintf(w, "\treturn i.To%sOutputWithContext(context.Background())\n", title(name)) + fmt.Fprintf(w, "func (i %s) To%sOutput() %sOutput {\n", receiverType, Title(name), name) + fmt.Fprintf(w, "\treturn i.To%sOutputWithContext(context.Background())\n", Title(name)) fmt.Fprintf(w, "}\n\n") - fmt.Fprintf(w, "func (i %s) To%sOutputWithContext(ctx context.Context) %sOutput {\n", receiverType, title(name), name) + fmt.Fprintf(w, "func (i %s) To%sOutputWithContext(ctx context.Context) %sOutput {\n", receiverType, Title(name), name) fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%sOutput)\n", name) fmt.Fprintf(w, "}\n\n") if ptrMethods { - fmt.Fprintf(w, "func (i %s) To%sPtrOutput() %sPtrOutput {\n", receiverType, title(name), name) - fmt.Fprintf(w, "\treturn i.To%sPtrOutputWithContext(context.Background())\n", title(name)) + fmt.Fprintf(w, "func (i %s) To%sPtrOutput() %sPtrOutput {\n", receiverType, Title(name), name) + fmt.Fprintf(w, "\treturn i.To%sPtrOutputWithContext(context.Background())\n", Title(name)) fmt.Fprintf(w, "}\n\n") - fmt.Fprintf(w, "func (i %s) To%sPtrOutputWithContext(ctx context.Context) %sPtrOutput {\n", receiverType, title(name), name) + fmt.Fprintf(w, "func (i %s) To%sPtrOutputWithContext(ctx context.Context) %sPtrOutput {\n", receiverType, Title(name), name) fmt.Fprintf(w, "\treturn pulumi.ToOutputWithContext(ctx, i).(%[1]sOutput).To%[1]sPtrOutputWithContext(ctx)\n", name) fmt.Fprintf(w, "}\n\n") } @@ -402,7 +404,7 @@ func (pkg *pkgContext) genPlainType(w io.Writer, name, comment string, propertie fmt.Fprintf(w, "type %s struct {\n", name) for _, p := range properties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", title(p.Name), pkg.plainType(p.Type, !p.IsRequired), p.Name) + fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.plainType(p.Type, !p.IsRequired), p.Name) } fmt.Fprintf(w, "}\n\n") } @@ -417,7 +419,7 @@ func (pkg *pkgContext) genInputTypes(w io.Writer, t *schema.ObjectType, details fmt.Fprintf(w, "type %sArgs struct {\n", name) for _, p := range t.Properties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", title(p.Name), pkg.inputType(p.Type, !p.IsRequired), p.Name) + fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.inputType(p.Type, !p.IsRequired), p.Name) } fmt.Fprintf(w, "}\n\n") @@ -462,11 +464,11 @@ func genOutputMethods(w io.Writer, name, elementType string) { fmt.Fprintf(w, "\treturn reflect.TypeOf((*%s)(nil)).Elem()\n", elementType) fmt.Fprintf(w, "}\n\n") - fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutput() %[1]sOutput {\n", name, title(name)) + fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutput() %[1]sOutput {\n", name, Title(name)) fmt.Fprintf(w, "\treturn o\n") fmt.Fprintf(w, "}\n\n") - fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutputWithContext(ctx context.Context) %[1]sOutput {\n", name, title(name)) + fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sOutputWithContext(ctx context.Context) %[1]sOutput {\n", name, Title(name)) fmt.Fprintf(w, "\treturn o\n") fmt.Fprintf(w, "}\n\n") } @@ -480,11 +482,11 @@ func (pkg *pkgContext) genOutputTypes(w io.Writer, t *schema.ObjectType, details genOutputMethods(w, name, name) if details.ptrElement { - fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutput() %[1]sPtrOutput {\n", name, title(name)) - fmt.Fprintf(w, "\treturn o.To%sPtrOutputWithContext(context.Background())\n", title(name)) + fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutput() %[1]sPtrOutput {\n", name, Title(name)) + fmt.Fprintf(w, "\treturn o.To%sPtrOutputWithContext(context.Background())\n", Title(name)) fmt.Fprintf(w, "}\n\n") - fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput {\n", name, title(name)) + fmt.Fprintf(w, "func (o %[1]sOutput) To%[2]sPtrOutputWithContext(ctx context.Context) %[1]sPtrOutput {\n", name, Title(name)) fmt.Fprintf(w, "\treturn o.ApplyT(func(v %[1]s) *%[1]s {\n", name) fmt.Fprintf(w, "\t\treturn &v\n") fmt.Fprintf(w, "\t}).(%sPtrOutput)\n", name) @@ -495,8 +497,8 @@ func (pkg *pkgContext) genOutputTypes(w io.Writer, t *schema.ObjectType, details printComment(w, p.Comment, false) outputType, applyType := pkg.outputType(p.Type, !p.IsRequired), pkg.plainType(p.Type, !p.IsRequired) - fmt.Fprintf(w, "func (o %sOutput) %s() %s {\n", name, title(p.Name), outputType) - fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n", name, applyType, title(p.Name), outputType) + fmt.Fprintf(w, "func (o %sOutput) %s() %s {\n", name, Title(p.Name), outputType) + fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n", name, applyType, Title(p.Name), outputType) fmt.Fprintf(w, "}\n\n") } @@ -513,8 +515,8 @@ func (pkg *pkgContext) genOutputTypes(w io.Writer, t *schema.ObjectType, details printComment(w, p.Comment, false) outputType, applyType := pkg.outputType(p.Type, !p.IsRequired), pkg.plainType(p.Type, !p.IsRequired) - fmt.Fprintf(w, "func (o %sPtrOutput) %s() %s {\n", name, title(p.Name), outputType) - fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n", name, applyType, title(p.Name), outputType) + fmt.Fprintf(w, "func (o %sPtrOutput) %s() %s {\n", name, Title(p.Name), outputType) + fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n", name, applyType, Title(p.Name), outputType) fmt.Fprintf(w, "}\n\n") } } @@ -619,7 +621,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error { } for _, p := range r.Properties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", title(p.Name), pkg.outputType(p.Type, !p.IsRequired), p.Name) + fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.outputType(p.Type, !p.IsRequired), p.Name) } fmt.Fprintf(w, "}\n\n") @@ -631,8 +633,8 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error { // Ensure required arguments are present. for _, p := range r.InputProperties { if p.IsRequired { - fmt.Fprintf(w, "\tif args == nil || args.%s == nil {\n", title(p.Name)) - fmt.Fprintf(w, "\t\treturn nil, errors.New(\"missing required argument '%s'\")\n", title(p.Name)) + fmt.Fprintf(w, "\tif args == nil || args.%s == nil {\n", Title(p.Name)) + fmt.Fprintf(w, "\t\treturn nil, errors.New(\"missing required argument '%s'\")\n", Title(p.Name)) fmt.Fprintf(w, "\t}\n") } } @@ -653,8 +655,8 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error { t = "pulumi.Any" } - fmt.Fprintf(w, "\tif args.%s == nil {\n", title(p.Name)) - fmt.Fprintf(w, "\t\targs.%s = %s(%s)\n", title(p.Name), t, v) + fmt.Fprintf(w, "\tif args.%s == nil {\n", Title(p.Name)) + fmt.Fprintf(w, "\t\targs.%s = %s(%s)\n", Title(p.Name), t, v) fmt.Fprintf(w, "\t}\n") } } @@ -708,14 +710,14 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error { fmt.Fprintf(w, "type %sState struct {\n", camel(name)) for _, p := range r.Properties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", title(p.Name), pkg.plainType(p.Type, true), p.Name) + fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.plainType(p.Type, true), p.Name) } fmt.Fprintf(w, "}\n\n") fmt.Fprintf(w, "type %sState struct {\n", name) for _, p := range r.Properties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s\n", title(p.Name), pkg.inputType(p.Type, true)) + fmt.Fprintf(w, "\t%s %s\n", Title(p.Name), pkg.inputType(p.Type, true)) } fmt.Fprintf(w, "}\n\n") @@ -728,7 +730,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error { fmt.Fprintf(w, "type %sArgs struct {\n", camel(name)) for _, p := range r.InputProperties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", title(p.Name), pkg.plainType(p.Type, !p.IsRequired), p.Name) + fmt.Fprintf(w, "\t%s %s `pulumi:\"%s\"`\n", Title(p.Name), pkg.plainType(p.Type, !p.IsRequired), p.Name) } fmt.Fprintf(w, "}\n\n") @@ -736,7 +738,7 @@ func (pkg *pkgContext) genResource(w io.Writer, r *schema.Resource) error { fmt.Fprintf(w, "type %sArgs struct {\n", name) for _, p := range r.InputProperties { printComment(w, p.Comment, true) - fmt.Fprintf(w, "\t%s %s\n", title(p.Name), pkg.inputType(p.Type, !p.IsRequired)) + fmt.Fprintf(w, "\t%s %s\n", Title(p.Name), pkg.inputType(p.Type, !p.IsRequired)) } fmt.Fprintf(w, "}\n\n") @@ -973,7 +975,7 @@ func (pkg *pkgContext) genConfig(w io.Writer, variables []*schema.Property) erro printComment(w, p.Comment, false) configKey := fmt.Sprintf("\"%s:%s\"", pkg.pkg.Name, camel(p.Name)) - fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context) %s {\n", title(p.Name), getType) + fmt.Fprintf(w, "func Get%s(ctx *pulumi.Context) %s {\n", Title(p.Name), getType) if p.DefaultValue != nil { defaultValue, err := pkg.getDefaultValue(p.DefaultValue, p.Type) if err != nil {