github.com/kazhuravlev/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 }