github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/subshell/tcsh/tcsh.go (about) 1 package tcsh 2 3 import ( 4 "os" 5 "os/exec" 6 "path/filepath" 7 8 "github.com/ActiveState/cli/internal/errs" 9 "github.com/ActiveState/cli/internal/fileutils" 10 "github.com/ActiveState/cli/internal/locale" 11 "github.com/ActiveState/cli/internal/logging" 12 "github.com/ActiveState/cli/internal/osutils" 13 "github.com/ActiveState/cli/internal/osutils/user" 14 "github.com/ActiveState/cli/internal/output" 15 "github.com/ActiveState/cli/internal/subshell/sscommon" 16 "github.com/ActiveState/cli/pkg/project" 17 ) 18 19 var escaper *osutils.ShellEscape 20 21 func init() { 22 escaper = osutils.NewBashEscaper() 23 } 24 25 // SubShell covers the subshell.SubShell interface, reference that for documentation 26 type SubShell struct { 27 binary string 28 rcFile *os.File 29 cmd *exec.Cmd 30 env map[string]string 31 errs chan error 32 } 33 34 const Name string = "tcsh" 35 36 // Shell - see subshell.SubShell 37 func (v *SubShell) Shell() string { 38 return Name 39 } 40 41 // Binary - see subshell.SubShell 42 func (v *SubShell) Binary() string { 43 return v.binary 44 } 45 46 // SetBinary - see subshell.SubShell 47 func (v *SubShell) SetBinary(binary string) { 48 v.binary = binary 49 } 50 51 // WriteUserEnv - see subshell.SubShell 52 func (v *SubShell) WriteUserEnv(cfg sscommon.Configurable, env map[string]string, envType sscommon.RcIdentification, _ bool) error { 53 rcFile, err := v.RcFile() 54 if err != nil { 55 return errs.Wrap(err, "RcFile failure") 56 } 57 58 env = sscommon.EscapeEnv(env) 59 return sscommon.WriteRcFile("tcshrc_append.sh", rcFile, envType, env) 60 } 61 62 func (v *SubShell) CleanUserEnv(cfg sscommon.Configurable, envType sscommon.RcIdentification, _ bool) error { 63 rcFile, err := v.RcFile() 64 if err != nil { 65 return errs.Wrap(err, "RcFile failure") 66 } 67 68 if err := sscommon.CleanRcFile(rcFile, envType); err != nil { 69 return errs.Wrap(err, "Failed to remove %s from rcFile", envType) 70 } 71 72 return nil 73 } 74 75 func (v *SubShell) RemoveLegacyInstallPath(cfg sscommon.Configurable) error { 76 rcFile, err := v.RcFile() 77 if err != nil { 78 return errs.Wrap(err, "RcFile-failure") 79 } 80 81 return sscommon.RemoveLegacyInstallPath(rcFile) 82 } 83 84 func (v *SubShell) WriteCompletionScript(completionScript string) error { 85 return locale.NewError("err_writecompletions_notsupported", "{{.V0}} does not support completions.", v.Shell()) 86 } 87 88 func (v *SubShell) RcFile() (string, error) { 89 homeDir, err := user.HomeDir() 90 if err != nil { 91 return "", errs.Wrap(err, "IO failure") 92 } 93 94 return filepath.Join(homeDir, ".tcshrc"), nil 95 } 96 97 func (v *SubShell) EnsureRcFileExists() error { 98 rcFile, err := v.RcFile() 99 if err != nil { 100 return errs.Wrap(err, "Could not determine rc file") 101 } 102 103 return fileutils.TouchFileUnlessExists(rcFile) 104 } 105 106 // SetupShellRcFile - subshell.SubShell 107 func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { 108 env = sscommon.EscapeEnv(env) 109 return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.tcsh"), "tcsh_global.sh", env, namespace, cfg) 110 } 111 112 // SetEnv - see subshell.SetEnv 113 func (v *SubShell) SetEnv(env map[string]string) error { 114 v.env = env 115 return nil 116 } 117 118 // Quote - see subshell.Quote 119 func (v *SubShell) Quote(value string) string { 120 return escaper.Quote(value) 121 } 122 123 // Activate - see subshell.SubShell 124 func (v *SubShell) Activate(proj *project.Project, cfg sscommon.Configurable, out output.Outputer) error { 125 // This is horrible but it works. tcsh doesn't offer a way to override the rc file and 126 // doesn't let us run a script and then drop to interactive mode. So we source the 127 // state rc file and then chain an exec which inherits the environment we just set up. 128 // It seems to work fine except we don't have a way to override the shell prompt. 129 // 130 // The exec'd shell does not inherit 'prompt' from the calling terminal since 131 // tcsh does not export prompt. This may be intractable. I couldn't figure out a 132 // hack to make it work. 133 var shellArgs []string 134 var directEnv []string 135 136 // available project files require more intensive modification of shell envs 137 if proj != nil { 138 env := sscommon.EscapeEnv(v.env) 139 var err error 140 if v.rcFile, err = sscommon.SetupProjectRcFile(proj, "tcsh.sh", "", env, out, cfg, false); err != nil { 141 return err 142 } 143 144 shellArgs = []string{"-c", "source " + v.rcFile.Name() + " ; exec " + v.Binary()} 145 } else { 146 directEnv = sscommon.EnvSlice(v.env) 147 } 148 149 cmd := sscommon.NewCommand(v.Binary(), shellArgs, directEnv) 150 v.errs = sscommon.Start(cmd) 151 v.cmd = cmd 152 return nil 153 } 154 155 // Errors returns a channel for receiving errors related to active behavior 156 func (v *SubShell) Errors() <-chan error { 157 return v.errs 158 } 159 160 // Deactivate - see subshell.SubShell 161 func (v *SubShell) Deactivate() error { 162 if !v.IsActive() { 163 return nil 164 } 165 166 if err := sscommon.Stop(v.cmd); err != nil { 167 return err 168 } 169 170 v.cmd = nil 171 return nil 172 } 173 174 // Run - see subshell.SubShell 175 func (v *SubShell) Run(filename string, args ...string) error { 176 return sscommon.RunFuncByBinary(v.Binary())(osutils.EnvMapToSlice(v.env), filename, args...) 177 } 178 179 // IsActive - see subshell.SubShell 180 func (v *SubShell) IsActive() bool { 181 return v.cmd != nil && (v.cmd.ProcessState == nil || !v.cmd.ProcessState.Exited()) 182 } 183 184 func (v *SubShell) IsAvailable() bool { 185 rcFile, err := v.RcFile() 186 if err != nil { 187 logging.Error("Could not determine rcFile: %s", err) 188 return false 189 } 190 return fileutils.FileExists(rcFile) 191 }