github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/pkg/genall/options.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     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 genall
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"sigs.k8s.io/controller-tools/pkg/markers"
    24  )
    25  
    26  var (
    27  	InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil)))
    28  )
    29  
    30  // +controllertools:marker:generateHelp:category=""
    31  
    32  // InputPaths represents paths and go-style path patterns to use as package roots.
    33  //
    34  // Multiple paths can be specified using "{path1, path2, path3}".
    35  type InputPaths []string
    36  
    37  // RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.
    38  // At this point, that's just InputPaths.
    39  func RegisterOptionsMarkers(into *markers.Registry) error {
    40  	if err := into.Register(InputPathsMarker); err != nil {
    41  		return err
    42  	}
    43  	// NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen
    44  	if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp {
    45  		into.AddHelp(InputPathsMarker, helpGiver.Help())
    46  	}
    47  	return nil
    48  }
    49  
    50  // RegistryFromOptions produces just the marker registry that would be used by FromOptions, without
    51  // attempting to produce a full Runtime.  This can be useful if you want to display help without
    52  // trying to load roots.
    53  func RegistryFromOptions(optionsRegistry *markers.Registry, options []string) (*markers.Registry, error) {
    54  	protoRt, err := protoFromOptions(optionsRegistry, options)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	reg := &markers.Registry{}
    59  	if err := protoRt.Generators.RegisterMarkers(reg); err != nil {
    60  		return nil, err
    61  	}
    62  	return reg, nil
    63  }
    64  
    65  // FromOptions parses the options from markers stored in the given registry out into a runtime.
    66  // The markers in the registry must be either
    67  //
    68  // a) Generators
    69  // b) OutputRules
    70  // c) InputPaths
    71  //
    72  // The paths specified in InputPaths are loaded as package roots, and the combined with
    73  // the generators and the specified output rules to produce a runtime that can be run or
    74  // further modified.  Not default generators are used if none are specified -- you can check
    75  // the output and rerun for that.
    76  func FromOptions(optionsRegistry *markers.Registry, options []string) (*Runtime, error) {
    77  
    78  	protoRt, err := protoFromOptions(optionsRegistry, options)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	// make the runtime
    84  	genRuntime, err := protoRt.Generators.ForRoots(protoRt.Paths...)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	// attempt to figure out what the user wants without a lot of verbose specificity:
    90  	// if the user specifies a default rule, assume that they probably want to fall back
    91  	// to that.  Otherwise, assume that they just wanted to customize one option from the
    92  	// set, and leave the rest in the standard configuration.
    93  	if protoRt.OutputRules.Default != nil {
    94  		genRuntime.OutputRules = protoRt.OutputRules
    95  		return genRuntime, nil
    96  	}
    97  
    98  	outRules := DirectoryPerGenerator("config", protoRt.GeneratorsByName)
    99  	for gen, rule := range protoRt.OutputRules.ByGenerator {
   100  		outRules.ByGenerator[gen] = rule
   101  	}
   102  
   103  	genRuntime.OutputRules = outRules
   104  	return genRuntime, nil
   105  }
   106  
   107  // protoFromOptions returns a proto-Runtime from the given options registry and
   108  // options set.  This can then be used to construct an actual Runtime.  See the
   109  // FromOptions function for more details about how the options work.
   110  func protoFromOptions(optionsRegistry *markers.Registry, options []string) (protoRuntime, error) {
   111  	var gens Generators
   112  	rules := OutputRules{
   113  		ByGenerator: make(map[*Generator]OutputRule),
   114  	}
   115  	var paths []string
   116  
   117  	// collect the generators first, so that we can key the output on the actual
   118  	// generator, which matters if there's settings in the gen object and it's not a pointer.
   119  	outputByGen := make(map[string]OutputRule)
   120  	gensByName := make(map[string]*Generator)
   121  
   122  	for _, rawOpt := range options {
   123  		if rawOpt[0] != '+' {
   124  			rawOpt = "+" + rawOpt // add a `+` to make it acceptable for usage with the registry
   125  		}
   126  		defn := optionsRegistry.Lookup(rawOpt, markers.DescribesPackage)
   127  		if defn == nil {
   128  			return protoRuntime{}, fmt.Errorf("unknown option %q", rawOpt[1:])
   129  		}
   130  
   131  		val, err := defn.Parse(rawOpt)
   132  		if err != nil {
   133  			return protoRuntime{}, fmt.Errorf("unable to parse option %q: %w", rawOpt[1:], err)
   134  		}
   135  
   136  		switch val := val.(type) {
   137  		case Generator:
   138  			gens = append(gens, &val)
   139  			gensByName[defn.Name] = &val
   140  		case OutputRule:
   141  			_, genName := splitOutputRuleOption(defn.Name)
   142  			if genName == "" {
   143  				// it's a default rule
   144  				rules.Default = val
   145  				continue
   146  			}
   147  
   148  			outputByGen[genName] = val
   149  			continue
   150  		case InputPaths:
   151  			paths = append(paths, val...)
   152  		default:
   153  			return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name)
   154  		}
   155  	}
   156  
   157  	// actually associate the rules now that we know the generators
   158  	for genName, outputRule := range outputByGen {
   159  		gen, knownGen := gensByName[genName]
   160  		if !knownGen {
   161  			return protoRuntime{}, fmt.Errorf("non-invoked generator %q", genName)
   162  		}
   163  
   164  		rules.ByGenerator[gen] = outputRule
   165  	}
   166  
   167  	return protoRuntime{
   168  		Paths:            paths,
   169  		Generators:       Generators(gens),
   170  		OutputRules:      rules,
   171  		GeneratorsByName: gensByName,
   172  	}, nil
   173  }
   174  
   175  // protoRuntime represents the raw pieces needed to compose a runtime, as
   176  // parsed from some options.
   177  type protoRuntime struct {
   178  	Paths            []string
   179  	Generators       Generators
   180  	OutputRules      OutputRules
   181  	GeneratorsByName map[string]*Generator
   182  }
   183  
   184  // splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule"
   185  // into its compontent rule and generator name.
   186  func splitOutputRuleOption(name string) (ruleName string, genName string) {
   187  	parts := strings.SplitN(name, ":", 3)
   188  	if len(parts) == 3 {
   189  		// output:<generator>:<rule>
   190  		return parts[2], parts[1]
   191  	}
   192  	// output:<rule>
   193  	return parts[1], ""
   194  }