Skip to content

Commit

Permalink
Merge pull request #680 from noxer/master
Browse files Browse the repository at this point in the history
Make the cmdrunner more interactive
  • Loading branch information
senorprogrammer authored Oct 9, 2019
2 parents f91e2c3 + 0629e28 commit abfdad0
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 17 deletions.
16 changes: 11 additions & 5 deletions modules/cmdrunner/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", 256),
}

return &settings
Expand Down
91 changes: 79 additions & 12 deletions modules/cmdrunner/widget.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
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
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 {
Expand All @@ -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')
}
}

0 comments on commit abfdad0

Please sign in to comment.