github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/pkg/model/cmd.go (about)

     1  package model
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"strings"
     7  )
     8  
     9  type Cmd struct {
    10  	Argv    []string
    11  	Dir     string
    12  	Env     []string
    13  	EchoOff bool
    14  }
    15  
    16  func (c Cmd) IsShellStandardForm() bool {
    17  	return len(c.Argv) == 3 && c.Argv[0] == "sh" && c.Argv[1] == "-c" && !strings.Contains(c.Argv[2], "\n")
    18  }
    19  
    20  func (c Cmd) IsWindowsStandardForm() bool {
    21  	return len(c.Argv) == 4 && c.Argv[0] == "cmd" && c.Argv[1] == "/S" && c.Argv[2] == "/C"
    22  }
    23  
    24  // Get the script when the shell is in standard form.
    25  // Panics if the command is not in shell standard form.
    26  func (c Cmd) ShellStandardScript() string {
    27  	if !c.IsShellStandardForm() {
    28  		panic(fmt.Sprintf("Not in shell standard form: %+v", c))
    29  	}
    30  	return c.Argv[2]
    31  }
    32  
    33  func (c Cmd) EntrypointStr() string {
    34  	if c.IsShellStandardForm() {
    35  		return fmt.Sprintf("ENTRYPOINT %s", c.Argv[2])
    36  	}
    37  
    38  	quoted := make([]string, len(c.Argv))
    39  	for i, arg := range c.Argv {
    40  		quoted[i] = fmt.Sprintf("%q", arg)
    41  	}
    42  	return fmt.Sprintf("ENTRYPOINT [%s]", strings.Join(quoted, ", "))
    43  }
    44  
    45  func (c Cmd) RunStr() string {
    46  	if c.IsShellStandardForm() {
    47  		return fmt.Sprintf("RUN %s", c.Argv[2])
    48  	}
    49  
    50  	quoted := make([]string, len(c.Argv))
    51  	for i, arg := range c.Argv {
    52  		quoted[i] = fmt.Sprintf("%q", arg)
    53  	}
    54  	return fmt.Sprintf("RUN [%s]", strings.Join(quoted, ", "))
    55  }
    56  
    57  func ArgListToString(args []string) string {
    58  	return Cmd{Argv: args}.String()
    59  }
    60  
    61  func (c Cmd) String() string {
    62  	if c.IsShellStandardForm() {
    63  		return c.Argv[2]
    64  	}
    65  
    66  	if c.IsWindowsStandardForm() {
    67  		return c.Argv[3]
    68  	}
    69  
    70  	quoted := make([]string, len(c.Argv))
    71  	for i, arg := range c.Argv {
    72  		if strings.Contains(arg, " ") {
    73  			quoted[i] = fmt.Sprintf("%q", arg)
    74  		} else {
    75  			quoted[i] = arg
    76  		}
    77  	}
    78  	return strings.Join(quoted, " ")
    79  }
    80  
    81  func (c Cmd) Empty() bool {
    82  	return len(c.Argv) == 0
    83  }
    84  
    85  // Create a shell command for running on the Host OS
    86  func ToHostCmd(cmd string) Cmd {
    87  	if cmd == "" {
    88  		return Cmd{}
    89  	}
    90  	if runtime.GOOS == "windows" {
    91  		return ToBatCmd(cmd)
    92  	}
    93  	return ToUnixCmd(cmd)
    94  }
    95  
    96  func ToHostCmdInDir(cmd string, dir string) Cmd {
    97  	c := ToHostCmd(cmd)
    98  	c.Dir = dir
    99  	return c
   100  }
   101  
   102  func ToHostCmdInDirWithEnv(cmd string, dir string, env []string) Cmd {
   103  	c := ToHostCmdInDir(cmd, dir)
   104  	c.Env = env
   105  	return c
   106  }
   107  
   108  // 🦇🦇🦇
   109  // Named in honor of Bazel
   110  // https://docs.bazel.build/versions/master/be/general.html#genrule.cmd_bat
   111  func ToBatCmd(cmd string) Cmd {
   112  	if cmd == "" {
   113  		return Cmd{}
   114  	}
   115  	// from https://docs.docker.com/engine/reference/builder/#run
   116  	//
   117  	// NOTE(nick): cmd /S /C does not handle multi-line strings correctly.
   118  	// It will execute the first line, then exit. Should we warn or error about this?
   119  	//
   120  	// The TrimSpace ensures we at least execute the first non-empty line.
   121  	return Cmd{Argv: []string{"cmd", "/S", "/C", strings.TrimSpace(cmd)}}
   122  }
   123  
   124  func ToUnixCmd(cmd string) Cmd {
   125  	if cmd == "" {
   126  		return Cmd{}
   127  	}
   128  
   129  	// trim spurious spaces and execute them in shell.
   130  	return Cmd{Argv: []string{"sh", "-c", strings.TrimSpace(cmd)}}
   131  }
   132  
   133  func ToUnixCmdInDir(cmd string, dir string) Cmd {
   134  	c := ToUnixCmd(cmd)
   135  	c.Dir = dir
   136  	return c
   137  }
   138  
   139  func ToUnixCmds(cmds []string) []Cmd {
   140  	res := make([]Cmd, len(cmds))
   141  	for i, cmd := range cmds {
   142  		res[i] = ToUnixCmd(cmd)
   143  	}
   144  	return res
   145  }
   146  
   147  func ToRun(cmd Cmd) Run {
   148  	return Run{Cmd: cmd}
   149  }
   150  
   151  func ToRuns(cmds []Cmd) []Run {
   152  	res := make([]Run, len(cmds))
   153  	for i, cmd := range cmds {
   154  		res[i] = ToRun(cmd)
   155  	}
   156  	return res
   157  }