mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
Merge pull request #387 from gusandrioli/wrap-custom-cmds-on-shell
This commit is contained in:
commit
6025912336
6 changed files with 127 additions and 42 deletions
|
|
@ -22,13 +22,11 @@ import (
|
||||||
|
|
||||||
// Platform stores the os state
|
// Platform stores the os state
|
||||||
type Platform struct {
|
type Platform struct {
|
||||||
os string
|
os string
|
||||||
shell string
|
shell string
|
||||||
shellArg string
|
shellArg string
|
||||||
escapedQuote string
|
openCommand string
|
||||||
openCommand string
|
openLinkCommand string
|
||||||
openLinkCommand string
|
|
||||||
fallbackEscapedQuote string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSCommand holds all the os commands
|
// OSCommand holds all the os commands
|
||||||
|
|
@ -98,6 +96,25 @@ func (c *OSCommand) ExecutableFromStringContext(ctx context.Context, commandStr
|
||||||
return exec.CommandContext(ctx, splitCmd[0], splitCmd[1:]...)
|
return exec.CommandContext(ctx, splitCmd[0], splitCmd[1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OSCommand) NewCommandStringWithShell(commandStr string) string {
|
||||||
|
quotedCommand := ""
|
||||||
|
// Windows does not seem to like quotes around the command
|
||||||
|
if c.Platform.os == "windows" {
|
||||||
|
quotedCommand = strings.NewReplacer(
|
||||||
|
"^", "^^",
|
||||||
|
"&", "^&",
|
||||||
|
"|", "^|",
|
||||||
|
"<", "^<",
|
||||||
|
">", "^>",
|
||||||
|
"%", "^%",
|
||||||
|
).Replace(commandStr)
|
||||||
|
} else {
|
||||||
|
quotedCommand = c.Quote(commandStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s %s %s", c.Platform.shell, c.Platform.shellArg, quotedCommand)
|
||||||
|
}
|
||||||
|
|
||||||
// RunCommand runs a command and just returns the error
|
// RunCommand runs a command and just returns the error
|
||||||
func (c *OSCommand) RunCommand(command string) error {
|
func (c *OSCommand) RunCommand(command string) error {
|
||||||
_, err := c.RunCommandWithOutput(command)
|
_, err := c.RunCommandWithOutput(command)
|
||||||
|
|
@ -116,16 +133,6 @@ func (c *OSCommand) FileType(path string) string {
|
||||||
return "file"
|
return "file"
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunDirectCommand wrapper around direct commands
|
|
||||||
func (c *OSCommand) RunDirectCommand(command string) (string, error) {
|
|
||||||
c.Log.WithField("command", command).Info("RunDirectCommand")
|
|
||||||
|
|
||||||
return sanitisedCommandOutput(
|
|
||||||
c.command(c.Platform.shell, c.Platform.shellArg, command).
|
|
||||||
CombinedOutput(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitisedCommandOutput(output []byte, err error) (string, error) {
|
func sanitisedCommandOutput(output []byte, err error) (string, error) {
|
||||||
outputString := string(output)
|
outputString := string(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -190,12 +197,23 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *ex
|
||||||
|
|
||||||
// Quote wraps a message in platform-specific quotation marks
|
// Quote wraps a message in platform-specific quotation marks
|
||||||
func (c *OSCommand) Quote(message string) string {
|
func (c *OSCommand) Quote(message string) string {
|
||||||
message = strings.Replace(message, "`", "\\`", -1)
|
var quote string
|
||||||
escapedQuote := c.Platform.escapedQuote
|
if c.Platform.os == "windows" {
|
||||||
if strings.Contains(message, c.Platform.escapedQuote) {
|
quote = `\"`
|
||||||
escapedQuote = c.Platform.fallbackEscapedQuote
|
message = strings.NewReplacer(
|
||||||
|
`"`, `"'"'"`,
|
||||||
|
`\"`, `\\"`,
|
||||||
|
).Replace(message)
|
||||||
|
} else {
|
||||||
|
quote = `"`
|
||||||
|
message = strings.NewReplacer(
|
||||||
|
`\`, `\\`,
|
||||||
|
`"`, `\"`,
|
||||||
|
`$`, `\$`,
|
||||||
|
"`", "\\`",
|
||||||
|
).Replace(message)
|
||||||
}
|
}
|
||||||
return escapedQuote + message + escapedQuote
|
return quote + message + quote
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unquote removes wrapping quotations marks if they are present
|
// Unquote removes wrapping quotations marks if they are present
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,10 @@ import (
|
||||||
|
|
||||||
func getPlatform() *Platform {
|
func getPlatform() *Platform {
|
||||||
return &Platform{
|
return &Platform{
|
||||||
os: runtime.GOOS,
|
os: runtime.GOOS,
|
||||||
shell: "bash",
|
shell: "bash",
|
||||||
shellArg: "-c",
|
shellArg: "-c",
|
||||||
escapedQuote: "'",
|
openCommand: "open {{filename}}",
|
||||||
openCommand: "open {{filename}}",
|
openLinkCommand: "open {{link}}",
|
||||||
openLinkCommand: "open {{link}}",
|
|
||||||
fallbackEscapedQuote: "\"",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
@ -153,13 +154,14 @@ func TestOSCommandEditFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOSCommandQuote is a function.
|
|
||||||
func TestOSCommandQuote(t *testing.T) {
|
func TestOSCommandQuote(t *testing.T) {
|
||||||
osCommand := NewDummyOSCommand()
|
osCommand := NewDummyOSCommand()
|
||||||
|
|
||||||
|
osCommand.Platform.os = "linux"
|
||||||
|
|
||||||
actual := osCommand.Quote("hello `test`")
|
actual := osCommand.Quote("hello `test`")
|
||||||
|
|
||||||
expected := osCommand.Platform.escapedQuote + "hello \\`test\\`" + osCommand.Platform.escapedQuote
|
expected := "\"hello \\`test\\`\""
|
||||||
|
|
||||||
assert.EqualValues(t, expected, actual)
|
assert.EqualValues(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
@ -169,12 +171,10 @@ func TestOSCommandQuoteSingleQuote(t *testing.T) {
|
||||||
osCommand := NewDummyOSCommand()
|
osCommand := NewDummyOSCommand()
|
||||||
|
|
||||||
osCommand.Platform.os = "linux"
|
osCommand.Platform.os = "linux"
|
||||||
osCommand.Platform.fallbackEscapedQuote = "\""
|
|
||||||
osCommand.Platform.escapedQuote = "'"
|
|
||||||
|
|
||||||
actual := osCommand.Quote("hello 'test'")
|
actual := osCommand.Quote("hello 'test'")
|
||||||
|
|
||||||
expected := osCommand.Platform.fallbackEscapedQuote + "hello 'test'" + osCommand.Platform.fallbackEscapedQuote
|
expected := `"hello 'test'"`
|
||||||
|
|
||||||
assert.EqualValues(t, expected, actual)
|
assert.EqualValues(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,20 @@ func TestOSCommandQuoteDoubleQuote(t *testing.T) {
|
||||||
|
|
||||||
actual := osCommand.Quote(`hello "test"`)
|
actual := osCommand.Quote(`hello "test"`)
|
||||||
|
|
||||||
expected := osCommand.Platform.escapedQuote + "hello \"test\"" + osCommand.Platform.escapedQuote
|
expected := `"hello \"test\""`
|
||||||
|
|
||||||
|
assert.EqualValues(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOSCommandQuoteWindows tests the quote function for Windows
|
||||||
|
func TestOSCommandQuoteWindows(t *testing.T) {
|
||||||
|
osCommand := NewDummyOSCommand()
|
||||||
|
|
||||||
|
osCommand.Platform.os = "windows"
|
||||||
|
|
||||||
|
actual := osCommand.Quote(`hello "test" 'test2'`)
|
||||||
|
|
||||||
|
expected := `\"hello "'"'"test"'"'" 'test2'\"`
|
||||||
|
|
||||||
assert.EqualValues(t, expected, actual)
|
assert.EqualValues(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
@ -291,3 +304,53 @@ func TestOSCommandCreateTempFile(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOSCommandExecutableFromStringWithShellLinux(t *testing.T) {
|
||||||
|
osCommand := NewDummyOSCommand()
|
||||||
|
|
||||||
|
osCommand.Platform.os = "linux"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
commandStr string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"success",
|
||||||
|
"pwd",
|
||||||
|
fmt.Sprintf("%v %v %v", osCommand.Platform.shell, osCommand.Platform.shellArg, "\"pwd\""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := osCommand.NewCommandStringWithShell(tt.commandStr)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOSCommandNewCommandStringWithShellWindows(t *testing.T) {
|
||||||
|
osCommand := NewDummyOSCommand()
|
||||||
|
|
||||||
|
osCommand.Platform.os = "windows"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
commandStr string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"success",
|
||||||
|
"pwd",
|
||||||
|
fmt.Sprintf("%v %v %v", osCommand.Platform.shell, osCommand.Platform.shellArg, "pwd"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := osCommand.NewCommandStringWithShell(tt.commandStr)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@ package commands
|
||||||
|
|
||||||
func getPlatform() *Platform {
|
func getPlatform() *Platform {
|
||||||
return &Platform{
|
return &Platform{
|
||||||
os: "windows",
|
os: "windows",
|
||||||
shell: "cmd",
|
shell: "cmd",
|
||||||
shellArg: "/c",
|
shellArg: "/c",
|
||||||
escapedQuote: `\"`,
|
|
||||||
fallbackEscapedQuote: "\\'",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,10 @@ type CustomCommand struct {
|
||||||
// option where the output plays in the main panel.
|
// option where the output plays in the main panel.
|
||||||
Attach bool `yaml:"attach"`
|
Attach bool `yaml:"attach"`
|
||||||
|
|
||||||
|
// Shell indicates whether to invoke the Command on a shell or not.
|
||||||
|
// Example of a bash invoked command: `/bin/bash -c "{Command}".
|
||||||
|
Shell bool `yaml:"shell"`
|
||||||
|
|
||||||
// Command is the command we want to run. We can use the go templates here as
|
// Command is the command we want to run. We can use the go templates here as
|
||||||
// well. One example might be `{{ .DockerCompose }} exec {{ .Service.Name }}
|
// well. One example might be `{{ .DockerCompose }} exec {{ .Service.Name }}
|
||||||
// /bin/sh`
|
// /bin/sh`
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,14 @@ func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, command
|
||||||
return command.InternalFunction()
|
return command.InternalFunction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolvedCommand := command.Command
|
||||||
|
if command.Shell {
|
||||||
|
resolvedCommand = gui.OSCommand.NewCommandStringWithShell(command.Command)
|
||||||
|
}
|
||||||
|
|
||||||
// if we have a command for attaching, we attach and return the subprocess error
|
// if we have a command for attaching, we attach and return the subprocess error
|
||||||
if command.Attach {
|
if command.Attach {
|
||||||
cmd := gui.OSCommand.ExecutableFromString(resolvedCommand)
|
return gui.runSubprocess(gui.OSCommand.ExecutableFromString(resolvedCommand))
|
||||||
return gui.runSubprocess(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.WithWaitingStatus(waitingStatus, func() error {
|
return gui.WithWaitingStatus(waitingStatus, func() error {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue