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  }