github.com/galaxyobe/gen@v0.0.0-20220910125335-392fa8f0990f/cmd/option-gen/generators/generator.go (about)

     1  /*
     2   Copyright 2022 Galaxyobe.
     3  
     4   Licensed under the Apache License, Version 2.0 (the "License");
     5   you may not use this file except in compliance with the License.
     6   You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10   Unless required by applicable law or agreed to in writing, software
    11   distributed under the License is distributed on an "AS IS" BASIS,
    12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   See the License for the specific language governing permissions and
    14   limitations under the License.
    15  */
    16  
    17  package generators
    18  
    19  import (
    20  	"io"
    21  	"strings"
    22  
    23  	"k8s.io/gengo/generator"
    24  	"k8s.io/gengo/namer"
    25  	"k8s.io/gengo/types"
    26  	"k8s.io/klog/v2"
    27  
    28  	"github.com/galaxyobe/gen/pkg/util"
    29  	tpgenerator "github.com/galaxyobe/gen/third_party/gengo/generator"
    30  
    31  	"github.com/galaxyobe/gen/third_party/gengo/parser"
    32  )
    33  
    34  type GenType struct {
    35  	*types.Type
    36  	AllowFields  []string
    37  	OptionName   string
    38  	OptionSuffix string
    39  }
    40  
    41  func NewGenTypes(pkg *types.Package) (pkgEnabled bool, genTypes GenTypes) {
    42  	for _, t := range pkg.Types {
    43  		ut := util.UnderlyingType(t)
    44  		switch ut.Kind {
    45  		case types.Struct:
    46  		case types.Func:
    47  			genTypes.Functions = append(genTypes.Functions, ut.Name.Name)
    48  		default:
    49  			continue
    50  		}
    51  		comments := t.CommentLines
    52  		setTag, enabled := util.GetTagBoolStatus(tagName, comments)
    53  		setName, name := util.GetTagValueStatus(tagTypeName, comments)
    54  		setSuffix, suffix := util.GetTagValueStatus(tagSuffixName, comments)
    55  		allowedFields := util.GetTagValues(tagSelectFieldsName, comments)
    56  		if len(allowedFields) > 0 || setName || setSuffix {
    57  			setTag = true
    58  			enabled = true
    59  		}
    60  		if !setTag || !enabled {
    61  			continue // ignore type
    62  		}
    63  		optionName := getOptionName(t.Name.Name)
    64  		if !setName {
    65  			name = optionName
    66  		}
    67  		if !setSuffix {
    68  			suffix = name
    69  		}
    70  		pkgEnabled = true
    71  		genTypes.Types = append(genTypes.Types, &GenType{
    72  			Type:         t,
    73  			AllowFields:  allowedFields,
    74  			OptionName:   name,
    75  			OptionSuffix: suffix,
    76  		})
    77  	}
    78  	if len(genTypes.Types) == 0 {
    79  		pkgEnabled = false
    80  		return
    81  	}
    82  	for name := range pkg.Functions {
    83  		genTypes.Functions = append(genTypes.Functions, name)
    84  	}
    85  	return
    86  }
    87  
    88  type GenTypes struct {
    89  	Types     []*GenType
    90  	Functions []string
    91  }
    92  
    93  func (g GenTypes) find(t *types.Type) *GenType {
    94  	for _, item := range g.Types {
    95  		if item.Name.Name == t.Name.Name && item.Name.Package == t.Name.Package {
    96  			return item
    97  		}
    98  	}
    99  	return nil
   100  }
   101  
   102  func (g GenTypes) allowed(t *types.Type) bool {
   103  	return g.find(t) != nil
   104  }
   105  
   106  // allowedField allowed type's field to generate Option func.
   107  // will be ignored field enabled status when it's in +gen:option:fields allowed fields.
   108  func (g GenTypes) allowedField(t *types.Type, m int) bool {
   109  	for _, item := range g.Types {
   110  		if item.Name.Name == t.Name.Name && item.Name.Package == t.Name.Package {
   111  			if len(t.Members) == 0 {
   112  				return true
   113  			}
   114  			field := t.Members[m]
   115  			set, enable := util.GetTagBoolStatus(tagFieldName, field.CommentLines)
   116  			if set && !enable && len(item.AllowFields) == 0 {
   117  				return false
   118  			}
   119  			if len(item.AllowFields) == 0 {
   120  				return true
   121  			}
   122  			return util.Exist(item.AllowFields, field.Name)
   123  		}
   124  	}
   125  	return false
   126  }
   127  
   128  func (g GenTypes) existMethod(funcName string) bool {
   129  	for _, item := range g.Functions {
   130  		if item == funcName {
   131  			return true
   132  		}
   133  	}
   134  	return false
   135  }
   136  
   137  type genOption struct {
   138  	generator.DefaultGen
   139  	build         *parser.Builder
   140  	targetPackage string
   141  	boundingDirs  []string
   142  	imports       namer.ImportTracker
   143  	genTypes      GenTypes
   144  	packageTypes  util.PackageTypes
   145  }
   146  
   147  func NewGenOption(build *parser.Builder, sanitizedName, targetPackage string, boundingDirs []string, genTypes GenTypes, sourcePath string) generator.Generator {
   148  	return &genOption{
   149  		DefaultGen: generator.DefaultGen{
   150  			OptionalName: sanitizedName,
   151  		},
   152  		build:         build,
   153  		targetPackage: targetPackage,
   154  		boundingDirs:  boundingDirs,
   155  		imports:       generator.NewImportTracker(),
   156  		genTypes:      genTypes,
   157  		packageTypes:  util.NewPackageTypes(build),
   158  	}
   159  }
   160  
   161  func (g *genOption) Name() string {
   162  	return "option"
   163  }
   164  
   165  func (g *genOption) Filter(c *generator.Context, t *types.Type) bool {
   166  	ok := g.genTypes.allowed(t)
   167  	if !ok {
   168  		klog.V(5).Infof("Ignore generate option function for type %v", t)
   169  	}
   170  	return ok
   171  }
   172  
   173  func (g *genOption) Namers(c *generator.Context) namer.NameSystems {
   174  	// Have the raw namer for this file track what it imports.
   175  	return namer.NameSystems{
   176  		"raw": namer.NewRawNamer(g.targetPackage, g.imports),
   177  	}
   178  }
   179  
   180  func (g *genOption) Init(c *generator.Context, w io.Writer) error {
   181  	return nil
   182  }
   183  
   184  func (g *genOption) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
   185  	klog.V(5).Infof("Generating option function for type %v", t)
   186  	sw := tpgenerator.NewSnippetWriter(w, c, "", "")
   187  	sw.AddFunc("slice", func(s string) string {
   188  		if strings.HasPrefix(s, "[]") {
   189  			return strings.ReplaceAll(s, "[]", "...")
   190  		}
   191  		return s
   192  	})
   193  	g.genOptionType(sw, t)
   194  	g.genWithFieldFunc(sw, t)
   195  	sw.Do("\n", nil)
   196  
   197  	return sw.Error()
   198  }
   199  
   200  func (g *genOption) isOtherPackage(pkg string) bool {
   201  	if pkg == g.targetPackage {
   202  		return false
   203  	}
   204  	if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") {
   205  		return false
   206  	}
   207  	return true
   208  }
   209  
   210  func (g *genOption) Imports(c *generator.Context) (imports []string) {
   211  	var importLines []string
   212  	for _, singleImport := range g.imports.ImportLines() {
   213  		if g.isOtherPackage(singleImport) {
   214  			importLines = append(importLines, singleImport)
   215  		}
   216  	}
   217  	return importLines
   218  }
   219  
   220  func getOptionName(typeName string) string {
   221  	if strings.HasSuffix(typeName, "Option") {
   222  		return typeName
   223  	}
   224  	return typeName + "Option"
   225  }
   226  
   227  func (g *genOption) getOptionSuffixName(t *types.Type) string {
   228  	suffix := getOptionName(t.Name.Name)
   229  	if v := g.genTypes.find(t); v != nil {
   230  		suffix = v.OptionSuffix
   231  	}
   232  	return suffix
   233  }
   234  
   235  func (g *genOption) getOptionTypeName(t *types.Type) string {
   236  	name := getOptionName(t.Name.Name)
   237  	if v := g.genTypes.find(t); v != nil {
   238  		name = v.OptionName
   239  	}
   240  	return name
   241  }
   242  
   243  func (g *genOption) genOptionType(sw *tpgenerator.SnippetWriter, t *types.Type) {
   244  	name := g.getOptionTypeName(t)
   245  	if g.genTypes.existMethod(name) {
   246  		return
   247  	}
   248  	args := generator.Args{
   249  		"type": t,
   250  		"name": name,
   251  	}
   252  	sw.Do("type {{.name}} func(* {{.type|public}})\n", args)
   253  }
   254  
   255  func (g *genOption) genWithFieldFunc(sw *tpgenerator.SnippetWriter, t *types.Type) {
   256  	isExternalType := g.packageTypes.IsExternalType(t.Name.Package, t.Name.Name)
   257  	option := g.getOptionTypeName(t)
   258  	suffix := g.getOptionSuffixName(t)
   259  	methodGen := util.NewMethodGenerate(util.GenName("With", suffix))
   260  	for idx, m := range t.Members {
   261  		if util.IsLower(m.Name) && isExternalType {
   262  			continue
   263  		}
   264  		if !g.genTypes.allowedField(t, idx) {
   265  			continue
   266  		}
   267  		method := methodGen.GenName(m.Name)
   268  		if g.genTypes.existMethod(method) {
   269  			continue
   270  		}
   271  		args := generator.Args{
   272  			"type":   t,
   273  			"option": option,
   274  			"field":  m,
   275  			"method": method,
   276  		}
   277  		sw.Do("func {{.method}} (val {{.field.Type|raw|slice}}) {{.option}} {\n", args)
   278  		sw.Do("return func(object * {{.type|public}}) {\n", args)
   279  		sw.Do("object.{{.field.Name}} = val\n", args)
   280  		sw.Do("}\n", nil)
   281  		sw.Do("}\n\n", nil)
   282  	}
   283  }