github.com/nikron/prototool@v1.3.0/internal/create/handler.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 create
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"io/ioutil"
    28  	"os"
    29  	"path/filepath"
    30  	"strings"
    31  	"text/template"
    32  
    33  	"github.com/uber/prototool/internal/protostrs"
    34  	"github.com/uber/prototool/internal/settings"
    35  	"go.uber.org/zap"
    36  )
    37  
    38  var tmpl = template.Must(template.New("tmpl").Parse(`syntax = "proto3";
    39  
    40  package {{.Pkg}};
    41  
    42  option go_package = "{{.GoPkg}}";
    43  option java_multiple_files = true;
    44  option java_outer_classname = "{{.JavaOuterClassname}}";
    45  option java_package = "{{.JavaPkg}}";`))
    46  
    47  type tmplData struct {
    48  	Pkg                string
    49  	GoPkg              string
    50  	JavaOuterClassname string
    51  	JavaPkg            string
    52  }
    53  
    54  type handler struct {
    55  	logger         *zap.Logger
    56  	configProvider settings.ConfigProvider
    57  	pkg            string
    58  }
    59  
    60  func newHandler(options ...HandlerOption) *handler {
    61  	handler := &handler{
    62  		logger: zap.NewNop(),
    63  	}
    64  	for _, option := range options {
    65  		option(handler)
    66  	}
    67  	handler.configProvider = settings.NewConfigProvider(
    68  		settings.ConfigProviderWithLogger(handler.logger),
    69  	)
    70  	return handler
    71  }
    72  
    73  func (h *handler) Create(filePaths ...string) error {
    74  	for _, filePath := range filePaths {
    75  		if err := h.checkFilePath(filePath); err != nil {
    76  			return err
    77  		}
    78  	}
    79  	for _, filePath := range filePaths {
    80  		if err := h.create(filePath); err != nil {
    81  			return err
    82  		}
    83  	}
    84  	return nil
    85  }
    86  
    87  func (h *handler) checkFilePath(filePath string) error {
    88  	if filePath == "" {
    89  		return errors.New("filePath empty")
    90  	}
    91  	dirPath := filepath.Dir(filePath)
    92  
    93  	fileInfo, err := os.Stat(dirPath)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if !fileInfo.IsDir() {
    98  		return fmt.Errorf("%q is not a directory somehow", dirPath)
    99  	}
   100  	if _, err := os.Stat(filePath); err == nil {
   101  		return fmt.Errorf("%q already exists", filePath)
   102  	}
   103  	return nil
   104  }
   105  
   106  func (h *handler) create(filePath string) error {
   107  	pkg, err := h.getPkg(filePath)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	data, err := getData(
   112  		&tmplData{
   113  			Pkg:                pkg,
   114  			GoPkg:              protostrs.GoPackage(pkg),
   115  			JavaOuterClassname: protostrs.JavaOuterClassname(filePath),
   116  			JavaPkg:            protostrs.JavaPackage(pkg),
   117  		},
   118  	)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	return ioutil.WriteFile(filePath, data, 0644)
   123  }
   124  
   125  func (h *handler) getPkg(filePath string) (string, error) {
   126  	if h.pkg != "" {
   127  		return h.pkg, nil
   128  	}
   129  	absFilePath, err := filepath.Abs(filePath)
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  	absDirPath := filepath.Dir(absFilePath)
   134  	config, err := h.configProvider.GetForDir(absDirPath)
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  	// no config file found, can't compute package
   139  	if config.DirPath == "" {
   140  		return DefaultPackage, nil
   141  	}
   142  	// we need to get all the matching directories and then choose the longest
   143  	// ie if you have a, a/b, we choose a/b
   144  	var longestCreateDirPath string
   145  	var longestBasePkg string
   146  	// note that createDirPath will always be absolute per the spec in
   147  	// the settings package
   148  	for createDirPath, basePkg := range config.Create.DirPathToBasePackage {
   149  		// TODO: cannot do rel right away because it will do ../.. if necessary
   150  		// strings.HasPrefix is not OS independent however
   151  		if !strings.HasPrefix(absDirPath, createDirPath) {
   152  			continue
   153  		}
   154  		if len(createDirPath) > len(longestCreateDirPath) {
   155  			longestCreateDirPath = createDirPath
   156  			longestBasePkg = basePkg
   157  		}
   158  	}
   159  	if longestCreateDirPath != "" {
   160  		rel, err := filepath.Rel(longestCreateDirPath, absDirPath)
   161  		if err != nil {
   162  			return "", err
   163  		}
   164  		return getPkgFromRel(rel, longestBasePkg), nil
   165  	}
   166  
   167  	// no package mapping found, do default logic
   168  
   169  	// TODO: cannot do rel right away because it will do ../.. if necessary
   170  	// strings.HasPrefix is not OS independent however
   171  	if !strings.HasPrefix(absDirPath, config.DirPath) {
   172  		return DefaultPackage, nil
   173  	}
   174  	rel, err := filepath.Rel(config.DirPath, absDirPath)
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  	return getPkgFromRel(rel, ""), nil
   179  }
   180  
   181  func getPkgFromRel(rel string, basePkg string) string {
   182  	if rel == "." {
   183  		if basePkg == "" {
   184  			return DefaultPackage
   185  		}
   186  		return basePkg
   187  	}
   188  	relPkg := strings.Join(strings.Split(rel, string(os.PathSeparator)), ".")
   189  	if basePkg == "" {
   190  		return relPkg
   191  	}
   192  	return basePkg + "." + relPkg
   193  }
   194  
   195  func getData(tmplData *tmplData) ([]byte, error) {
   196  	buffer := bytes.NewBuffer(nil)
   197  	if err := tmpl.Execute(buffer, tmplData); err != nil {
   198  		return nil, err
   199  	}
   200  	return buffer.Bytes(), nil
   201  }