github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/subshell/cmd/cmd.go (about) 1 package cmd 2 3 import ( 4 "os" 5 "os/exec" 6 "path/filepath" 7 "runtime" 8 9 "github.com/ActiveState/cli/internal/errs" 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/output" 14 "github.com/ActiveState/cli/internal/subshell/sscommon" 15 "github.com/ActiveState/cli/pkg/project" 16 ) 17 18 var escaper *osutils.ShellEscape 19 20 func init() { 21 escaper = osutils.NewBatchEscaper() 22 } 23 24 // SubShell covers the subshell.SubShell interface, reference that for documentation 25 type SubShell struct { 26 binary string 27 rcFile *os.File 28 cmd *exec.Cmd 29 env map[string]string 30 errs chan error 31 } 32 33 const Name string = "cmd" 34 35 // Shell - see subshell.SubShell 36 func (v *SubShell) Shell() string { 37 return Name 38 } 39 40 // Binary - see subshell.SubShell 41 func (v *SubShell) Binary() string { 42 return v.binary 43 } 44 45 // SetBinary - see subshell.SubShell 46 func (v *SubShell) SetBinary(binary string) { 47 v.binary = binary 48 } 49 50 // WriteUserEnv - see subshell.SubShell 51 func (v *SubShell) WriteUserEnv(cfg sscommon.Configurable, env map[string]string, envType sscommon.RcIdentification, userScope bool) error { 52 cmdEnv := NewCmdEnv(userScope) 53 54 // Clean up old entries 55 oldEnv := cfg.GetStringMap(envType.Key) 56 for k, v := range oldEnv { 57 if err := cmdEnv.unset(k, v.(string)); err != nil { 58 return err 59 } 60 } 61 62 // Store new entries 63 err := cfg.Set(envType.Key, env) 64 if err != nil { 65 return errs.Wrap(err, "Could not set env infomation in config") 66 } 67 68 for k, v := range env { 69 value := v 70 if k == "PATH" { 71 path, err := cmdEnv.Get("PATH") 72 if err != nil { 73 return err 74 } 75 if path != "" { 76 path = ";" + path 77 } 78 79 value = v + path 80 } 81 82 // Set key/value in the user environment 83 err := cmdEnv.Set(k, value) 84 if err != nil { 85 return err 86 } 87 } 88 89 if err := osutils.PropagateEnv(); err != nil { 90 return errs.Wrap(err, "Sending OS signal to update environment failed.") 91 } 92 return nil 93 } 94 95 func (v *SubShell) CleanUserEnv(cfg sscommon.Configurable, envType sscommon.RcIdentification, userScope bool) error { 96 cmdEnv := NewCmdEnv(userScope) 97 98 // Clean up old entries 99 oldEnv := cfg.GetStringMap(envType.Key) 100 for k, v := range oldEnv { 101 val, ok := v.(string) 102 if !ok { 103 logging.Debug("Invalid non-string value in environment mapping") 104 continue 105 } 106 if err := cmdEnv.unset(k, val); err != nil { 107 return err 108 } 109 } 110 111 if err := osutils.PropagateEnv(); err != nil { 112 return errs.Wrap(err, "Sending OS signal to update environment failed.") 113 } 114 return nil 115 } 116 117 func (v *SubShell) RemoveLegacyInstallPath(_ sscommon.Configurable) error { 118 return nil 119 } 120 121 func (v *SubShell) WriteCompletionScript(completionScript string) error { 122 return locale.NewError("err_writecompletions_notsupported", "{{.V0}} does not support completions.", v.Shell()) 123 } 124 125 func (v *SubShell) RcFile() (string, error) { 126 return "", locale.NewError("err_cmd_rcile", "cmd does not support RC files") 127 } 128 129 func (v *SubShell) EnsureRcFileExists() error { 130 // Windows does not use RC files 131 return nil 132 } 133 134 // SetupShellRcFile - subshell.SubShell 135 func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { 136 env = sscommon.EscapeEnv(env) 137 return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.bat"), "config_global.bat", env, namespace, cfg) 138 } 139 140 // SetEnv - see subshell.SetEnv 141 func (v *SubShell) SetEnv(env map[string]string) error { 142 v.env = env 143 return nil 144 } 145 146 // Quote - see subshell.Quote 147 func (v *SubShell) Quote(value string) string { 148 return escaper.Quote(value) 149 } 150 151 // Activate - see subshell.SubShell 152 func (v *SubShell) Activate(prj *project.Project, cfg sscommon.Configurable, out output.Outputer) error { 153 var shellArgs []string 154 var directEnv []string 155 156 if prj != nil { 157 env := sscommon.EscapeEnv(v.env) 158 var err error 159 if v.rcFile, err = sscommon.SetupProjectRcFile(prj, "config.bat", ".bat", env, out, cfg, false); err != nil { 160 return err 161 } 162 163 shellArgs = append(shellArgs, "/K", v.rcFile.Name()) 164 } else { 165 directEnv = sscommon.EnvSlice(v.env) 166 } 167 168 cmd := sscommon.NewCommand("cmd", shellArgs, directEnv) 169 v.errs = sscommon.Start(cmd) 170 v.cmd = cmd 171 return nil 172 } 173 174 // Errors returns a channel for receiving errors related to active behavior 175 func (v *SubShell) Errors() <-chan error { 176 return v.errs 177 } 178 179 // Deactivate - see subshell.SubShell 180 func (v *SubShell) Deactivate() error { 181 if !v.IsActive() { 182 return nil 183 } 184 185 if err := sscommon.Stop(v.cmd); err != nil { 186 return err 187 } 188 189 v.cmd = nil 190 return nil 191 } 192 193 // Run - see subshell.SubShell 194 func (v *SubShell) Run(filename string, args ...string) error { 195 return sscommon.RunFuncByBinary(v.Binary())(osutils.EnvMapToSlice(v.env), filename, args...) 196 } 197 198 // IsActive - see subshell.SubShell 199 func (v *SubShell) IsActive() bool { 200 return v.cmd != nil && (v.cmd.ProcessState == nil || !v.cmd.ProcessState.Exited()) 201 } 202 203 func (v *SubShell) IsAvailable() bool { 204 return runtime.GOOS == "windows" 205 }