From c9c7e124cc2364d7e223a9e82007a1fce4b6a0b3 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 12 Apr 2019 04:59:16 -0700 Subject: [PATCH] WTF-389 Don't load widgets that have invalid position co-ordinates in their config --- main.go | 145 +++++++++++++++++++++---------------- wtf/bargraph.go | 13 +++- wtf/display.go | 4 + wtf/enabler.go | 2 + wtf/position.go | 20 +++-- wtf/position_test.go | 86 ++++++++++++++++++++++ wtf/text_widget.go | 6 ++ wtf_tests/position_test.go | 27 ------- 8 files changed, 203 insertions(+), 100 deletions(-) create mode 100644 wtf/position_test.go delete mode 100644 wtf_tests/position_test.go diff --git a/main.go b/main.go index 079f407f..ac055346 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,7 @@ import ( ) var focusTracker wtf.FocusTracker -var widgets []wtf.Wtfable +var runningWidgets []wtf.Wtfable // Config parses the config.yml file and makes available the settings within var Config *config.Config @@ -81,13 +81,13 @@ var ( /* -------------------- Functions -------------------- */ -func disableAllWidgets() { +func disableAllWidgets(widgets []wtf.Wtfable) { for _, widget := range widgets { widget.Disable() } } -func initializeFocusTracker(app *tview.Application) { +func initializeFocusTracker(app *tview.Application, widgets []wtf.Wtfable) { focusTracker = wtf.FocusTracker{ App: app, Idx: -1, @@ -100,7 +100,7 @@ func initializeFocusTracker(app *tview.Application) { func keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyCtrlR: - refreshAllWidgets() + refreshAllWidgets(runningWidgets) case tcell.KeyTab: focusTracker.Next() case tcell.KeyBacktab: @@ -121,7 +121,7 @@ func loadConfigFile(filePath string) { wtf.Config = Config } -func refreshAllWidgets() { +func refreshAllWidgets(widgets []wtf.Wtfable) { for _, widget := range widgets { go widget.Refresh() } @@ -138,19 +138,21 @@ func watchForConfigChanges(app *tview.Application, configFilePath string, grid * watch := watcher.New() absPath, _ := wtf.ExpandHomeDir(configFilePath) - // notify write events. + // Notify write events watch.FilterOps(watcher.Write) go func() { for { select { case <-watch.Event: - loadConfigFile(absPath) // Disable all widgets to stop scheduler goroutines and rmeove widgets from memory. - disableAllWidgets() - widgets = nil - makeWidgets(app, pages) - initializeFocusTracker(app) + disableAllWidgets(runningWidgets) + + loadConfigFile(absPath) + + widgets := makeWidgets(app, pages) + initializeFocusTracker(app, widgets) + display := wtf.NewDisplay(widgets) pages.AddPage("grid", display.Grid, true, true) case err := <-watch.Error: @@ -172,112 +174,128 @@ func watchForConfigChanges(app *tview.Application, configFilePath string, grid * } } -func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { +func makeWidget(app *tview.Application, pages *tview.Pages, widgetName string) wtf.Wtfable { + var widget wtf.Wtfable + // Always in alphabetical order switch widgetName { case "bamboohr": - widgets = append(widgets, bamboohr.NewWidget(app)) + widget = bamboohr.NewWidget(app) case "bargraph": - widgets = append(widgets, bargraph.NewWidget(app)) + widget = bargraph.NewWidget(app) case "bittrex": - widgets = append(widgets, bittrex.NewWidget(app)) + widget = bittrex.NewWidget(app) case "blockfolio": - widgets = append(widgets, blockfolio.NewWidget(app)) + widget = blockfolio.NewWidget(app) case "circleci": - widgets = append(widgets, circleci.NewWidget(app)) + widget = circleci.NewWidget(app) case "clocks": - widgets = append(widgets, clocks.NewWidget(app)) + widget = clocks.NewWidget(app) case "cmdrunner": - widgets = append(widgets, cmdrunner.NewWidget(app)) - case "resourceusage": - widgets = append(widgets, resourceusage.NewWidget(app)) + widget = cmdrunner.NewWidget(app) case "cryptolive": - widgets = append(widgets, cryptolive.NewWidget(app)) + widget = cryptolive.NewWidget(app) case "datadog": - widgets = append(widgets, datadog.NewWidget(app)) + widget = datadog.NewWidget(app) case "gcal": - widgets = append(widgets, gcal.NewWidget(app)) + widget = gcal.NewWidget(app) case "gerrit": - widgets = append(widgets, gerrit.NewWidget(app, pages)) + widget = gerrit.NewWidget(app, pages) case "git": - widgets = append(widgets, git.NewWidget(app, pages)) + widget = git.NewWidget(app, pages) case "github": - widgets = append(widgets, github.NewWidget(app, pages)) + widget = github.NewWidget(app, pages) case "gitlab": - widgets = append(widgets, gitlab.NewWidget(app, pages)) + widget = gitlab.NewWidget(app, pages) case "gitter": - widgets = append(widgets, gitter.NewWidget(app, pages)) + widget = gitter.NewWidget(app, pages) case "gspreadsheets": - widgets = append(widgets, gspreadsheets.NewWidget(app)) + widget = gspreadsheets.NewWidget(app) case "hackernews": - widgets = append(widgets, hackernews.NewWidget(app, pages)) + widget = hackernews.NewWidget(app, pages) case "ipapi": - widgets = append(widgets, ipapi.NewWidget(app)) + widget = ipapi.NewWidget(app) case "ipinfo": - widgets = append(widgets, ipinfo.NewWidget(app)) + widget = ipinfo.NewWidget(app) case "jenkins": - widgets = append(widgets, jenkins.NewWidget(app, pages)) + widget = jenkins.NewWidget(app, pages) case "jira": - widgets = append(widgets, jira.NewWidget(app, pages)) + widget = jira.NewWidget(app, pages) case "logger": - widgets = append(widgets, logger.NewWidget(app)) + widget = logger.NewWidget(app) case "mercurial": - widgets = append(widgets, mercurial.NewWidget(app, pages)) + widget = mercurial.NewWidget(app, pages) case "nbascore": - widgets = append(widgets, nbascore.NewWidget(app, pages)) + widget = nbascore.NewWidget(app, pages) case "newrelic": - widgets = append(widgets, newrelic.NewWidget(app)) + widget = newrelic.NewWidget(app) case "opsgenie": - widgets = append(widgets, opsgenie.NewWidget(app)) + widget = opsgenie.NewWidget(app) case "pagerduty": - widgets = append(widgets, pagerduty.NewWidget(app)) + widget = pagerduty.NewWidget(app) case "power": - widgets = append(widgets, power.NewWidget(app)) + widget = power.NewWidget(app) case "prettyweather": - widgets = append(widgets, prettyweather.NewWidget(app)) + widget = prettyweather.NewWidget(app) + case "resourceusage": + widget = resourceusage.NewWidget(app) case "security": - widgets = append(widgets, security.NewWidget(app)) + widget = security.NewWidget(app) case "status": - widgets = append(widgets, status.NewWidget(app)) + widget = status.NewWidget(app) case "system": - widgets = append(widgets, system.NewWidget(app, date, version)) + widget = system.NewWidget(app, date, version) case "spotify": - widgets = append(widgets, spotify.NewWidget(app, pages)) + widget = spotify.NewWidget(app, pages) case "spotifyweb": - widgets = append(widgets, spotifyweb.NewWidget(app, pages)) + widget = spotifyweb.NewWidget(app, pages) case "textfile": - widgets = append(widgets, textfile.NewWidget(app, pages)) + widget = textfile.NewWidget(app, pages) case "todo": - widgets = append(widgets, todo.NewWidget(app, pages)) + widget = todo.NewWidget(app, pages) case "todoist": - widgets = append(widgets, todoist.NewWidget(app, pages)) + widget = todoist.NewWidget(app, pages) case "travisci": - widgets = append(widgets, travisci.NewWidget(app, pages)) + widget = travisci.NewWidget(app, pages) case "rollbar": - widgets = append(widgets, rollbar.NewWidget(app, pages)) + widget = rollbar.NewWidget(app, pages) case "trello": - widgets = append(widgets, trello.NewWidget(app)) + widget = trello.NewWidget(app) case "twitter": - widgets = append(widgets, twitter.NewWidget(app, pages)) + widget = twitter.NewWidget(app, pages) case "victorops": - widgets = append(widgets, victorops.NewWidget(app)) + widget = victorops.NewWidget(app) case "weather": - widgets = append(widgets, weather.NewWidget(app, pages)) + widget = weather.NewWidget(app, pages) case "zendesk": - widgets = append(widgets, zendesk.NewWidget(app)) + widget = zendesk.NewWidget(app) default: - widgets = append(widgets, unknown.NewWidget(app, widgetName)) + widget = unknown.NewWidget(app, widgetName) } + + return widget } -func makeWidgets(app *tview.Application, pages *tview.Pages) { +func makeWidgets(app *tview.Application, pages *tview.Pages) []wtf.Wtfable { + widgets := []wtf.Wtfable{} + mods, _ := Config.Map("wtf.mods") for mod := range mods { if enabled := Config.UBool("wtf.mods."+mod+".enabled", false); enabled { - addWidget(app, pages, mod) + widget := makeWidget(app, pages, mod) + + if widget.IsPositionable() { + widgets = append(widgets, widget) + } } } + + // This is a hack to allow refreshAllWidgets and disableAllWidgets to work + // Need to implement a non-global way to track these + runningWidgets = widgets + + return widgets } /* -------------------- Main -------------------- */ @@ -303,11 +321,12 @@ func main() { app := tview.NewApplication() pages := tview.NewPages() - makeWidgets(app, pages) - initializeFocusTracker(app) + widgets := makeWidgets(app, pages) + initializeFocusTracker(app, widgets) display := wtf.NewDisplay(widgets) pages.AddPage("grid", display.Grid, true, true) + app.SetInputCapture(keyboardIntercept) go watchForConfigChanges(app, flags.Config, display.Grid, pages) diff --git a/wtf/bargraph.go b/wtf/bargraph.go index 8642ea20..ebfa7486 100644 --- a/wtf/bargraph.go +++ b/wtf/bargraph.go @@ -18,7 +18,6 @@ type BarGraph struct { View *tview.TextView Position - } type Bar struct { @@ -78,6 +77,12 @@ func (widget *BarGraph) FocusChar() string { return "" } +// IsPositionable returns TRUE if the widget has valid position parameters, FALSE if it has +// invalid position parameters (ie: cannot be placed onscreen) +func (widget *BarGraph) IsPositionable() bool { + return widget.Position.IsValid() +} + func (widget *BarGraph) RefreshInterval() int { return widget.RefreshInt } @@ -154,11 +159,11 @@ func BuildStars(data []Bar, maxStars int, starChar string) string { fmt.Sprintf( "%s%s[[red]%s[white]%s] %s\n", bar.Label, - strings.Repeat(" ", longestLabel - len(bar.Label)), + strings.Repeat(" ", longestLabel-len(bar.Label)), strings.Repeat(starChar, starCount), - strings.Repeat(" ", maxStars - starCount), + strings.Repeat(" ", maxStars-starCount), label, - ), + ), ) } diff --git a/wtf/display.go b/wtf/display.go index 7779cb5b..431e36d4 100644 --- a/wtf/display.go +++ b/wtf/display.go @@ -26,6 +26,10 @@ func (display *Display) add(widget Wtfable) { return } + if !widget.IsPositionable() { + return + } + display.Grid.AddItem( widget.TextView(), widget.Top(), diff --git a/wtf/enabler.go b/wtf/enabler.go index c7a5b973..cdf2894d 100644 --- a/wtf/enabler.go +++ b/wtf/enabler.go @@ -3,5 +3,7 @@ package wtf type Enabler interface { Disabled() bool Enabled() bool + IsPositionable() bool + Disable() } diff --git a/wtf/position.go b/wtf/position.go index 521a93b3..18e8fc9e 100644 --- a/wtf/position.go +++ b/wtf/position.go @@ -18,18 +18,26 @@ func NewPosition(top, left, width, height int) Position { return pos } -func (pos *Position) Top() int { - return pos.top +func (pos *Position) IsValid() bool { + if pos.height < 1 || pos.left < 0 || pos.top < 0 || pos.width < 1 { + return false + } + + return true +} + +func (pos *Position) Height() int { + return pos.height } func (pos *Position) Left() int { return pos.left } +func (pos *Position) Top() int { + return pos.top +} + func (pos *Position) Width() int { return pos.width } - -func (pos *Position) Height() int { - return pos.height -} diff --git a/wtf/position_test.go b/wtf/position_test.go new file mode 100644 index 00000000..0e8edabd --- /dev/null +++ b/wtf/position_test.go @@ -0,0 +1,86 @@ +package wtf + +import ( + "testing" +) + +func Test_NewPosition(t *testing.T) { + pos := NewPosition(0, 1, 2, 3) + + if pos.Height() != 3 { + t.Fatalf("Expected 3 but got %d", pos.Height()) + } + + if pos.Left() != 1 { + t.Fatalf("Expected 1 but got %d", pos.Left()) + } + + if pos.Top() != 0 { + t.Fatalf("Expected 0 but got %d", pos.Top()) + } + + if pos.Width() != 2 { + t.Fatalf("Expected 2 but got %d", pos.Width()) + } +} + +func Test_IsValid(t *testing.T) { + tests := []struct { + name string + height int + left int + top int + width int + expected bool + }{ + { + name: "valid position", + height: 2, + left: 0, + top: 1, + width: 2, + expected: true, + }, + { + name: "invalid height", + height: 0, + left: 0, + top: 1, + width: 2, + expected: false, + }, + { + name: "invalid left", + height: 2, + left: -1, + top: 1, + width: 2, + expected: false, + }, + { + name: "invalid top", + height: 2, + left: 0, + top: -1, + width: 2, + expected: false, + }, + { + name: "invalid width", + height: 2, + left: 0, + top: 1, + width: 0, + expected: false, + }, + } + + for _, tt := range tests { + pos := NewPosition(tt.top, tt.left, tt.width, tt.height) + actual := pos.IsValid() + + if actual != tt.expected { + t.Errorf("%s: expected: %v, got: %v", tt.name, tt.expected, actual) + } + } +} diff --git a/wtf/text_widget.go b/wtf/text_widget.go index 1e661e9b..82bd1946 100644 --- a/wtf/text_widget.go +++ b/wtf/text_widget.go @@ -86,6 +86,12 @@ func (widget *TextWidget) FocusChar() string { return widget.focusChar } +// IsPositionable returns TRUE if the widget has valid position parameters, FALSE if it has +// invalid position parameters (ie: cannot be placed onscreen) +func (widget *TextWidget) IsPositionable() bool { + return widget.Position.IsValid() +} + func (widget *TextWidget) RefreshInterval() int { return widget.RefreshInt } diff --git a/wtf_tests/position_test.go b/wtf_tests/position_test.go deleted file mode 100644 index ed5cbd4c..00000000 --- a/wtf_tests/position_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package wtf_tests - -import ( - "testing" - - . "github.com/wtfutil/wtf/wtf" -) - -func TestPosition(t *testing.T) { - pos := NewPosition(0, 1, 2, 3) - - if pos.Top() != 0 { - t.Fatalf("Expected 0 but got %d", pos.Top()) - } - - if pos.Left() != 1 { - t.Fatalf("Expected 1 but got %d", pos.Left()) - } - - if pos.Width() != 2 { - t.Fatalf("Expected 2 but got %d", pos.Width()) - } - - if pos.Height() != 3 { - t.Fatalf("Expected 3 but got %d", pos.Height()) - } -}