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