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  }