From 45b95556333a204d3f05e80591608c00f8a8ba9f Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Mon, 7 Oct 2019 22:10:51 +0200 Subject: [PATCH 1/2] CmdRunner now updates view while running --- modules/cmdrunner/settings.go | 16 ++++-- modules/cmdrunner/widget.go | 91 ++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/modules/cmdrunner/settings.go b/modules/cmdrunner/settings.go index cfd64abf8..44dec40ac 100644 --- a/modules/cmdrunner/settings.go +++ b/modules/cmdrunner/settings.go @@ -7,24 +7,30 @@ import ( ) const ( - defaultFocusable = false + defaultFocusable = true defaultTitle = "CmdRunner" ) +// Settings for the cmdrunner widget type Settings struct { common *cfg.Common - args []string `help:"The arguments to the command, with each item as an element in an array. Example: for curl -I cisco.com, the arguments array would be ['-I', 'cisco.com']."` - cmd string `help:"The terminal command to be run, withouth the arguments. Ie: ping, whoami, curl."` + args []string `help:"The arguments to the command, with each item as an element in an array. Example: for curl -I cisco.com, the arguments array would be ['-I', 'cisco.com']."` + cmd string `help:"The terminal command to be run, withouth the arguments. Ie: ping, whoami, curl."` + tail bool `help:"Automatically scroll to the end of the command output."` + maxLines int `help:"Maximum number of lines kept in the buffer."` } +// NewSettingsFromYAML loads the cmdrunner portion of the WTF config func NewSettingsFromYAML(name string, moduleConfig *config.Config, globalConfig *config.Config) *Settings { settings := Settings{ common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, moduleConfig, globalConfig), - args: utils.ToStrs(moduleConfig.UList("args")), - cmd: moduleConfig.UString("cmd"), + args: utils.ToStrs(moduleConfig.UList("args")), + cmd: moduleConfig.UString("cmd"), + tail: moduleConfig.UBool("tail"), + maxLines: moduleConfig.UInt("maxLines", -1), } return &settings diff --git a/modules/cmdrunner/widget.go b/modules/cmdrunner/widget.go index d882d8f45..ddcfd36d8 100644 --- a/modules/cmdrunner/widget.go +++ b/modules/cmdrunner/widget.go @@ -1,21 +1,25 @@ package cmdrunner import ( + "bytes" "fmt" "os/exec" "strings" + "sync" "github.com/rivo/tview" - "github.com/wtfutil/wtf/utils" "github.com/wtfutil/wtf/view" ) +// Widget contains the data for this widget type Widget struct { view.TextWidget - args []string - cmd string settings *Settings + + m sync.Mutex + buffer *bytes.Buffer + running bool } // NewWidget creates a new instance of the widget @@ -23,18 +27,18 @@ func NewWidget(app *tview.Application, settings *Settings) *Widget { widget := Widget{ TextWidget: view.NewTextWidget(app, settings.common), - args: settings.args, - cmd: settings.cmd, settings: settings, + buffer: &bytes.Buffer{}, } widget.View.SetWrap(true) + widget.View.SetScrollable(true) return &widget } func (widget *Widget) content() (string, string, bool) { - result := widget.execute() + result := widget.buffer.String() ansiTitle := tview.TranslateANSI(widget.CommonSettings().Title) if ansiTitle == defaultTitle { @@ -47,23 +51,86 @@ func (widget *Widget) content() (string, string, bool) { // Refresh executes the command and updates the view with the results func (widget *Widget) Refresh() { + widget.m.Lock() + defer widget.m.Unlock() + + widget.execute() widget.Redraw(widget.content) + if widget.settings.tail { + widget.View.ScrollToEnd() + } } // String returns the string representation of the widget func (widget *Widget) String() string { - args := strings.Join(widget.args, " ") + args := strings.Join(widget.settings.args, " ") if args != "" { - return fmt.Sprintf(" %s %s ", widget.cmd, args) + return fmt.Sprintf(" %s %s ", widget.settings.cmd, args) } - return fmt.Sprintf(" %s ", widget.cmd) + return fmt.Sprintf(" %s ", widget.settings.cmd) +} + +func (widget *Widget) Write(p []byte) (n int, err error) { + widget.m.Lock() + defer widget.m.Unlock() + + // Write the new data into the buffer + n, err = widget.buffer.Write(p) + + // Remove lines that exceed maxLines + lines := widget.countLines() + if lines > widget.settings.maxLines { + widget.drainLines(lines - widget.settings.maxLines) + } + + // Redraw the widget + widget.Redraw(widget.content) + + return } /* -------------------- Unexported Functions -------------------- */ -func (widget *Widget) execute() string { - cmd := exec.Command(widget.cmd, widget.args...) - return utils.ExecuteCommand(cmd) +func (widget *Widget) execute() { + // Make sure the command is not already running + if widget.running { + return + } + + // Reset the buffer + widget.buffer.Reset() + + // Indicate that the command is running + widget.running = true + + // Setup the command to run + cmd := exec.Command(widget.settings.cmd, widget.settings.args...) + cmd.Stdout = widget + + // Run the command and wait for it to exit in another Go-routine + go func() { + err := cmd.Run() + + // The command has exited, print any error messages + widget.m.Lock() + if err != nil { + widget.buffer.WriteString(err.Error()) + } + widget.running = false + widget.m.Unlock() + }() +} + +// countLines counts the lines of data in the buffer +func (widget *Widget) countLines() int { + return bytes.Count(widget.buffer.Bytes(), []byte{'\n'}) +} + +// drainLines removed the first n lines from the buffer +func (widget *Widget) drainLines(n int) { + for i := 0; i < n; i++ { + widget.buffer.ReadBytes('\n') + } } From 0629e28105efa5fb31288a14941c8f0e0729d6b4 Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Mon, 7 Oct 2019 22:15:14 +0200 Subject: [PATCH 2/2] Set a proper default line limit --- modules/cmdrunner/settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cmdrunner/settings.go b/modules/cmdrunner/settings.go index 44dec40ac..3f6c13db7 100644 --- a/modules/cmdrunner/settings.go +++ b/modules/cmdrunner/settings.go @@ -30,7 +30,7 @@ func NewSettingsFromYAML(name string, moduleConfig *config.Config, globalConfig args: utils.ToStrs(moduleConfig.UList("args")), cmd: moduleConfig.UString("cmd"), tail: moduleConfig.UBool("tail"), - maxLines: moduleConfig.UInt("maxLines", -1), + maxLines: moduleConfig.UInt("maxLines", 256), } return &settings