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  }