github.com/swaros/contxt/module/taskrun@v0.0.0-20240305083542-3dbd4436ac40/shellinstall.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 "bytes" 26 "fmt" 27 28 "os" 29 "os/user" 30 "strings" 31 32 "github.com/spf13/cobra" 33 34 "github.com/swaros/contxt/module/dirhandle" 35 "github.com/swaros/contxt/module/systools" 36 "github.com/swaros/manout" 37 ) 38 39 // here we have all the functions to install the shell completion and 40 // function files for the shell 41 42 var pwrShellPathCache string = "" // cache the path to the powershell profile 43 44 func UserDirectory() (string, error) { 45 usr, err := user.Current() 46 if err != nil { 47 log.Fatal(err) 48 } 49 return usr.HomeDir, err 50 } 51 52 func updateExistingFile(filename, content, doNotContain string) (bool, string) { 53 ok, errDh := dirhandle.Exists(filename) 54 errmsg := "" 55 if errDh == nil && ok { 56 byteCnt, err := os.ReadFile(filename) 57 if err != nil { 58 return false, "file not readable " + filename 59 } 60 strContent := string(byteCnt) 61 if strings.Contains(strContent, doNotContain) { 62 return false, "it seems file is already updated. it contains: " + doNotContain 63 } else { 64 file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) 65 if err != nil { 66 log.Println(err) 67 return false, "error while opening file " + filename 68 } 69 defer file.Close() 70 if _, err := file.WriteString(content); err != nil { 71 log.Fatal(err) 72 return false, "error adding content to file " + filename 73 } 74 return true, "" 75 } 76 77 } else { 78 errmsg = "file update error: file not exists " + filename 79 } 80 return false, errmsg 81 } 82 83 // FishFunctionUpdate updates the fish function file 84 // and adds code completion for the fish shell 85 func FishUpdate(cmd *cobra.Command) { 86 FishFunctionUpdate() 87 FishCompletionUpdate(cmd) 88 } 89 90 // FishCompletionUpdate updates the fish completion file 91 func FishCompletionUpdate(cmd *cobra.Command) { 92 usrDir, err := UserDirectory() 93 if err == nil && usrDir != "" { 94 // completion dir Exists ? 95 exists, err := dirhandle.Exists(usrDir + "/.config/fish/completions") 96 if err == nil && !exists { 97 mkErr := os.Mkdir(usrDir+"/.config/fish/completions/", os.ModePerm) 98 if mkErr != nil { 99 log.Fatal(mkErr) 100 } 101 } 102 } 103 cmpltn := new(bytes.Buffer) 104 cmd.Root().GenFishCompletion(cmpltn, true) 105 106 origin := cmpltn.String() 107 ctxCmpltn := strings.ReplaceAll(origin, "contxt", "ctx") 108 systools.WriteFileIfNotExists(usrDir+"/.config/fish/completions/contxt.fish", origin) 109 systools.WriteFileIfNotExists(usrDir+"/.config/fish/completions/ctx.fish", ctxCmpltn) 110 111 } 112 113 func FishFunctionUpdate() { 114 115 fishFunc := `function ctx 116 contxt $argv 117 switch $argv[1] 118 case switch 119 cd (contxt dir --last) 120 contxt dir paths --coloroff --nohints 121 end 122 end` 123 cnFunc := `function cn 124 cd (contxt dir find $argv) 125 end` 126 127 usrDir, err := UserDirectory() 128 if err == nil && usrDir != "" { 129 // functions dir Exists ? 130 exists, err := dirhandle.Exists(usrDir + "/.config/fish/functions") 131 if err == nil && !exists { 132 mkErr := os.Mkdir(usrDir+"/.config/fish/functions", os.ModePerm) 133 if mkErr != nil { 134 log.Fatal(mkErr) 135 } 136 } 137 138 funcExists, funcErr := dirhandle.Exists(usrDir + "/.config/fish/functions/ctx.fish") 139 if funcErr == nil && !funcExists { 140 os.WriteFile(usrDir+"/.config/fish/functions/ctx.fish", []byte(fishFunc), 0644) 141 } else if funcExists { 142 fmt.Println("ctx function already exists. did not change that") 143 } 144 145 funcExists, funcErr = dirhandle.Exists(usrDir + "/.config/fish/functions/cn.fish") 146 if funcErr == nil && !funcExists { 147 os.WriteFile(usrDir+"/.config/fish/functions/cn.fish", []byte(cnFunc), 0644) 148 } else if funcExists { 149 fmt.Println("cn function already exists. did not change that") 150 } 151 } 152 } 153 154 func BashUser() { 155 bashrcAdd := ` 156 ### begin contxt bashrc 157 function cn() { cd $(contxt dir find "$@"); } 158 function ctx() { 159 contxt "$@"; 160 [ $? -eq 0 ] || return 1 161 case $1 in 162 switch) 163 cd $(contxt dir --last); 164 contxt dir paths --coloroff --nohints 165 ;; 166 esac 167 } 168 function ctxcompletion() { 169 ORIG=$(contxt completion bash) 170 CM="contxt" 171 CT="ctx" 172 CTXC="${ORIG//$CM/$CT}" 173 echo "$CTXC" 174 } 175 source <(contxt completion bash) 176 source <(ctxcompletion) 177 ### end of contxt bashrc 178 ` 179 usrDir, err := UserDirectory() 180 if err == nil && usrDir != "" { 181 ok, errDh := dirhandle.Exists(usrDir + "/.bashrc") 182 if errDh == nil && ok { 183 fmt.Println(usrDir + "/.bashrc") 184 fine, errmsg := updateExistingFile(usrDir+"/.bashrc", bashrcAdd, "### begin contxt bashrc") 185 if !fine { 186 manout.Error("bashrc update failed", errmsg) 187 } else { 188 fmt.Println(manout.MessageCln(manout.ForeGreen, "success", manout.CleanTag, " to update bash run ", manout.ForeCyan, " source ~/.bashrc")) 189 } 190 } else { 191 manout.Error("missing .bashrc", "could not find expected "+usrDir+"/.bashrc") 192 } 193 } 194 195 } 196 197 func ZshUpdate(cmd *cobra.Command) { 198 ZshUser() 199 updateZshFunctions(cmd) 200 } 201 202 // try to get the best path by reading the permission 203 // because zsh seems not be used in windows, we stick to linux related 204 // permission check 205 func ZshFuncDir() string { 206 fpath := os.Getenv("FPATH") 207 if fpath != "" { 208 paths := strings.Split(fpath, ":") 209 for _, path := range paths { 210 fileStats, err := os.Stat(path) 211 if err != nil { 212 continue 213 } 214 permissions := fileStats.Mode().Perm() 215 if permissions&0b110000000 == 0b110000000 { 216 return path 217 } 218 } 219 return "" 220 } 221 return fpath 222 } 223 224 func updateZshFunctions(cmd *cobra.Command) { 225 funcDir := ZshFuncDir() 226 if funcDir != "" { 227 contxtPath := funcDir + "/_contxt" 228 ctxPath := funcDir + "/_ctx" 229 fmt.Println(funcDir) 230 231 cmpltn := new(bytes.Buffer) 232 cmd.Root().GenZshCompletion(cmpltn) 233 234 origin := cmpltn.String() 235 ctxCmpltn := strings.ReplaceAll(origin, "contxt", "ctx") 236 237 systools.WriteFileIfNotExists(contxtPath, origin) 238 systools.WriteFileIfNotExists(ctxPath, ctxCmpltn) 239 } else { 240 manout.Error("could not find a writable path for zsh functions in fpath") 241 } 242 } 243 244 func ZshUser() { 245 zshrcAdd := ` 246 ### begin contxt zshrc 247 function cn() { cd $(contxt dir find "$@"); } 248 function ctx() { 249 contxt "$@"; 250 [ $? -eq 0 ] || return $? 251 case $1 in 252 switch) 253 cd $(contxt dir --last); 254 contxt dir paths --coloroff --nohints 255 ;; 256 esac 257 } 258 ### end of contxt zshrc 259 ` 260 usrDir, err := UserDirectory() 261 if err == nil && usrDir != "" { 262 ok, errDh := dirhandle.Exists(usrDir + "/.zshrc") 263 if errDh == nil && ok { 264 fmt.Println(usrDir + "/.zshrc") 265 fine, errmsg := updateExistingFile(usrDir+"/.zshrc", zshrcAdd, "### begin contxt zshrc") 266 if !fine { 267 manout.Error("zshrc update failed", errmsg) 268 } else { 269 fmt.Println(manout.MessageCln(manout.ForeGreen, "success", manout.CleanTag, " ", manout.ForeCyan, " ")) 270 } 271 } else { 272 manout.Error("missing .zshrc", "could not find expected "+usrDir+"/.zshrc") 273 } 274 } 275 276 } 277 278 func PwrShellUpdate(cmd *cobra.Command) { 279 forceProfile, _ := cmd.Flags().GetBool("create-profile") 280 if forceProfile { 281 PwrShellForceCreateProfile() 282 } 283 PwrShellUser() 284 PwrShellCompletionUpdate(cmd) 285 } 286 287 func PwrShellUser() { 288 pwrshrcAdd := ` 289 ### begin contxt pwrshrc 290 function cn($path) { 291 Set-Location $(contxt dir find $path) 292 } 293 function ctx { 294 & contxt $args 295 } 296 ### end of contxt pwrshrc 297 ` 298 if found, pwrshProfile := FindPwrShellProfile(); found { 299 fine, errmsg := updateExistingFile(pwrshProfile, pwrshrcAdd, "### begin contxt pwrshrc") 300 if !fine { 301 manout.Error("pwrshrc update failed", errmsg) 302 } else { 303 fmt.Println(manout.MessageCln(manout.ForeGreen, "success", manout.CleanTag, " ", manout.ForeCyan, " ")) 304 } 305 } else { 306 manout.Error("missing pwrshrc", "could not find expected powershell profile") 307 } 308 } 309 310 func FindPwrShellProfile() (bool, string) { 311 if pwrShellPathCache != "" { 312 return true, pwrShellPathCache 313 } 314 pwrshProfile := os.Getenv("PROFILE") 315 // retry by using powershell as host 316 if pwrshProfile == "" { 317 pwrshProfile = PwrShellExec(PWRSHELL_CMD_PROFILE) 318 } 319 if pwrshProfile != "" { 320 fileStats, err := os.Stat(pwrshProfile) 321 if err == nil { 322 323 permissions := fileStats.Mode().Perm() 324 if permissions&0b110000000 == 0b110000000 { 325 pwrShellPathCache = pwrshProfile 326 return true, pwrshProfile 327 } 328 } 329 } 330 return false, pwrshProfile 331 } 332 333 func PwrShellForceCreateProfile() { 334 if !PwrShellTestProfile() { 335 PwrShellExec(PWRSHELL_CMD_PROFILE_CREATE) 336 } 337 } 338 339 func PwrShellCompletionUpdate(cmd *cobra.Command) { 340 if !PwrShellTestProfile() { 341 manout.Error("missing powershell profile", "could not find expected powershell profile") 342 manout.Om.Println("you can create a profile by running 'New-Item -Type File -Path $PROFILE -Force'") 343 return 344 } 345 ok, profile := FindPwrShellProfile() 346 if ok { 347 cmpltn := new(bytes.Buffer) 348 cmd.Root().GenPowerShellCompletion(cmpltn) 349 origin := cmpltn.String() 350 if ctxBasePath, err := GetContxtBasePath(); err == nil { 351 ctxCmpltn := strings.ReplaceAll(origin, "contxt", "ctx") 352 353 ctxPowerShellPath := ctxBasePath + "/powershell" 354 if exists, err := systools.Exists(ctxPowerShellPath); err != nil || !exists { 355 if err := os.MkdirAll(ctxPowerShellPath, 0755); err != nil { 356 manout.Error("error", "could not create the contxt base path") 357 return 358 } 359 } 360 systools.WriteFileIfNotExists(ctxPowerShellPath+"/contxt.ps1", origin) 361 systools.WriteFileIfNotExists(ctxPowerShellPath+"/ctx.ps1", ctxCmpltn) 362 363 profileAdd := ` 364 ### begin contxt powershell profile 365 . "` + ctxPowerShellPath + `/contxt.ps1" 366 . "` + ctxPowerShellPath + `/ctx.ps1" 367 ### end of contxt powershell profile 368 ` 369 370 fine, errmsg := updateExistingFile(profile, profileAdd, "### begin contxt powershell profile") 371 if !fine { 372 manout.Error("powershell profile update failed", errmsg) 373 } else { 374 manout.MessageCln(manout.ForeGreen, "success", manout.CleanTag, " ", manout.ForeCyan, " ") 375 } 376 377 } else { 378 manout.Error("error", "could not find the contxt base path") 379 } 380 } else { 381 manout.Error("could not find a writable path for powershell completion") 382 } 383 } 384 385 func PwrShellTestProfile() bool { 386 foundBool := PwrShellExec(PWRSHELL_CMD_TEST_PROFILE) 387 return strings.ToLower(foundBool) == "true" 388 }