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 }