github.com/swaros/contxt/module/taskrun@v0.0.0-20240305083542-3dbd4436ac40/template.go (about) 1 // Copyright (c) 2020 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved. 2 // 3 // # Licensed under the MIT License 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in all 13 // copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 // SOFTWARE. 22 package taskrun 23 24 import ( 25 "errors" 26 "fmt" 27 28 "os" 29 "os/user" 30 "path/filepath" 31 "regexp" 32 "strconv" 33 "strings" 34 35 "github.com/imdario/mergo" 36 37 "github.com/swaros/contxt/module/configure" 38 "github.com/swaros/contxt/module/dirhandle" 39 "github.com/swaros/contxt/module/systools" 40 "github.com/swaros/manout" 41 "gopkg.in/yaml.v2" 42 ) 43 44 const ( 45 incFileParseError = 105 46 mainFileParseError = 104 47 ) 48 49 // FindTemplate searchs for Template files in different spaces 50 func FindTemplate() (string, bool) { 51 // 1. looking in user mirror path 52 usr, err := user.Current() 53 if err != nil { 54 GetLogger().Fatal(err) 55 } 56 57 homeDirYml := usr.HomeDir + configure.DefaultPath + configure.MirrorPath + DefaultExecYaml 58 exists, exerr := dirhandle.Exists(homeDirYml) 59 if exerr == nil && exists { 60 return homeDirYml, true 61 } 62 63 // 2. looking in current path with user name as prefix 64 dir, curerr := dirhandle.Current() 65 if curerr == nil { 66 userYml := dir + string(os.PathSeparator) + usr.Username + defaultExecYamlName 67 exists, exerr = dirhandle.Exists(userYml) 68 if exerr == nil && exists { 69 return userYml, true 70 } 71 72 // 3. plain template in current dir 73 regularPath := dir + DefaultExecYaml 74 exists, exerr = dirhandle.Exists(regularPath) 75 if exerr == nil && exists { 76 return regularPath, true 77 } 78 } 79 80 return "", false 81 } 82 83 // GetTemplate return current template, the absolute path,if it exists, any error 84 func GetTemplate() (configure.RunConfig, string, bool, error) { 85 86 foundPath, success := FindTemplate() 87 var template configure.RunConfig 88 if !success { 89 return template, "", false, errors.New("template not found or have failures") 90 } 91 ctemplate, err := GetPwdTemplate(foundPath) 92 if err == nil { 93 // checking required shared templates 94 // and merge them into the current template 95 if len(ctemplate.Config.Require) > 0 { 96 for _, reqSource := range ctemplate.Config.Require { 97 GetLogger().WithField("path", reqSource).Debug("handle required ") 98 fullPath, pathError := CheckOrCreateUseConfig(reqSource) 99 if pathError == nil { 100 GetLogger().WithField("path", fullPath).Info("require path resolved") 101 subTemplate, tError := GetPwdTemplate(fullPath + string(os.PathSeparator) + DefaultExecYaml) 102 if tError == nil { 103 mergo.Merge(&ctemplate, subTemplate, mergo.WithOverride, mergo.WithAppendSlice) 104 GetLogger().WithField("template", ctemplate).Debug("merged") 105 } else { 106 return template, "", false, tError 107 } 108 } else { 109 return template, "", false, pathError 110 } 111 } 112 } else { 113 GetLogger().Debug("no required files configured") 114 } 115 116 return ctemplate, foundPath, true, nil 117 } 118 119 return template, "", false, err 120 } 121 122 func getIncludeConfigPath(path string) (string, string, bool) { 123 fullPath := filepath.Dir(path) 124 checkIncPath := fullPath + string(os.PathSeparator) + ".inc.contxt.yml" 125 existing, fileerror := dirhandle.Exists(checkIncPath) 126 if fileerror != nil || !existing { 127 return checkIncPath, fullPath, false 128 } 129 GetLogger().WithField("include-config", checkIncPath).Debug("found include setting") 130 return checkIncPath, fullPath, true 131 } 132 133 func getIncludeConfig(path string) (configure.IncludePaths, string, string, bool) { 134 var importTemplate configure.IncludePaths 135 checkIncPath, fullPath, existing := getIncludeConfigPath(path) 136 if !existing { 137 return importTemplate, checkIncPath, fullPath, false 138 } 139 140 file, ferr := os.ReadFile(checkIncPath) 141 if ferr != nil { 142 return importTemplate, checkIncPath, fullPath, false 143 } 144 err := yaml.Unmarshal(file, &importTemplate) 145 if err != nil { 146 fmt.Println(manout.MessageCln(manout.ForeRed, "error reading include config file: ", manout.ForeWhite, checkIncPath), err) 147 systools.Exit(incFileParseError) 148 } 149 return importTemplate, checkIncPath, fullPath, true 150 } 151 152 // LoadIncTempalte check if .inc.contxt.yml files exists 153 // and if this is the case the content will be loaded and all defined paths 154 // used to get values for parsing the template file 155 func LoadIncTempalte(path string) (string, bool) { 156 importTemplate, _, fullPath, existing := getIncludeConfig(path) 157 if !existing { 158 return "", false 159 } 160 // imports by Include.Folders 161 if len(importTemplate.Include.Folders) > 0 || importTemplate.Include.Basedir { 162 GetLogger().WithField("file", path).Info("parsing task-file") 163 var dirs []string = importTemplate.Include.Folders 164 if importTemplate.Include.Basedir { 165 GetLogger().WithField("dir", fullPath).Debug("add parsing source dir") 166 dirs = append(dirs, fullPath) 167 } 168 169 parsedTemplate, perr := ImportFolders(path, dirs...) 170 if perr != nil { 171 fmt.Println(perr) 172 fmt.Println(manout.MessageCln(manout.ForeRed, "error parsing files from path: ", manout.ForeWhite, path), perr) 173 systools.Exit(incFileParseError) 174 } 175 return parsedTemplate, true 176 } 177 return "", false 178 } 179 180 // GetParsedTemplateSource Returns the soucecode of the template 181 // including parsing placeholders 182 func GetParsedTemplateSource(path string) (string, error) { 183 existing, fileerror := dirhandle.Exists(path) 184 if fileerror != nil { 185 return "", fileerror 186 } 187 if existing { 188 // first check if includes exists 189 templateSource, inExists := LoadIncTempalte(path) 190 if inExists { 191 return templateSource, nil 192 } 193 // no imports .... load template file 194 file, ferr := os.ReadFile(path) 195 if ferr != nil { 196 return "", ferr 197 } 198 return string(file), nil 199 } 200 notExistsErr := errors.New("file not exists") 201 return "", notExistsErr 202 } 203 204 // GetPwdTemplate returns the template path if exists. 205 // it also parses the content of the template 206 // against imports and handles them 207 func GetPwdTemplate(path string) (configure.RunConfig, error) { 208 var template configure.RunConfig 209 source, err := GetParsedTemplateSource(path) 210 if err != nil { 211 return template, err 212 } 213 214 err2 := yaml.Unmarshal([]byte(source), &template) 215 216 if err2 != nil { 217 printErrSource(err2, source) 218 return template, err2 219 } 220 return template, nil 221 } 222 223 func getLineNr(str string) (int, error) { 224 re := regexp.MustCompile("[0-9]+") 225 found := re.FindAllString(str, -1) 226 if len(found) > 0 { 227 return strconv.Atoi(found[0]) 228 } 229 return -1, errors.New("no line number found in message " + str) 230 } 231 232 func printErrSource(err error, source string) { 233 errPlain := err.Error() 234 errParts := strings.Split(errPlain, ":") 235 236 if len(errParts) == 3 { // this is depending an regular error message from yaml. like: yaml: line 3: mapping values are not allowed in this context 237 if lineNr, lErr := getLineNr(errParts[1]); lErr == nil { 238 sourceParts := strings.Split(source, "\n") 239 if len(sourceParts) >= lineNr && lineNr >= 0 { 240 min := lineNr - 3 241 max := lineNr + 3 242 if min < 0 { 243 min = 0 244 } 245 if max > len(sourceParts) { 246 max = len(sourceParts) 247 } 248 for i := min; i < max; i++ { 249 nrback := manout.BackWhite 250 nrFore := manout.ForeBlue 251 msgFore := manout.ForeCyan 252 msgBack := "" 253 msg := "" 254 if i == lineNr { 255 nrback = manout.BackLightRed 256 nrFore = manout.ForeRed 257 msgFore = manout.ForeWhite 258 msgBack = manout.BackRed 259 msg = errParts[2] 260 } 261 262 padLineNr := fmt.Sprintf("%4d |", i) 263 outstr := manout.MessageCln( 264 nrback, 265 nrFore, 266 " ", 267 padLineNr, 268 manout.CleanTag, 269 msgFore, 270 msgBack, 271 sourceParts[i], 272 manout.CleanTag, 273 manout.ForeLightYellow, 274 " ", 275 msg) 276 277 fmt.Println(outstr) 278 } 279 } else { 280 fmt.Println("source parsing faliure", sourceParts) 281 } 282 } else { 283 fmt.Println(lErr) 284 } 285 } else { 286 fmt.Println("unexpected message format ", len(errParts), " ", errParts) 287 } 288 }