Move resource docs index file generation to Go templates (#4308)

* Add new templates for generating index files. Remove the now irrelevant code.

* Add comment for generating index files by deduping module names. Use the path format of a module name as the link.

* Add a trailing slash for module links in the index page.

* Move the categories rendered in the index file into their own template file. Handle kubernetes provider specific module name conversion while generating parent modules.

* Fix issue with the k8s provider resource being generated as a module rather than a resource on the package-level index page.

* Show the deprecation message of a resource at the top of the document.

* Move k8s specific things to its own file.

* Add title attribute to the list items. Show the last part of a module name, in case it contains path separators

* Lookup the package name from the Go language info object for k8s then use that to lookup the C# namespace.

* Move the logic for cleaning a property type string for display names to a separate function.

* Export the title function from go and dotnet code generators for use in the resource doc generator. Use the title function from the respective lang code gens to match the correct title case used there. Fix issue with the Kubernetes namespace not being stripped for C# property type strings.

* Skip including apiVersion and kind as input properties for Kubernetes until pulumi-kubernetes#1062 is merged.
This commit is contained in:
Praneet Loke 2020-04-06 17:01:33 -07:00 committed by GitHub
parent d363be9de0
commit 22aeaa43b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 514 additions and 287 deletions

View file

@ -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, "<!-- WARNING: this file was generated by %v. -->\n", mod.tool)
fmt.Fprintf(w, "<!-- Do not edit by hand unless you're certain you know what you are doing! -->\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, "<h3>Modules</h3>\n")
fmt.Fprintf(w, "<ul class=\"api\">\n")
for _, mod := range children {
fmt.Fprintf(w, " <li><a href=\"%s/\"><span class=\"symbol module\"></span>%s</a></li>\n", mod, mod)
}
fmt.Fprintf(w, "</ul>\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, "<h3>Resources</h3>\n")
fmt.Fprintf(w, "<ul class=\"api\">\n")
for _, r := range resources {
fmt.Fprintf(w, " <li><a href=\"%s\"><span class=\"symbol resource\"></span>%s</a></li>\n", lower(r), r)
}
fmt.Fprintf(w, "</ul>\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, "<h3>Functions</h3>\n")
fmt.Fprintf(w, "<ul class=\"api\">\n")
for _, f := range functions {
// TODO: We want to use "function" rather than "data source" terminology. Need to add a
// "function" class in the docs repo to replace "datasource".
fmt.Fprintf(w, " <li><a href=\"%s\"><span class=\"symbol datasource\"></span>%s</a></li>\n", lower(f), f)
}
fmt.Fprintf(w, "</ul>\n\n")
}
fmt.Fprintf(w, "<h3>Package Details</h3>\n")
fmt.Fprintf(w, "<dl class=\"package-details\">\n")
fmt.Fprintf(w, " <dt>Repository</dt>\n")
fmt.Fprintf(w, " <dd>%s</dd>\n", mod.pkg.Repository)
fmt.Fprintf(w, " <dt>License</dt>\n")
fmt.Fprintf(w, " <dd>%s</dd>\n", mod.pkg.License)
if mod.pkg.Attribution != "" {
fmt.Fprintf(w, " <dt>Notes</dt>\n")
fmt.Fprintf(w, " <dd>%s</dd>\n", mod.pkg.Attribution)
}
fmt.Fprintf(w, "</dl>\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

View file

@ -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");

View file

@ -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
}

View file

@ -4,4 +4,4 @@ title: "{{ .Title }}"
block_external_search_index: true
---
{{- end }}
{{ end }}

28
pkg/codegen/docs/templates/index.tmpl vendored Normal file
View file

@ -0,0 +1,28 @@
---
title: "{{ .Title }}"
block_external_search_index: true
{{- if .Menu }}
menu:
reference:
parent: API Reference
{{- end }}
---
{{ htmlSafe "<!-- WARNING: this file was generated by" }} {{ .Tool }}. {{ htmlSafe "-->" }}
{{ htmlSafe "<!-- Do not edit by hand unless you're certain you know what you are doing! -->" }}
{{ 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 }}

View file

@ -0,0 +1,26 @@
{{- define "index_modules" }}
<h3>Modules</h3>
<ul class="api">
{{- range . }}
<li><a href="{{ .Link }}" title="{{ .DisplayName }}"><span class="symbol module"></span>{{ .DisplayName }}</a></li>
{{- end }}
</ul>
{{- end }}
{{- define "index_resources" }}
<h3>Resources</h3>
<ul class="api">
{{- range . }}
<li><a href="{{ .Link }}" title="{{ .DisplayName }}"><span class="symbol resource"></span>{{ .DisplayName }}</a></li>
{{- end }}
</ul>
{{- end }}
{{- define "index_functions" }}
<h3>Functions</h3>
<ul class="api">
{{- range . }}
<li><a href="{{ .Link }}" title="{{ .DisplayName }}"><span class="symbol function"></span>{{ .DisplayName }}</a></li>
{{- end }}
</ul>
{{- end }}

View file

@ -0,0 +1,13 @@
{{ define "package_details" }}
<h3>Package Details</h3>
<dl class="package-details">
<dt>Repository</dt>
<dd><a href="{{ htmlSafe .Repository }}">{{ htmlSafe .Repository }}</a></dd>
<dt>License</dt>
<dd>{{ htmlSafe .License }}</dd>
{{ if ne .Notes "" -}}
<dt>Notes</dt>
<dd>{{ htmlSafe .Notes }}</dd>
{{- end }}
</dl>
{{ end }}

View file

@ -1,5 +1,7 @@
{{ template "header" .Header }}
{{- htmlSafe .DeprecationMessage }}
{{ htmlSafe .Comment }}
<!-- Create resource -->
@ -138,14 +140,4 @@ The following state arguments are supported:
{{ end }}
<h3>Package Details</h3>
<dl class="package-details">
<dt>Repository</dt>
<dd><a href="{{ htmlSafe .PackageDetails.Repository }}">{{ htmlSafe .PackageDetails.Repository }}</a></dd>
<dt>License</dt>
<dd>{{ htmlSafe .PackageDetails.License }}</dd>
{{- if ne .PackageDetails.Notes "" -}}
<dt>Notes</dt>
<dd>{{ htmlSafe .PackageDetails.Notes }}</dd>
{{- end -}}
</dl>
{{ template "package_details" .PackageDetails }}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)

View file

@ -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 {