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  }