2021-08-12 21:31:15 +00:00
// Copyright 2016-2021, 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.
//
2023-01-06 00:07:45 +00:00
//nolint:lll, goconst
2021-08-12 21:31:15 +00:00
package docs
import (
"bytes"
"fmt"
"sort"
"strings"
2024-07-09 14:54:50 +00:00
"github.com/golang/glog"
2021-08-12 21:31:15 +00:00
"github.com/pulumi/pulumi/pkg/v3/codegen/python"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
2023-06-28 16:02:04 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
2022-12-12 14:55:35 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
2021-08-12 21:31:15 +00:00
)
type methodDocArgs struct {
Title string
ResourceName string
DeprecationMessage string
Comment string
2024-07-09 14:54:50 +00:00
ExamplesSection examplesSection
2021-08-12 21:31:15 +00:00
// MethodName is a map of the language and the method name in that language.
MethodName map [ string ] string
// MethodArgs is map per language view of the parameters
// in the method.
MethodArgs map [ string ] string
// MethodResult is a map per language property types
// that is returned as a result of calling a method.
MethodResult map [ string ] propertyType
// InputProperties is a map per language and the corresponding slice
// of input properties accepted by the method.
InputProperties map [ string ] [ ] property
// OutputProperties is a map per language and the corresponding slice
// of output properties, which are properties of the MethodResult type.
OutputProperties map [ string ] [ ] property
}
func ( mod * modContext ) genMethods ( r * schema . Resource ) [ ] methodDocArgs {
2023-06-28 16:02:04 +00:00
methods := slice . Prealloc [ methodDocArgs ] ( len ( r . Methods ) )
2021-08-12 21:31:15 +00:00
for _ , m := range r . Methods {
methods = append ( methods , mod . genMethod ( r , m ) )
}
return methods
}
func ( mod * modContext ) genMethod ( r * schema . Resource , m * schema . Method ) methodDocArgs {
2021-10-06 15:03:21 +00:00
dctx := mod . docGenContext
2021-08-12 21:31:15 +00:00
f := m . Function
inputProps , outputProps := make ( map [ string ] [ ] property ) , make ( map [ string ] [ ] property )
2021-10-06 15:03:21 +00:00
for _ , lang := range dctx . supportedLanguages {
2021-08-12 21:31:15 +00:00
if f . Inputs != nil {
exclude := func ( name string ) bool {
return name == "__self__"
}
2021-08-13 00:20:13 +00:00
props := mod . getPropertiesWithIDPrefixAndExclude ( f . Inputs . Properties , lang , true , false , false ,
2023-12-12 12:19:42 +00:00
m . Name + "_arg_" , exclude )
2021-08-12 21:31:15 +00:00
if len ( props ) > 0 {
inputProps [ lang ] = props
}
}
2023-01-11 22:17:14 +00:00
if f . ReturnType != nil {
if objectType , ok := f . ReturnType . ( * schema . ObjectType ) ; ok && objectType != nil {
outputProps [ lang ] = mod . getPropertiesWithIDPrefixAndExclude ( objectType . Properties , lang , false , false , false ,
2023-12-12 12:19:42 +00:00
m . Name + "_result_" , nil )
2023-01-11 22:17:14 +00:00
}
2021-08-12 21:31:15 +00:00
}
}
// Generate the per-language map for the method name.
methodNameMap := map [ string ] string { }
2021-10-06 15:03:21 +00:00
for _ , lang := range dctx . supportedLanguages {
docHelper := dctx . getLanguageDocHelper ( lang )
2021-08-12 21:31:15 +00:00
methodNameMap [ lang ] = docHelper . GetMethodName ( m )
}
2024-07-09 14:54:50 +00:00
defer glog . Flush ( )
resourceSnippetLanguages := mod . docGenContext . getSupportedSnippetLanguages ( r . IsOverlay , r . OverlaySupportedLanguages )
methodSnippetLanguages := mod . docGenContext . getSupportedSnippetLanguages ( f . IsOverlay , f . OverlaySupportedLanguages )
if resourceSnippetLanguages != methodSnippetLanguages {
// All code choosers are tied together. If the method doesn't support the same languages as the resource, we
// default to the resource's supported languages for consistency.
glog . Warningf ( "Resource %s (%s) and method %s (%s) have different supported snippet languages. Defaulting to the resources supported snippet languages." ,
r . Token , resourceSnippetLanguages , m . Name , methodSnippetLanguages )
}
docInfo := dctx . decomposeDocstring ( f . Comment , resourceSnippetLanguages )
2021-08-12 21:31:15 +00:00
args := methodDocArgs {
Title : title ( m . Name , "" ) ,
ResourceName : resourceName ( r ) ,
MethodName : methodNameMap ,
MethodArgs : mod . genMethodArgs ( r , m , methodNameMap ) ,
MethodResult : mod . getMethodResult ( r , m ) ,
Comment : docInfo . description ,
DeprecationMessage : f . DeprecationMessage ,
2024-07-09 14:54:50 +00:00
ExamplesSection : examplesSection {
Examples : docInfo . examples ,
LangChooserLanguages : resourceSnippetLanguages ,
} ,
2021-08-12 21:31:15 +00:00
InputProperties : inputProps ,
OutputProperties : outputProps ,
}
return args
}
func ( mod * modContext ) genMethodTS ( f * schema . Function , resourceName , methodName string ,
2023-03-03 16:36:39 +00:00
optionalArgs bool ,
) [ ] formalParam {
2021-08-12 21:31:15 +00:00
argsType := fmt . Sprintf ( "%s.%sArgs" , resourceName , title ( methodName , "nodejs" ) )
var optionalFlag string
if optionalArgs {
optionalFlag = "?"
}
var params [ ] formalParam
if f . Inputs != nil {
params = append ( params , formalParam {
Name : "args" ,
OptionalFlag : optionalFlag ,
Type : propertyType {
Name : argsType ,
} ,
} )
}
return params
}
func ( mod * modContext ) genMethodGo ( f * schema . Function , resourceName , methodName string ,
2023-03-03 16:36:39 +00:00
optionalArgs bool ,
) [ ] formalParam {
2021-08-12 21:31:15 +00:00
argsType := fmt . Sprintf ( "%s%sArgs" , resourceName , title ( methodName , "go" ) )
params := [ ] formalParam {
{
Name : "ctx" ,
OptionalFlag : "*" ,
Type : propertyType {
Name : "Context" ,
Link : "https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/pulumi?tab=doc#Context" ,
} ,
} ,
}
var optionalFlag string
if optionalArgs {
optionalFlag = "*"
}
if f . Inputs != nil {
params = append ( params , formalParam {
Name : "args" ,
OptionalFlag : optionalFlag ,
Type : propertyType {
Name : argsType ,
} ,
} )
}
return params
}
func ( mod * modContext ) genMethodCS ( f * schema . Function , resourceName , methodName string , optionalArgs bool ) [ ] formalParam {
argsType := fmt . Sprintf ( "%s.%sArgs" , resourceName , title ( methodName , "csharp" ) )
var params [ ] formalParam
if f . Inputs != nil {
var optionalFlag string
if optionalArgs {
optionalFlag = "?"
}
params = append ( params , formalParam {
Name : "args" ,
OptionalFlag : optionalFlag ,
DefaultValue : "" ,
Type : propertyType {
Name : argsType ,
} ,
} )
}
return params
}
func ( mod * modContext ) genMethodPython ( f * schema . Function ) [ ] formalParam {
2021-10-06 15:03:21 +00:00
dctx := mod . docGenContext
docLanguageHelper := dctx . getLanguageDocHelper ( "python" )
2021-08-12 21:31:15 +00:00
var params [ ] formalParam
params = append ( params , formalParam {
Name : "self" ,
} )
if f . Inputs != nil {
// Filter out the __self__ argument from the inputs.
2023-06-28 16:02:04 +00:00
args := slice . Prealloc [ * schema . Property ] ( len ( f . Inputs . InputShape . Properties ) - 1 )
2021-08-12 21:31:15 +00:00
for _ , arg := range f . Inputs . InputShape . Properties {
if arg . Name == "__self__" {
continue
}
args = append ( args , arg )
}
// Sort required args first.
sort . Slice ( args , func ( i , j int ) bool {
pi , pj := args [ i ] , args [ j ]
switch {
case pi . IsRequired ( ) != pj . IsRequired ( ) :
return pi . IsRequired ( ) && ! pj . IsRequired ( )
default :
return pi . Name < pj . Name
}
} )
for _ , arg := range args {
2022-12-12 14:55:35 +00:00
def , err := mod . pkg . Definition ( )
2023-02-17 01:23:09 +00:00
contract . AssertNoErrorf ( err , "failed to get definition for package %q" , mod . pkg . Name ( ) )
2022-12-12 14:55:35 +00:00
typ := docLanguageHelper . GetLanguageTypeString ( def , mod . mod , arg . Type , true /*input*/ )
2021-08-12 21:31:15 +00:00
var defaultValue string
if ! arg . IsRequired ( ) {
defaultValue = " = None"
}
params = append ( params , formalParam {
Name : python . PyName ( arg . Name ) ,
DefaultValue : defaultValue ,
Type : propertyType {
Name : typ ,
} ,
} )
}
}
return params
}
// genMethodArgs generates the arguments string for a given method that can be
// rendered directly into a template. An empty string indicates no args.
func ( mod * modContext ) genMethodArgs ( r * schema . Resource , m * schema . Method ,
2023-03-03 16:36:39 +00:00
methodNameMap map [ string ] string ,
) map [ string ] string {
2021-10-06 15:03:21 +00:00
dctx := mod . docGenContext
2021-08-12 21:31:15 +00:00
f := m . Function
functionParams := make ( map [ string ] string )
2021-10-06 15:03:21 +00:00
for _ , lang := range dctx . supportedLanguages {
2021-08-12 21:31:15 +00:00
var (
paramTemplate string
params [ ] formalParam
)
b := & bytes . Buffer { }
paramSeparatorTemplate := "param_separator"
ps := paramSeparator { }
var hasArgs bool
optionalArgs := true
if f . Inputs != nil {
for _ , arg := range f . Inputs . InputShape . Properties {
if arg . Name == "__self__" {
continue
}
hasArgs = true
if arg . IsRequired ( ) {
optionalArgs = false
}
}
}
if ! hasArgs {
functionParams [ lang ] = ""
continue
}
switch lang {
case "nodejs" :
params = mod . genMethodTS ( f , resourceName ( r ) , methodNameMap [ "nodejs" ] , optionalArgs )
paramTemplate = "ts_formal_param"
case "go" :
params = mod . genMethodGo ( f , resourceName ( r ) , methodNameMap [ "go" ] , optionalArgs )
paramTemplate = "go_formal_param"
case "csharp" :
params = mod . genMethodCS ( f , resourceName ( r ) , methodNameMap [ "csharp" ] , optionalArgs )
paramTemplate = "csharp_formal_param"
case "python" :
params = mod . genMethodPython ( f )
paramTemplate = "py_formal_param"
paramSeparatorTemplate = "py_param_separator"
2021-10-06 15:03:21 +00:00
docHelper := dctx . getLanguageDocHelper ( lang )
2021-08-12 21:31:15 +00:00
methodName := docHelper . GetMethodName ( m )
ps = paramSeparator { Indent : strings . Repeat ( " " , len ( "def (" ) + len ( methodName ) ) }
}
n := len ( params )
if n == 0 {
functionParams [ lang ] = ""
continue
}
for i , p := range params {
2021-10-06 15:03:21 +00:00
if err := dctx . templates . ExecuteTemplate ( b , paramTemplate , p ) ; err != nil {
2021-08-12 21:31:15 +00:00
panic ( err )
}
if i != n - 1 {
2021-10-06 15:03:21 +00:00
if err := dctx . templates . ExecuteTemplate ( b , paramSeparatorTemplate , ps ) ; err != nil {
2021-08-12 21:31:15 +00:00
panic ( err )
}
}
}
functionParams [ lang ] = b . String ( )
}
return functionParams
}
// getMethodResult returns a map of per-language information about the method result.
// An empty propertyType.Name indicates no result.
func ( mod * modContext ) getMethodResult ( r * schema . Resource , m * schema . Method ) map [ string ] propertyType {
resourceMap := make ( map [ string ] propertyType )
2021-10-06 15:03:21 +00:00
dctx := mod . docGenContext
2021-08-12 21:31:15 +00:00
var resultTypeName string
2021-10-06 15:03:21 +00:00
for _ , lang := range dctx . supportedLanguages {
2023-01-11 22:17:14 +00:00
if m . Function . ReturnType != nil {
2022-12-12 14:55:35 +00:00
def , err := mod . pkg . Definition ( )
2023-02-17 01:23:09 +00:00
contract . AssertNoErrorf ( err , "failed to get definition for package %q" , mod . pkg . Name ( ) )
2022-12-12 14:55:35 +00:00
resultTypeName = dctx . getLanguageDocHelper ( lang ) . GetMethodResultName ( def , mod . mod , r , m )
2021-08-12 21:31:15 +00:00
}
resourceMap [ lang ] = propertyType {
Name : resultTypeName ,
Link : fmt . Sprintf ( "#method_%s_result" , title ( m . Name , "" ) ) ,
}
}
return resourceMap
}