github.com/nikron/prototool@v1.3.0/internal/settings/settings.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package settings
    22  
    23  import (
    24  	"fmt"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"go.uber.org/zap"
    29  )
    30  
    31  const (
    32  	// DefaultConfigFilename is the default config filename.
    33  	DefaultConfigFilename = "prototool.yaml"
    34  
    35  	// GenPluginTypeNone says there is no specific plugin type.
    36  	GenPluginTypeNone GenPluginType = iota
    37  	// GenPluginTypeGo says the plugin is a Golang plugin that
    38  	// is or uses github.com/golang/protobuf.
    39  	// This will use GenGoPluginOptions.
    40  	GenPluginTypeGo
    41  	// GenPluginTypeGogo says the plugin is a Golang plugin that
    42  	// is or uses github.com/gogo/protobuf.
    43  	// This will use GenGoPluginOptions.
    44  	GenPluginTypeGogo
    45  )
    46  
    47  var (
    48  	// ConfigFilenames are all possible config filenames.
    49  	ConfigFilenames = []string{
    50  		DefaultConfigFilename,
    51  		"prototool.json",
    52  	}
    53  
    54  	_genPluginTypeToString = map[GenPluginType]string{
    55  		GenPluginTypeNone: "",
    56  		GenPluginTypeGo:   "go",
    57  		GenPluginTypeGogo: "gogo",
    58  	}
    59  	_stringToGenPluginType = map[string]GenPluginType{
    60  		"":     GenPluginTypeNone,
    61  		"go":   GenPluginTypeGo,
    62  		"gogo": GenPluginTypeGogo,
    63  	}
    64  
    65  	_genPluginTypeToIsGo = map[GenPluginType]bool{
    66  		GenPluginTypeNone: false,
    67  		GenPluginTypeGo:   true,
    68  		GenPluginTypeGogo: false,
    69  	}
    70  	_genPluginTypeToIsGogo = map[GenPluginType]bool{
    71  		GenPluginTypeNone: false,
    72  		GenPluginTypeGo:   false,
    73  		GenPluginTypeGogo: true,
    74  	}
    75  )
    76  
    77  // GenPluginType is a type of protoc plugin.
    78  type GenPluginType int
    79  
    80  // String implements fmt.Stringer.
    81  func (g GenPluginType) String() string {
    82  	if s, ok := _genPluginTypeToString[g]; ok {
    83  		return s
    84  	}
    85  	return strconv.Itoa(int(g))
    86  }
    87  
    88  // The Is functions do not validate if the plugin type is known
    89  // as this is supposed to be done in ConfigProvider.
    90  // It's a lot easier if they just return a bool.
    91  
    92  // IsGo returns true if the plugin type is associated with
    93  // github.com/golang/protobuf.
    94  func (g GenPluginType) IsGo() bool {
    95  	return _genPluginTypeToIsGo[g]
    96  }
    97  
    98  // IsGogo returns true if the plugin type is associated with
    99  // github.com/gogo/protobuf.
   100  func (g GenPluginType) IsGogo() bool {
   101  	return _genPluginTypeToIsGogo[g]
   102  }
   103  
   104  // ParseGenPluginType parses the GenPluginType from the given string.
   105  //
   106  // Input is case-insensitive.
   107  func ParseGenPluginType(s string) (GenPluginType, error) {
   108  	genPluginType, ok := _stringToGenPluginType[strings.ToLower(s)]
   109  	if !ok {
   110  		return GenPluginTypeNone, fmt.Errorf("could not parse %s to a GenPluginType", s)
   111  	}
   112  	return genPluginType, nil
   113  }
   114  
   115  // Config is the main config.
   116  //
   117  // Configs are derived from ExternalConfigs, which represent the Config
   118  // in a more palpable format for configuration via a config file
   119  // or flags.
   120  //
   121  // String slices will be deduped and sorted if returned from this package.
   122  // Configs will be validated if returned from this package.
   123  //
   124  // All paths returned should be absolute paths. Outside of this package,
   125  // all other internal packages should verify that all given paths are
   126  // absolute, except for the internal/text package.
   127  type Config struct {
   128  	// The working directory path.
   129  	// Expected to be absolute path.
   130  	DirPath string
   131  	// The prefixes to exclude.
   132  	// Expected to be absolute paths.
   133  	// Expected to be unique.
   134  	ExcludePrefixes []string
   135  	// The compile config.
   136  	Compile CompileConfig
   137  	// The create config.
   138  	Create CreateConfig
   139  	// Lint is a special case. If nothing is set, the defaults are used. Either IDs,
   140  	// or Group/IncludeIDs/ExcludeIDs can be set, but not both. There can be no overlap
   141  	// between IncludeIDs and ExcludeIDs.
   142  	Lint LintConfig
   143  	// The gen config.
   144  	Gen GenConfig
   145  }
   146  
   147  // CompileConfig is the compile config.
   148  type CompileConfig struct {
   149  	// The Protobuf version to use from https://github.com/protocolbuffers/protobuf/releases.
   150  	// Must have a valid protoc zip file asset, so for example 3.5.0 is a valid version
   151  	// but 3.5.0.1 is not.
   152  	ProtobufVersion string
   153  	// IncludePaths are the additional paths to include with -I to protoc.
   154  	// Expected to be absolute paths.
   155  	// Expected to be unique.
   156  	IncludePaths []string
   157  	// IncludeWellKnownTypes says to add the Google well-known types with -I to protoc.
   158  	IncludeWellKnownTypes bool
   159  	// AllowUnusedImports says to not error when an import is not used.
   160  	AllowUnusedImports bool
   161  }
   162  
   163  // CreateConfig is the create config.
   164  type CreateConfig struct {
   165  	// The map from directory to the package to use as the base.
   166  	// Directories expected to be absolute paths.
   167  	DirPathToBasePackage map[string]string
   168  }
   169  
   170  // LintConfig is the lint config.
   171  type LintConfig struct {
   172  	// NoDefault is set to exclude the default set of linters.
   173  	NoDefault bool
   174  	// IncludeIDs are the list of linter IDs to use in addition to the defaults.
   175  	// Expected to be all uppercase.
   176  	// Expected to be unique.
   177  	// Expected to have no overlap with ExcludeIDs.
   178  	IncludeIDs []string
   179  	// ExcludeIDs are the list of linter IDs to exclude from the defaults.
   180  	// Expected to be all uppercase.
   181  	// Expected to be unique.
   182  	// Expected to have no overlap with IncludeIDs.
   183  	ExcludeIDs []string
   184  	// IgnoreIDToFilePaths is the map of ID to absolute file path to ignore.
   185  	// IDs expected to be all upper-case.
   186  	// File paths expected to be absolute paths.
   187  	IgnoreIDToFilePaths map[string][]string
   188  }
   189  
   190  // GenConfig is the gen config.
   191  type GenConfig struct {
   192  	// The go plugin options.
   193  	GoPluginOptions GenGoPluginOptions
   194  	// The plugins.
   195  	// These will be sorted by name if returned from this package.
   196  	Plugins []GenPlugin
   197  }
   198  
   199  // GenGoPluginOptions are options for go plugins.
   200  //
   201  // This will be used for plugin types go, gogo, gogrpc, gogogrpc.
   202  type GenGoPluginOptions struct {
   203  	// The base import path. This should be the go path of the config file.
   204  	// This is required for go plugins.
   205  	ImportPath string
   206  	// ExtraModifiers to include with Mfile=package.
   207  	ExtraModifiers map[string]string
   208  }
   209  
   210  // GenPlugin is a plugin to use.
   211  type GenPlugin struct {
   212  	// The name of the plugin. For example, if you want to use
   213  	// protoc-gen-gogoslick, the name is "gogoslick".
   214  	Name string
   215  	// The path to the executable. For example, if the name is "grpc-cpp"
   216  	// but the path to the executable "protoc-gen-grpc-cpp" is "/usr/local/bin/grpc_cpp_plugin",
   217  	// then this will be "/usr/local/bin/grpc_cpp_plugin".
   218  	Path string
   219  	// The type, if any. This will be GenPluginTypeNone if
   220  	// there is no specific type.
   221  	Type GenPluginType
   222  	// Extra flags to pass.
   223  	// If there is an associated type, some flags may be generated,
   224  	// for example plugins=grpc or Mfile=package modifiers.
   225  	Flags string
   226  	// The path to output to.
   227  	// Must be relative in a config file.
   228  	OutputPath OutputPath
   229  }
   230  
   231  // OutputPath is an output path.
   232  //
   233  // We need the relative path for go package references for generation.
   234  // TODO: we might want all paths to have the given path and absolute path,
   235  // see if we need this.
   236  type OutputPath struct {
   237  	// Must be relative.
   238  	RelPath string
   239  	AbsPath string
   240  }
   241  
   242  // ExternalConfig is the external representation of Config.
   243  //
   244  // It is meant to be set by a YAML or JSON config file, or flags.
   245  type ExternalConfig struct {
   246  	Excludes []string `json:"excludes,omitempty" yaml:"excludes,omitempty"`
   247  	Protoc   struct {
   248  		AllowUnusedImports bool     `json:"allow_unused_imports,omitempty" yaml:"allow_unused_imports,omitempty"`
   249  		Version            string   `json:"version,omitempty" yaml:"version,omitempty"`
   250  		Includes           []string `json:"includes,omitempty" yaml:"includes,omitempty"`
   251  	} `json:"protoc,omitempty" yaml:"protoc,omitempty"`
   252  	Create struct {
   253  		Packages []struct {
   254  			Directory string `json:"directory,omitempty" yaml:"directory,omitempty"`
   255  			Name      string `json:"name,omitempty" yaml:"name,omitempty"`
   256  		} `json:"packages,omitempty" yaml:"packages,omitempty"`
   257  	} `json:"create,omitempty" yaml:"create,omitempty"`
   258  	Lint struct {
   259  		Ignores []struct {
   260  			ID    string   `json:"id,omitempty" yaml:"id,omitempty"`
   261  			Files []string `json:"files,omitempty" yaml:"files,omitempty"`
   262  		}
   263  		Rules struct {
   264  			NoDefault bool     `json:"no_default,omitempty" yaml:"no_default,omitempty"`
   265  			Add       []string `json:"add" yaml:"add"`
   266  			Remove    []string `json:"remove" yaml:"remove"`
   267  		}
   268  	} `json:"lint,omitempty" yaml:"lint,omitempty"`
   269  	Gen struct {
   270  		GoOptions struct {
   271  			ImportPath     string            `json:"import_path,omitempty" yaml:"import_path,omitempty"`
   272  			ExtraModifiers map[string]string `json:"extra_modifiers,omitempty" yaml:"extra_modifiers,omitempty"`
   273  		} `json:"go_options,omitempty" yaml:"go_options,omitempty"`
   274  		Plugins []struct {
   275  			Name   string `json:"name,omitempty" yaml:"name,omitempty"`
   276  			Type   string `json:"type,omitempty" yaml:"type,omitempty"`
   277  			Flags  string `json:"flags,omitempty" yaml:"flags,omitempty"`
   278  			Output string `json:"output,omitempty" yaml:"output,omitempty"`
   279  			Path   string `json:"path,omitempty" yaml:"path,omitempty"`
   280  		} `json:"plugins,omitempty" yaml:"plugins,omitempty"`
   281  	} `json:"generate,omitempty" yaml:"generate,omitempty"`
   282  }
   283  
   284  // ConfigProvider provides Configs.
   285  type ConfigProvider interface {
   286  	// GetForDir tries to find a file named by one of the ConfigFilenames starting in the
   287  	// given directory, and going up a directory until hitting root.
   288  	//
   289  	// The directory must be an absolute path.
   290  	//
   291  	// If such a file is found, it is read as an ExternalConfig and converted to a Config.
   292  	// If no such file is found, Config{} is returned.
   293  	// If multiple files named by one of the ConfigFilenames are found in the same
   294  	// directory, error is returned.
   295  	GetForDir(dirPath string) (Config, error)
   296  	// Get tries to find a file named filePath with a config.
   297  	//
   298  	// The path must be an absolute path.
   299  	// The file must have either the extension .yaml or .json.
   300  	//
   301  	// If such a file is found, it is read as an ExternalConfig and converted to a Config.
   302  	// If no such file is found, Config{} is returned.
   303  	Get(filePath string) (Config, error)
   304  	// GetFilePathForDir tries to find a file named by one of the ConfigFilenames starting in the
   305  	// given directory, and going up a directory until hitting root.
   306  	//
   307  	// The directory must be an absolute path.
   308  	//
   309  	// If such a file is found, it is returned.
   310  	// If no such file is found, "" is returned.
   311  	// If multiple files named by one of the ConfigFilenames are found in the same
   312  	// directory, error is returned.
   313  	GetFilePathForDir(dirPath string) (string, error)
   314  	// GetForData returns a Config for the given ExternalConfigData in JSON format.
   315  	// The Config will be as if there was a configuration file at the given dirPath.
   316  	GetForData(dirPath string, externalConfigData string) (Config, error)
   317  
   318  	// GetExcludePrefixesForDir tries to find a file named by one of the ConfigFilenames in the given
   319  	// directory and returns the cleaned absolute exclude prefixes. Unlike other functions
   320  	// on ConfigProvider, this has no recursive functionality - if there is no
   321  	// config file, nothing is returned.
   322  	// If multiple files named by one of the ConfigFilenames are found in the same
   323  	// directory, error is returned.
   324  	GetExcludePrefixesForDir(dirPath string) ([]string, error)
   325  	// GetExcludePrefixesForData gets the exclude prefixes for the given ExternalConfigData in JSON format.
   326  	// The logic will act is if there was a configuration file at the given dirPath.
   327  	GetExcludePrefixesForData(dirPath string, externalConfigData string) ([]string, error)
   328  }
   329  
   330  // ConfigProviderOption is an option for a new ConfigProvider.
   331  type ConfigProviderOption func(*configProvider)
   332  
   333  // ConfigProviderWithLogger returns a ConfigProviderOption that uses the given logger.
   334  //
   335  // The default is to use zap.NewNop().
   336  func ConfigProviderWithLogger(logger *zap.Logger) ConfigProviderOption {
   337  	return func(configProvider *configProvider) {
   338  		configProvider.logger = logger
   339  	}
   340  }
   341  
   342  // NewConfigProvider returns a new ConfigProvider.
   343  func NewConfigProvider(options ...ConfigProviderOption) ConfigProvider {
   344  	return newConfigProvider(options...)
   345  }