github.com/swaros/contxt/module/runner@v0.0.0-20240305083542-3dbd4436ac40/zshhelper.go (about) 1 // MIT License 2 // 3 // Copyright (c) 2020 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved. 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 23 // AINC-NOTE-0815 24 25 package runner 26 27 import ( 28 "fmt" 29 "os" 30 "path/filepath" 31 "strings" 32 33 "github.com/swaros/contxt/module/systools" 34 "github.com/swaros/contxt/module/tasks" 35 ) 36 37 // the zsh helper is used to find the zsh binary and the fpath 38 // for the zsh completion files. 39 type ZshHelper struct { 40 paths []string 41 usabalePaths []string 42 firstUsable string 43 found bool 44 binpath string 45 collected bool 46 } 47 48 // create a new zsh helper 49 func NewZshHelper() *ZshHelper { 50 return &ZshHelper{ 51 paths: []string{}, 52 usabalePaths: []string{}, 53 found: false, 54 } 55 } 56 57 // collect the zsh binary and the fpath 58 // this method is called by the other methods 59 // if not already called. 60 func (z *ZshHelper) collect() error { 61 if z.collected { 62 return nil 63 } 64 65 if _, err := z.GetFpathByEnv(); err != nil { 66 // if we, as expected most of the time, not find the fpath in the env 67 // we try to get it by executing zsh -c "echo $fpath" 68 errInst := z.checkZshInstalled() 69 if errInst != nil { 70 return errInst 71 } 72 if z.binpath == "" { 73 return fmt.Errorf("zsh seems not installed") 74 } 75 _, err := z.getFpathByExec() 76 if err != nil { 77 return err 78 } 79 } 80 z.findUsableFpath() 81 z.found = z.firstUsable != "" 82 z.collected = true 83 return nil 84 } 85 86 // get the zsh binary path or an error 87 func (z *ZshHelper) GetBinPath() (string, error) { 88 if z.found { 89 return z.binpath, nil 90 } 91 err := z.collect() 92 if err != nil { 93 return "", err 94 } 95 return z.binpath, nil 96 } 97 98 // get all the fpaths they are usable (writeable) or an error 99 func (z *ZshHelper) GetFPaths() ([]string, error) { 100 if z.found { 101 return z.usabalePaths, nil 102 } 103 err := z.collect() 104 if err != nil { 105 return nil, err 106 } 107 return z.usabalePaths, nil 108 } 109 110 func (z *ZshHelper) GetFirstExistingPath() (string, error) { 111 112 err := z.collect() 113 if err != nil { 114 return "", err 115 } 116 for _, path := range z.paths { 117 if exists, err := systools.Exists(path); err == nil && exists { 118 return path, nil 119 } 120 } 121 return z.firstUsable, nil 122 } 123 124 // get the first usable fpath or an error 125 func (z *ZshHelper) GetFirstFPath() (string, error) { 126 if z.found { 127 return filepath.Clean(z.firstUsable), nil 128 } 129 err := z.collect() 130 if err != nil { 131 return "", err 132 } 133 if z.firstUsable == "" { 134 if len(z.paths) == 0 { 135 return "", fmt.Errorf("no fpath found") 136 } 137 return "", fmt.Errorf("no usable fpath found in %d possible paths. you may retry with sudo", len(z.paths)) 138 } 139 return filepath.Clean(z.firstUsable), nil 140 } 141 142 // check if zsh is installed 143 func (z *ZshHelper) checkZshInstalled() error { 144 _, _, err := tasks.Execute("bash", []string{"-c"}, "which zsh", func(s string, err error) bool { 145 if err == nil { 146 z.binpath = s 147 } 148 return true 149 }, func(p *os.Process) { 150 151 }) 152 if err != nil { 153 return err 154 } 155 return nil 156 } 157 158 // try to read the fpath from the env. variable FPATH 159 // this is not the way that should work in real, but so we can test it. 160 // also it can be used to force the fpath. 161 func (z *ZshHelper) GetFpathByEnv() (string, error) { 162 fpaths := os.Getenv("FPATH") 163 if fpaths != "" { 164 envPaths := strings.Split(fpaths, ":") 165 for _, path := range envPaths { 166 if systools.IsDirWriteable(path) { 167 z.paths = append(z.paths, path) 168 if z.firstUsable == "" { 169 z.firstUsable = path 170 } 171 } 172 } 173 if len(z.paths) > 0 { 174 return strings.Join(z.paths, " "), nil 175 } else { 176 return "", fmt.Errorf("no usable fpath found in FPATH env") 177 } 178 } 179 return "", fmt.Errorf("no fpath found in env") 180 } 181 182 // get the fpath by executing zsh -c "echo $fpath" 183 func (z *ZshHelper) getFpathByExec() (string, error) { 184 fpaths := "" 185 _, _, errorEx := tasks.Execute("zsh", []string{"-c"}, "echo $fpath", func(s string, err error) bool { 186 if err == nil { 187 fpaths = s 188 } 189 190 return true 191 }, func(p *os.Process) { 192 193 }) 194 z.paths = strings.Split(fpaths, " ") 195 return fpaths, errorEx 196 } 197 198 // find the usable fpath by checking the permissions 199 func (z *ZshHelper) findUsableFpath() { 200 201 for _, path := range z.paths { 202 if systools.IsDirWriteable(path) { 203 z.usabalePaths = append(z.usabalePaths, path) 204 } 205 } 206 }