From 923d70e0df3a5ded5b17bb771a2d74faab02494d Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 26 Jul 2019 21:45:21 -0700 Subject: [PATCH 1/8] Clean up the MakeWidgets process by miniming the number of params passed around --- help/help.go | 3 +- main.go | 4 +- maker/widget_maker.go | 124 ++++++++++++++++++----------------- modules/bamboohr/settings.go | 1 - wtf/utils.go | 5 ++ 5 files changed, 72 insertions(+), 65 deletions(-) diff --git a/help/help.go b/help/help.go index e8f84e6c..d753f5bd 100644 --- a/help/help.go +++ b/help/help.go @@ -18,8 +18,7 @@ func Display(moduleName string, config *config.Config) { } func helpFor(moduleName string, config *config.Config) string { - modConfig, _ := config.Get("wtf.mods." + moduleName) - widget := maker.MakeWidget(nil, nil, moduleName, moduleName, modConfig, config) + widget := maker.MakeWidget(nil, nil, moduleName, config) result := "" result += utils.StripColorTags(widget.HelpText()) diff --git a/main.go b/main.go index df46e8cd..31288936 100644 --- a/main.go +++ b/main.go @@ -154,8 +154,6 @@ func main() { setTerm(config) - wtf.OpenFileUtil = config.UString("wtf.openFileUtil", "open") - app := tview.NewApplication() pages := tview.NewPages() @@ -173,6 +171,8 @@ func main() { go watchForConfigChanges(app, flags.Config, flags.HasCustomConfig(), display.Grid, pages) + wtf.Init(config.UString("wtf.openFileUtil", "open")) + if err := app.SetRoot(pages, true).Run(); err != nil { fmt.Printf("\n%s %v\n", aurora.Red("ERROR"), err) os.Exit(1) diff --git a/maker/widget_maker.go b/maker/widget_maker.go index 05148cb3..cb11cba5 100644 --- a/maker/widget_maker.go +++ b/maker/widget_maker.go @@ -55,181 +55,185 @@ import ( "github.com/wtfutil/wtf/wtf" ) +// MakeWidget creates and returns instances of widgets func MakeWidget( app *tview.Application, pages *tview.Pages, - widgetName string, - widgetType string, - moduleConfig *config.Config, - globalConfig *config.Config, + moduleName string, + config *config.Config, ) wtf.Wtfable { var widget wtf.Wtfable + moduleConfig, _ := config.Get("wtf.mods." + moduleName) + if enabled := moduleConfig.UBool("enabled", false); !enabled { + // Don't initialize modules that aren't enabled + return nil + } + // Always in alphabetical order - switch widgetType { + switch moduleConfig.UString("type", moduleName) { case "bamboohr": - settings := bamboohr.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := bamboohr.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = bamboohr.NewWidget(app, settings) case "bargraph": - settings := bargraph.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := bargraph.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = bargraph.NewWidget(app, settings) case "bittrex": - settings := bittrex.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := bittrex.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = bittrex.NewWidget(app, settings) case "blockfolio": - settings := blockfolio.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := blockfolio.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = blockfolio.NewWidget(app, settings) case "circleci": - settings := circleci.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := circleci.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = circleci.NewWidget(app, settings) case "clocks": - settings := clocks.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := clocks.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = clocks.NewWidget(app, settings) case "cmdrunner": - settings := cmdrunner.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := cmdrunner.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = cmdrunner.NewWidget(app, settings) case "cryptolive": - settings := cryptolive.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := cryptolive.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = cryptolive.NewWidget(app, settings) case "datadog": - settings := datadog.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := datadog.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = datadog.NewWidget(app, pages, settings) case "feedreader": - settings := feedreader.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := feedreader.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = feedreader.NewWidget(app, pages, settings) case "gcal": - settings := gcal.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := gcal.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = gcal.NewWidget(app, settings) case "gerrit": - settings := gerrit.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := gerrit.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = gerrit.NewWidget(app, pages, settings) case "git": - settings := git.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := git.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = git.NewWidget(app, pages, settings) case "github": - settings := github.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := github.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = github.NewWidget(app, pages, settings) case "gitlab": - settings := gitlab.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := gitlab.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = gitlab.NewWidget(app, pages, settings) case "gitter": - settings := gitter.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := gitter.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = gitter.NewWidget(app, pages, settings) case "googleanalytics": - settings := googleanalytics.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := googleanalytics.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = googleanalytics.NewWidget(app, settings) case "gspreadsheets": - settings := gspreadsheets.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := gspreadsheets.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = gspreadsheets.NewWidget(app, settings) case "hackernews": - settings := hackernews.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := hackernews.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = hackernews.NewWidget(app, pages, settings) case "hibp": - settings := hibp.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := hibp.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = hibp.NewWidget(app, settings) case "ipapi": - settings := ipapi.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := ipapi.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = ipapi.NewWidget(app, settings) case "ipinfo": - settings := ipinfo.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := ipinfo.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = ipinfo.NewWidget(app, settings) case "jenkins": - settings := jenkins.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := jenkins.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = jenkins.NewWidget(app, pages, settings) case "jira": - settings := jira.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := jira.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = jira.NewWidget(app, pages, settings) case "logger": - settings := logger.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := logger.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = logger.NewWidget(app, settings) case "mercurial": - settings := mercurial.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := mercurial.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = mercurial.NewWidget(app, pages, settings) case "nbascore": - settings := nbascore.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := nbascore.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = nbascore.NewWidget(app, pages, settings) case "newrelic": - settings := newrelic.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := newrelic.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = newrelic.NewWidget(app, settings) case "opsgenie": - settings := opsgenie.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := opsgenie.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = opsgenie.NewWidget(app, settings) case "pagerduty": - settings := pagerduty.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := pagerduty.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = pagerduty.NewWidget(app, settings) case "power": - settings := power.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := power.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = power.NewWidget(app, settings) case "prettyweather": - settings := prettyweather.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := prettyweather.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = prettyweather.NewWidget(app, settings) case "resourceusage": - settings := resourceusage.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := resourceusage.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = resourceusage.NewWidget(app, settings) case "rollbar": - settings := rollbar.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := rollbar.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = rollbar.NewWidget(app, pages, settings) case "security": - settings := security.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := security.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = security.NewWidget(app, settings) case "spotify": - settings := spotify.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := spotify.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = spotify.NewWidget(app, pages, settings) case "spotifyweb": - settings := spotifyweb.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := spotifyweb.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = spotifyweb.NewWidget(app, pages, settings) case "status": - settings := status.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := status.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = status.NewWidget(app, settings) case "textfile": - settings := textfile.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := textfile.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = textfile.NewWidget(app, pages, settings) case "todo": - settings := todo.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := todo.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = todo.NewWidget(app, pages, settings) case "todoist": - settings := todoist.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := todoist.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = todoist.NewWidget(app, pages, settings) case "transmission": - settings := transmission.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := transmission.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = transmission.NewWidget(app, pages, settings) case "travisci": - settings := travisci.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := travisci.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = travisci.NewWidget(app, pages, settings) case "trello": - settings := trello.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := trello.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = trello.NewWidget(app, settings) case "twitter": - settings := twitter.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := twitter.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = twitter.NewWidget(app, pages, settings) case "victorops": - settings := victorops.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := victorops.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = victorops.NewWidget(app, settings) case "weather": - settings := weather.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := weather.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = weather.NewWidget(app, pages, settings) case "zendesk": - settings := zendesk.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := zendesk.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = zendesk.NewWidget(app, pages, settings) default: - settings := unknown.NewSettingsFromYAML(widgetName, moduleConfig, globalConfig) + settings := unknown.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = unknown.NewWidget(app, settings) } return widget } +// MakeWidgets creates and returns a collection of enabled widgets func MakeWidgets(app *tview.Application, pages *tview.Pages, config *config.Config) []wtf.Wtfable { widgets := []wtf.Wtfable{} - mods, _ := config.Map("wtf.mods") + moduleNames, _ := config.Map("wtf.mods") - for mod := range mods { - modConfig, _ := config.Get("wtf.mods." + mod) - widgetType := modConfig.UString("type", mod) + for moduleName := range moduleNames { + widget := MakeWidget(app, pages, moduleName, config) - if enabled := modConfig.UBool("enabled", false); enabled { - widget := MakeWidget(app, pages, mod, widgetType, modConfig, config) + if widget != nil { widgets = append(widgets, widget) } } diff --git a/modules/bamboohr/settings.go b/modules/bamboohr/settings.go index 6f781cec..5e05bded 100644 --- a/modules/bamboohr/settings.go +++ b/modules/bamboohr/settings.go @@ -17,7 +17,6 @@ type Settings struct { } func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { - settings := Settings{ common: cfg.NewCommonSettingsFromModule(name, defaultTitle, ymlConfig, globalConfig), diff --git a/wtf/utils.go b/wtf/utils.go index c4a2512c..fa5ea96e 100644 --- a/wtf/utils.go +++ b/wtf/utils.go @@ -23,6 +23,11 @@ const TimestampFormat = "2006-01-02T15:04:05-0700" var OpenFileUtil = "open" +// Init initializes global settings in the wtf package +func Init(openFileUtil string) { + OpenFileUtil = openFileUtil +} + // CenterText takes a string and a width and pads the left and right of the string with // empty spaces to ensure that the string is in the middle of the returned value // From f4886fdb7a15a6f5faa70def9d2c3265233152c6 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 26 Jul 2019 23:58:26 -0700 Subject: [PATCH 2/8] Extract most app setup out of main and into wtf_app --- {wtf => app}/display.go | 21 +++--- app/wtf_app.go | 156 ++++++++++++++++++++++++++++++++++++++++ main.go | 132 ++++------------------------------ 3 files changed, 183 insertions(+), 126 deletions(-) rename {wtf => app}/display.go (51%) create mode 100644 app/wtf_app.go diff --git a/wtf/display.go b/app/display.go similarity index 51% rename from wtf/display.go rename to app/display.go index 27931950..0801c879 100644 --- a/wtf/display.go +++ b/app/display.go @@ -1,30 +1,33 @@ -package wtf +package app import ( "github.com/olebedev/config" "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" ) +// Display is the container for the onscreen representation of a WtfApp type Display struct { Grid *tview.Grid config *config.Config } -func NewDisplay(widgets []Wtfable, config *config.Config) *Display { +// NewDisplay creates and returns a Display +func NewDisplay(widgets []wtf.Wtfable, config *config.Config) *Display { display := Display{ Grid: tview.NewGrid(), config: config, } display.build(widgets) - display.Grid.SetBackgroundColor(ColorFor(config.UString("wtf.colors.background", "black"))) + display.Grid.SetBackgroundColor(wtf.ColorFor(config.UString("wtf.colors.background", "black"))) return &display } /* -------------------- Unexported Functions -------------------- */ -func (display *Display) add(widget Wtfable) { +func (display *Display) add(widget wtf.Wtfable) { if widget.Disabled() { return } @@ -41,14 +44,16 @@ func (display *Display) add(widget Wtfable) { ) } -func (display *Display) build(widgets []Wtfable) *tview.Grid { - display.Grid.SetColumns(ToInts(display.config.UList("wtf.grid.columns"))...) - display.Grid.SetRows(ToInts(display.config.UList("wtf.grid.rows"))...) +func (display *Display) build(widgets []wtf.Wtfable) *tview.Grid { + cols := wtf.ToInts(display.config.UList("wtf.grid.columns")) + rows := wtf.ToInts(display.config.UList("wtf.grid.rows")) + + display.Grid.SetColumns(cols...) + display.Grid.SetRows(rows...) display.Grid.SetBorder(false) for _, widget := range widgets { display.add(widget) - go Schedule(widget) } return display.Grid diff --git a/app/wtf_app.go b/app/wtf_app.go new file mode 100644 index 00000000..289ad7e3 --- /dev/null +++ b/app/wtf_app.go @@ -0,0 +1,156 @@ +package app + +import ( + "log" + "time" + + "github.com/gdamore/tcell" + "github.com/olebedev/config" + "github.com/radovskyb/watcher" + "github.com/rivo/tview" + "github.com/wtfutil/wtf/cfg" + "github.com/wtfutil/wtf/maker" + "github.com/wtfutil/wtf/utils" + "github.com/wtfutil/wtf/wtf" +) + +// WtfApp is the container for a collection of widgets that are all constructed from a single +// configuration file and displayed together +type WtfApp struct { + App *tview.Application + Config *config.Config + ConfigFilePath string + Display *Display + FocusTracker wtf.FocusTracker + IsCustomConfig bool + Pages *tview.Pages + Widgets []wtf.Wtfable +} + +// NewWtfApp creates and returns an instance of WtfApp +func NewWtfApp(app *tview.Application, config *config.Config, configFilePath string, isCustom bool) *WtfApp { + wtfApp := WtfApp{ + App: app, + Config: config, + ConfigFilePath: configFilePath, + IsCustomConfig: isCustom, + Pages: tview.NewPages(), + } + + wtfApp.Widgets = maker.MakeWidgets(wtfApp.App, wtfApp.Pages, wtfApp.Config) + wtfApp.Display = NewDisplay(wtfApp.Widgets, wtfApp.Config) + wtfApp.FocusTracker = wtf.NewFocusTracker(wtfApp.App, wtfApp.Widgets, wtfApp.Config) + + wtfApp.App.SetInputCapture(wtfApp.keyboardIntercept) + wtfApp.Pages.AddPage("grid", wtfApp.Display.Grid, true, true) + wtfApp.App.SetRoot(wtfApp.Pages, true) + + wtf.ValidateWidgets(wtfApp.Widgets) + + wtfApp.scheduleWidgets() + + return &wtfApp +} + +/* -------------------- Exported Functions -------------------- */ + +// Start initializes the app +func (wtfApp *WtfApp) Start() { + go wtfApp.watchForConfigChanges() +} + +// Stop kills all the currently-running widgets in this app +func (wtfApp *WtfApp) Stop() { + // TODO: Pretty sure we should kill their go routines that run them as well + // otherwise....? + wtfApp.disableAllWidgets() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (wtfApp *WtfApp) disableAllWidgets() { + for _, widget := range wtfApp.Widgets { + widget.Disable() + } +} + +func (wtfApp *WtfApp) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + // These keys are global keys used by the app. Widgets should not implement these keys + switch event.Key() { + case tcell.KeyCtrlR: + wtfApp.refreshAllWidgets() + return nil + case tcell.KeyTab: + wtfApp.FocusTracker.Next() + return nil + case tcell.KeyBacktab: + wtfApp.FocusTracker.Prev() + return nil + case tcell.KeyEsc: + wtfApp.FocusTracker.None() + return nil + } + + // Checks to see if any widget has been assigned the pressed key as its focus key + if wtfApp.FocusTracker.FocusOn(string(event.Rune())) { + return nil + } + + // If no specific widget has focus, then allow the key presses to fall through to the app + if !wtfApp.FocusTracker.IsFocused { + switch string(event.Rune()) { + case "/": + return nil + } + } + + return event +} + +func (wtfApp *WtfApp) refreshAllWidgets() { + for _, widget := range wtfApp.Widgets { + go widget.Refresh() + } +} + +func (wtfApp *WtfApp) scheduleWidgets() { + for _, widget := range wtfApp.Widgets { + go wtf.Schedule(widget) + } +} + +func (wtfApp *WtfApp) watchForConfigChanges() { + watch := watcher.New() + + // Notify write events + watch.FilterOps(watcher.Write) + + go func() { + for { + select { + case <-watch.Event: + wtfApp.Stop() + + config := cfg.LoadWtfConfigFile(wtfApp.ConfigFilePath, wtfApp.IsCustomConfig) + + newApp := NewWtfApp(wtfApp.App, config, wtfApp.ConfigFilePath, wtfApp.IsCustomConfig) + newApp.Start() + case err := <-watch.Error: + log.Fatalln(err) + case <-watch.Closed: + return + } + } + }() + + // Watch config file for changes. + absPath, _ := utils.ExpandHomeDir(wtfApp.ConfigFilePath) + if err := watch.Add(absPath); err != nil { + log.Fatalln(err) + } + + // Start the watching process - it'll check for changes every 100ms. + if err := watch.Start(time.Millisecond * 100); err != nil { + log.Fatalln(err) + } +} diff --git a/main.go b/main.go index 31288936..de8a37a1 100644 --- a/main.go +++ b/main.go @@ -9,23 +9,19 @@ import ( "fmt" "log" "os" - "time" - "github.com/gdamore/tcell" "github.com/logrusorgru/aurora" "github.com/olebedev/config" "github.com/pkg/profile" - "github.com/radovskyb/watcher" + "github.com/rivo/tview" + "github.com/wtfutil/wtf/app" "github.com/wtfutil/wtf/cfg" "github.com/wtfutil/wtf/flags" - "github.com/wtfutil/wtf/maker" - "github.com/wtfutil/wtf/utils" "github.com/wtfutil/wtf/wtf" ) -var focusTracker wtf.FocusTracker -var runningWidgets []wtf.Wtfable +var tviewApp *tview.Application var ( commit = "dev" @@ -35,52 +31,6 @@ var ( /* -------------------- Functions -------------------- */ -func disableAllWidgets(widgets []wtf.Wtfable) { - for _, widget := range widgets { - widget.Disable() - } -} - -func keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { - // These keys are global keys used by the app. Widgets should not implement these keys - switch event.Key() { - case tcell.KeyCtrlR: - refreshAllWidgets(runningWidgets) - return nil - case tcell.KeyTab: - focusTracker.Next() - return nil - case tcell.KeyBacktab: - focusTracker.Prev() - return nil - case tcell.KeyEsc: - focusTracker.None() - return nil - } - - // This function checks to see if any widget has been assigned the pressed key as its - // focus key - if focusTracker.FocusOn(string(event.Rune())) { - return nil - } - - // If no specific widget has focus, then allow the key presses to fall through to the app - if !focusTracker.IsFocused { - switch string(event.Rune()) { - case "/": - return nil - } - } - - return event -} - -func refreshAllWidgets(widgets []wtf.Wtfable) { - for _, widget := range widgets { - go widget.Refresh() - } -} - func setTerm(config *config.Config) { term := config.UString("wtf.term", os.Getenv("TERM")) err := os.Setenv("TERM", term) @@ -90,61 +40,19 @@ func setTerm(config *config.Config) { } } -func watchForConfigChanges(app *tview.Application, configFilePath string, isCustomConfig bool, grid *tview.Grid, pages *tview.Pages) { - watch := watcher.New() - absPath, _ := utils.ExpandHomeDir(configFilePath) - - // Notify write events - watch.FilterOps(watcher.Write) - - go func() { - for { - select { - case <-watch.Event: - // Disable all widgets to stop scheduler goroutines and remove widgets from memory - disableAllWidgets(runningWidgets) - - config := cfg.LoadWtfConfigFile(absPath, false) - - widgets := maker.MakeWidgets(app, pages, config) - runningWidgets = widgets - - wtf.ValidateWidgets(widgets) - - focusTracker = wtf.NewFocusTracker(app, widgets, config) - - display := wtf.NewDisplay(widgets, config) - pages.AddPage("grid", display.Grid, true, true) - case err := <-watch.Error: - log.Fatalln(err) - case <-watch.Closed: - return - } - } - }() - - // Watch config file for changes. - if err := watch.Add(absPath); err != nil { - log.Fatalln(err) - } - - // Start the watching process - it'll check for changes every 100ms. - if err := watch.Start(time.Millisecond * 100); err != nil { - log.Fatalln(err) - } -} - /* -------------------- Main -------------------- */ func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) - // Manage the configuration directories and file + // Manage the configuration directories and config file cfg.Initialize() // Parse and handle flags flags := flags.NewFlags() flags.Parse() + + // Load the configuration file config := cfg.LoadWtfConfigFile(flags.ConfigFilePath(), flags.HasCustomConfig()) flags.RenderIf(version, config) @@ -152,28 +60,16 @@ func main() { defer profile.Start(profile.MemProfile).Stop() } - setTerm(config) - - app := tview.NewApplication() - pages := tview.NewPages() - - widgets := maker.MakeWidgets(app, pages, config) - runningWidgets = widgets - - wtf.ValidateWidgets(widgets) - - focusTracker = wtf.NewFocusTracker(app, widgets, config) - - display := wtf.NewDisplay(widgets, config) - pages.AddPage("grid", display.Grid, true, true) - - app.SetInputCapture(keyboardIntercept) - - go watchForConfigChanges(app, flags.Config, flags.HasCustomConfig(), display.Grid, pages) - wtf.Init(config.UString("wtf.openFileUtil", "open")) - if err := app.SetRoot(pages, true).Run(); err != nil { + setTerm(config) + + // Build the application + tviewApp = tview.NewApplication() + wtfApp := app.NewWtfApp(tviewApp, config, flags.Config, flags.HasCustomConfig()) + wtfApp.Start() + + if err := wtfApp.App.Run(); err != nil { fmt.Printf("\n%s %v\n", aurora.Red("ERROR"), err) os.Exit(1) } From 24cf82cc53884300a70f1314be9ff28a291ae7b4 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 27 Jul 2019 06:45:14 -0700 Subject: [PATCH 3/8] Move the scheduler up into /app --- app/scheduler.go | 37 +++++++++++++++++++++++++++++++++++++ app/wtf_app.go | 7 ++----- wtf/enablable.go | 3 +-- wtf/schedulable.go | 34 ---------------------------------- 4 files changed, 40 insertions(+), 41 deletions(-) create mode 100644 app/scheduler.go diff --git a/app/scheduler.go b/app/scheduler.go new file mode 100644 index 00000000..10a38207 --- /dev/null +++ b/app/scheduler.go @@ -0,0 +1,37 @@ +package app + +import ( + "time" + + "github.com/wtfutil/wtf/wtf" +) + +// Schedule kicks off the first refresh of a module's data and then queues the rest of the +// data refreshes on a timer +func Schedule(widget wtf.Wtfable) { + widget.Refresh() + + interval := time.Duration(widget.RefreshInterval()) * time.Second + + if interval <= 0 { + return + } + + tick := time.NewTicker(interval) + quit := make(chan struct{}) + + for { + select { + case <-tick.C: + if widget.Enabled() { + widget.Refresh() + } else { + tick.Stop() + return + } + case <-quit: + tick.Stop() + return + } + } +} diff --git a/app/wtf_app.go b/app/wtf_app.go index 289ad7e3..7d9ad76e 100644 --- a/app/wtf_app.go +++ b/app/wtf_app.go @@ -47,8 +47,6 @@ func NewWtfApp(app *tview.Application, config *config.Config, configFilePath str wtf.ValidateWidgets(wtfApp.Widgets) - wtfApp.scheduleWidgets() - return &wtfApp } @@ -56,13 +54,12 @@ func NewWtfApp(app *tview.Application, config *config.Config, configFilePath str // Start initializes the app func (wtfApp *WtfApp) Start() { + wtfApp.scheduleWidgets() go wtfApp.watchForConfigChanges() } // Stop kills all the currently-running widgets in this app func (wtfApp *WtfApp) Stop() { - // TODO: Pretty sure we should kill their go routines that run them as well - // otherwise....? wtfApp.disableAllWidgets() } @@ -115,7 +112,7 @@ func (wtfApp *WtfApp) refreshAllWidgets() { func (wtfApp *WtfApp) scheduleWidgets() { for _, widget := range wtfApp.Widgets { - go wtf.Schedule(widget) + go Schedule(widget) } } diff --git a/wtf/enablable.go b/wtf/enablable.go index fc02ab88..fe95afd2 100644 --- a/wtf/enablable.go +++ b/wtf/enablable.go @@ -2,8 +2,7 @@ package wtf // Enablable is the interface that enforces enable/disable capabilities on a module type Enablable interface { + Disable() Disabled() bool Enabled() bool - - Disable() } diff --git a/wtf/schedulable.go b/wtf/schedulable.go index 5ebd0fc4..24ee6dce 100644 --- a/wtf/schedulable.go +++ b/wtf/schedulable.go @@ -1,42 +1,8 @@ package wtf -import ( - "time" -) - // Schedulable is the interface that enforces scheduling capabilities on a module type Schedulable interface { Refresh() Refreshing() bool RefreshInterval() int } - -// Schedule kicks off the first refresh of a module's data and then queues the rest of the -// data refreshes on a timer -func Schedule(widget Wtfable) { - widget.Refresh() - - interval := time.Duration(widget.RefreshInterval()) * time.Second - - if interval <= 0 { - return - } - - tick := time.NewTicker(interval) - quit := make(chan struct{}) - - for { - select { - case <-tick.C: - if widget.Enabled() { - widget.Refresh() - } else { - tick.Stop() - return - } - case <-quit: - tick.Stop() - return - } - } -} From 991119e5c5db0143551c971d8e25f19500ad89b9 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 27 Jul 2019 07:25:55 -0700 Subject: [PATCH 4/8] Add ability to explicitly stop modules via a QuitChan --- app/scheduler.go | 15 ++++++++------- app/wtf_app.go | 6 +++--- modules/spotify/keyboard.go | 6 +++--- modules/spotify/widget.go | 9 ++++----- wtf/bargraph.go | 14 +++++++++++++- wtf/stoppable.go | 6 ++++++ wtf/text_widget.go | 11 +++++++++++ wtf/wtfable.go | 2 ++ 8 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 wtf/stoppable.go diff --git a/app/scheduler.go b/app/scheduler.go index 10a38207..72f69d8f 100644 --- a/app/scheduler.go +++ b/app/scheduler.go @@ -17,21 +17,22 @@ func Schedule(widget wtf.Wtfable) { return } - tick := time.NewTicker(interval) - quit := make(chan struct{}) + timer := time.NewTicker(interval) for { select { - case <-tick.C: + case <-timer.C: if widget.Enabled() { widget.Refresh() } else { - tick.Stop() + timer.Stop() + return + } + case quit := <-widget.QuitChan(): + if quit == true { + timer.Stop() return } - case <-quit: - tick.Stop() - return } } } diff --git a/app/wtf_app.go b/app/wtf_app.go index 7d9ad76e..2cd450a2 100644 --- a/app/wtf_app.go +++ b/app/wtf_app.go @@ -60,14 +60,14 @@ func (wtfApp *WtfApp) Start() { // Stop kills all the currently-running widgets in this app func (wtfApp *WtfApp) Stop() { - wtfApp.disableAllWidgets() + wtfApp.stopAllWidgets() } /* -------------------- Unexported Functions -------------------- */ -func (wtfApp *WtfApp) disableAllWidgets() { +func (wtfApp *WtfApp) stopAllWidgets() { for _, widget := range wtfApp.Widgets { - widget.Disable() + widget.Stop() } } diff --git a/modules/spotify/keyboard.go b/modules/spotify/keyboard.go index ba5cf8db..6c650834 100644 --- a/modules/spotify/keyboard.go +++ b/modules/spotify/keyboard.go @@ -18,19 +18,19 @@ func (widget *Widget) initializeKeyboardControls() { } func (widget *Widget) previous() { - widget.SpotifyClient.Previous() + widget.client.Previous() time.Sleep(time.Second * 1) widget.Refresh() } func (widget *Widget) next() { - widget.SpotifyClient.Next() + widget.client.Next() time.Sleep(time.Second * 1) widget.Refresh() } func (widget *Widget) playPause() { - widget.SpotifyClient.PlayPause() + widget.client.PlayPause() time.Sleep(time.Second * 1) widget.Refresh() } diff --git a/modules/spotify/widget.go b/modules/spotify/widget.go index 17493e08..10caeccd 100644 --- a/modules/spotify/widget.go +++ b/modules/spotify/widget.go @@ -13,20 +13,19 @@ type Widget struct { wtf.KeyboardWidget wtf.TextWidget + client spotigopher.SpotifyClient settings *Settings spotigopher.Info - spotigopher.SpotifyClient } // NewWidget creates a new instance of a widget func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget { - spotifyClient := spotigopher.NewClient() widget := Widget{ KeyboardWidget: wtf.NewKeyboardWidget(app, pages, settings.common), TextWidget: wtf.NewTextWidget(app, settings.common, true), - Info: spotigopher.Info{}, - SpotifyClient: spotifyClient, + Info: spotigopher.Info{}, + client: spotigopher.NewClient(), settings: settings, } @@ -45,7 +44,7 @@ func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) * } func (w *Widget) refreshSpotifyInfos() error { - info, err := w.SpotifyClient.GetInfo() + info, err := w.client.GetInfo() w.Info = info return err } diff --git a/wtf/bargraph.go b/wtf/bargraph.go index 41486b6b..ac827524 100644 --- a/wtf/bargraph.go +++ b/wtf/bargraph.go @@ -17,6 +17,7 @@ type BarGraph struct { key string maxStars int name string + quitChan chan bool refreshing bool starChar string @@ -37,9 +38,11 @@ func NewBarGraph(app *tview.Application, name string, settings *cfg.Common, focu focusable: focusable, maxStars: settings.Config.UInt("graphStars", 20), name: settings.Title, + quitChan: make(chan bool), starChar: settings.Config.UString("graphIcon", "|"), - RefreshInt: settings.RefreshInterval, commonSettings: settings, + + RefreshInt: settings.RefreshInterval, } widget.View = widget.addView() @@ -90,6 +93,10 @@ func (widget *BarGraph) Name() string { return widget.name } +func (widget *BarGraph) QuitChan() chan bool { + return widget.quitChan +} + // Refreshing returns TRUE if the widget is currently refreshing its data, FALSE if it is not func (widget *BarGraph) Refreshing() bool { return widget.refreshing @@ -104,6 +111,11 @@ func (widget *BarGraph) SetFocusChar(char string) { return } +func (widget *BarGraph) Stop() { + widget.enabled = false + widget.quitChan <- true +} + func (widget *BarGraph) TextView() *tview.TextView { return widget.View } diff --git a/wtf/stoppable.go b/wtf/stoppable.go new file mode 100644 index 00000000..b194dcc5 --- /dev/null +++ b/wtf/stoppable.go @@ -0,0 +1,6 @@ +package wtf + +// Stoppable is the interface that enforces a stoppable state +type Stoppable interface { + Stop() +} diff --git a/wtf/text_widget.go b/wtf/text_widget.go index 441f3685..eb851918 100644 --- a/wtf/text_widget.go +++ b/wtf/text_widget.go @@ -15,6 +15,7 @@ type TextWidget struct { focusable bool focusChar string name string + quitChan chan bool refreshing bool refreshInterval int app *tview.Application @@ -32,6 +33,7 @@ func NewTextWidget(app *tview.Application, commonSettings *cfg.Common, focusable focusable: focusable, focusChar: commonSettings.FocusChar(), name: commonSettings.Name, + quitChan: make(chan bool), refreshing: false, refreshInterval: commonSettings.RefreshInterval, } @@ -97,6 +99,10 @@ func (widget *TextWidget) HelpText() string { return fmt.Sprintf("\n There is no help available for widget %s", widget.commonSettings.Module.Type) } +func (widget *TextWidget) QuitChan() chan bool { + return widget.quitChan +} + func (widget *TextWidget) Name() string { return widget.name } @@ -115,6 +121,11 @@ func (widget *TextWidget) SetFocusChar(char string) { widget.focusChar = char } +func (widget *TextWidget) Stop() { + widget.enabled = false + widget.quitChan <- true +} + func (widget *TextWidget) String() string { return widget.name } diff --git a/wtf/wtfable.go b/wtf/wtfable.go index d3aac524..cb6fd39a 100644 --- a/wtf/wtfable.go +++ b/wtf/wtfable.go @@ -10,12 +10,14 @@ import ( type Wtfable interface { Enablable Schedulable + Stoppable BorderColor() string ConfigText() string FocusChar() string Focusable() bool HelpText() string + QuitChan() chan bool Name() string SetFocusChar(string) TextView() *tview.TextView From ac31ea2291308e506e8ab814cce893185774f375 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 27 Jul 2019 23:15:51 -0700 Subject: [PATCH 5/8] Improve error messages --- cfg/error_messages.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cfg/error_messages.go b/cfg/error_messages.go index b0c5c7d8..55be87c8 100644 --- a/cfg/error_messages.go +++ b/cfg/error_messages.go @@ -19,25 +19,25 @@ func displayError(err error) { } func displayDefaultConfigWriteError(err error) { - fmt.Printf("\n%s Could not write the default configuration.\n", aurora.Red("ERROR:")) + fmt.Printf("\n%s Could not write the default configuration.\n", aurora.Red("ERROR")) fmt.Println() displayError(err) } func displayXdgConfigDirCreateError(err error) { - fmt.Printf("\n%s Could not create the '%s' directory.\n", aurora.Red("ERROR:"), aurora.Yellow(XdgConfigDir)) + fmt.Printf("\n%s Could not create the '%s' directory.\n", aurora.Red("ERROR"), aurora.Yellow(XdgConfigDir)) fmt.Println() displayError(err) } func displayWtfConfigDirCreateError(err error) { - fmt.Printf("\n%s Could not create the '%s' directory.\n", aurora.Red("ERROR:"), aurora.Yellow(WtfConfigDirV2)) + fmt.Printf("\n%s Could not create the '%s' directory.\n", aurora.Red("ERROR"), aurora.Yellow(WtfConfigDirV2)) fmt.Println() displayError(err) } func displayWtfConfigFileLoadError(err error) { - fmt.Printf("\n%s Could not load '%s'.\n", aurora.Red("ERROR:"), aurora.Yellow(WtfConfigFile)) + fmt.Printf("\n%s Could not load '%s'.\n", aurora.Red("ERROR"), aurora.Yellow(WtfConfigFile)) fmt.Println() fmt.Println("This could mean one of two things:") fmt.Println() @@ -48,7 +48,7 @@ func displayWtfConfigFileLoadError(err error) { } func displayWtfCustomConfigFileLoadError(err error) { - fmt.Printf("\n%s Could not load '%s'.\n", aurora.Red("ERROR:"), aurora.Yellow(WtfConfigFile)) + fmt.Printf("\n%s Could not load '%s'.\n", aurora.Red("ERROR"), aurora.Yellow(WtfConfigFile)) fmt.Println() fmt.Println("This could mean one of two things:") fmt.Println() From bba54368f4efde6550df2b925e38bf53d2312227 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 27 Jul 2019 23:23:10 -0700 Subject: [PATCH 6/8] Make all exported fields unexported in WtfApp --- app/wtf_app.go | 67 +++++++++++++++++++++++++++----------------------- main.go | 2 +- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/app/wtf_app.go b/app/wtf_app.go index 2cd450a2..efa25343 100644 --- a/app/wtf_app.go +++ b/app/wtf_app.go @@ -17,41 +17,46 @@ import ( // WtfApp is the container for a collection of widgets that are all constructed from a single // configuration file and displayed together type WtfApp struct { - App *tview.Application - Config *config.Config - ConfigFilePath string - Display *Display - FocusTracker wtf.FocusTracker - IsCustomConfig bool - Pages *tview.Pages - Widgets []wtf.Wtfable + app *tview.Application + config *config.Config + configFilePath string + display *Display + focusTracker wtf.FocusTracker + isCustomConfig bool + pages *tview.Pages + widgets []wtf.Wtfable } // NewWtfApp creates and returns an instance of WtfApp func NewWtfApp(app *tview.Application, config *config.Config, configFilePath string, isCustom bool) *WtfApp { wtfApp := WtfApp{ - App: app, - Config: config, - ConfigFilePath: configFilePath, - IsCustomConfig: isCustom, - Pages: tview.NewPages(), + app: app, + config: config, + configFilePath: configFilePath, + isCustomConfig: isCustom, + pages: tview.NewPages(), } - wtfApp.Widgets = maker.MakeWidgets(wtfApp.App, wtfApp.Pages, wtfApp.Config) - wtfApp.Display = NewDisplay(wtfApp.Widgets, wtfApp.Config) - wtfApp.FocusTracker = wtf.NewFocusTracker(wtfApp.App, wtfApp.Widgets, wtfApp.Config) + wtfApp.widgets = maker.MakeWidgets(wtfApp.app, wtfApp.pages, wtfApp.config) + wtfApp.display = NewDisplay(wtfApp.widgets, wtfApp.config) + wtfApp.focusTracker = wtf.NewFocusTracker(wtfApp.app, wtfApp.widgets, wtfApp.config) - wtfApp.App.SetInputCapture(wtfApp.keyboardIntercept) - wtfApp.Pages.AddPage("grid", wtfApp.Display.Grid, true, true) - wtfApp.App.SetRoot(wtfApp.Pages, true) + wtfApp.pages.AddPage("grid", wtfApp.display.Grid, true, true) + wtfApp.app.SetRoot(wtfApp.pages, true) + wtfApp.app.SetInputCapture(wtfApp.keyboardIntercept) - wtf.ValidateWidgets(wtfApp.Widgets) + wtf.ValidateWidgets(wtfApp.widgets) return &wtfApp } /* -------------------- Exported Functions -------------------- */ +// App returns the *tview.Application instance +func (wtfApp *WtfApp) App() *tview.Application { + return wtfApp.app +} + // Start initializes the app func (wtfApp *WtfApp) Start() { wtfApp.scheduleWidgets() @@ -66,7 +71,7 @@ func (wtfApp *WtfApp) Stop() { /* -------------------- Unexported Functions -------------------- */ func (wtfApp *WtfApp) stopAllWidgets() { - for _, widget := range wtfApp.Widgets { + for _, widget := range wtfApp.widgets { widget.Stop() } } @@ -78,23 +83,23 @@ func (wtfApp *WtfApp) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { wtfApp.refreshAllWidgets() return nil case tcell.KeyTab: - wtfApp.FocusTracker.Next() + wtfApp.focusTracker.Next() return nil case tcell.KeyBacktab: - wtfApp.FocusTracker.Prev() + wtfApp.focusTracker.Prev() return nil case tcell.KeyEsc: - wtfApp.FocusTracker.None() + wtfApp.focusTracker.None() return nil } // Checks to see if any widget has been assigned the pressed key as its focus key - if wtfApp.FocusTracker.FocusOn(string(event.Rune())) { + if wtfApp.focusTracker.FocusOn(string(event.Rune())) { return nil } // If no specific widget has focus, then allow the key presses to fall through to the app - if !wtfApp.FocusTracker.IsFocused { + if !wtfApp.focusTracker.IsFocused { switch string(event.Rune()) { case "/": return nil @@ -105,13 +110,13 @@ func (wtfApp *WtfApp) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { } func (wtfApp *WtfApp) refreshAllWidgets() { - for _, widget := range wtfApp.Widgets { + for _, widget := range wtfApp.widgets { go widget.Refresh() } } func (wtfApp *WtfApp) scheduleWidgets() { - for _, widget := range wtfApp.Widgets { + for _, widget := range wtfApp.widgets { go Schedule(widget) } } @@ -128,9 +133,9 @@ func (wtfApp *WtfApp) watchForConfigChanges() { case <-watch.Event: wtfApp.Stop() - config := cfg.LoadWtfConfigFile(wtfApp.ConfigFilePath, wtfApp.IsCustomConfig) + config := cfg.LoadWtfConfigFile(wtfApp.configFilePath, wtfApp.isCustomConfig) - newApp := NewWtfApp(wtfApp.App, config, wtfApp.ConfigFilePath, wtfApp.IsCustomConfig) + newApp := NewWtfApp(wtfApp.app, config, wtfApp.configFilePath, wtfApp.isCustomConfig) newApp.Start() case err := <-watch.Error: log.Fatalln(err) @@ -141,7 +146,7 @@ func (wtfApp *WtfApp) watchForConfigChanges() { }() // Watch config file for changes. - absPath, _ := utils.ExpandHomeDir(wtfApp.ConfigFilePath) + absPath, _ := utils.ExpandHomeDir(wtfApp.configFilePath) if err := watch.Add(absPath); err != nil { log.Fatalln(err) } diff --git a/main.go b/main.go index de8a37a1..fd1cdb02 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,7 @@ func main() { wtfApp := app.NewWtfApp(tviewApp, config, flags.Config, flags.HasCustomConfig()) wtfApp.Start() - if err := wtfApp.App.Run(); err != nil { + if err := wtfApp.App().Run(); err != nil { fmt.Printf("\n%s %v\n", aurora.Red("ERROR"), err) os.Exit(1) } From 1b1ce693780f7dd62de2693a1c05db5ed22f1e98 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 28 Jul 2019 06:24:04 -0700 Subject: [PATCH 7/8] Move the FocusTracker from /wtf to /app --- {wtf => app}/focus_tracker.go | 17 +++++++++-------- app/wtf_app.go | 4 ++-- main.go | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) rename {wtf => app}/focus_tracker.go (90%) diff --git a/wtf/focus_tracker.go b/app/focus_tracker.go similarity index 90% rename from wtf/focus_tracker.go rename to app/focus_tracker.go index c2987f2a..0d6ef821 100644 --- a/wtf/focus_tracker.go +++ b/app/focus_tracker.go @@ -1,10 +1,11 @@ -package wtf +package app import ( "sort" "github.com/olebedev/config" "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" ) type FocusState int @@ -21,12 +22,12 @@ type FocusTracker struct { App *tview.Application Idx int IsFocused bool - Widgets []Wtfable + Widgets []wtf.Wtfable config *config.Config } -func NewFocusTracker(app *tview.Application, widgets []Wtfable, config *config.Config) FocusTracker { +func NewFocusTracker(app *tview.Application, widgets []wtf.Wtfable, config *config.Config) FocusTracker { focusTracker := FocusTracker{ App: app, Idx: -1, @@ -157,7 +158,7 @@ func (tracker *FocusTracker) blur(idx int) { view := widget.TextView() view.Blur() - view.SetBorderColor(ColorFor(widget.BorderColor())) + view.SetBorderColor(wtf.ColorFor(widget.BorderColor())) tracker.IsFocused = false } @@ -177,12 +178,12 @@ func (tracker *FocusTracker) focus(idx int) { } view := widget.TextView() - view.SetBorderColor(ColorFor(tracker.config.UString("wtf.colors.border.focused", "gray"))) + view.SetBorderColor(wtf.ColorFor(tracker.config.UString("wtf.colors.border.focused", "gray"))) tracker.App.SetFocus(view) } -func (tracker *FocusTracker) focusables() []Wtfable { - focusable := []Wtfable{} +func (tracker *FocusTracker) focusables() []wtf.Wtfable { + focusable := []wtf.Wtfable{} for _, widget := range tracker.Widgets { if widget.Focusable() { @@ -207,7 +208,7 @@ func (tracker *FocusTracker) focusables() []Wtfable { return focusable } -func (tracker *FocusTracker) focusableAt(idx int) Wtfable { +func (tracker *FocusTracker) focusableAt(idx int) wtf.Wtfable { if idx < 0 || idx >= len(tracker.focusables()) { return nil } diff --git a/app/wtf_app.go b/app/wtf_app.go index efa25343..dca52b19 100644 --- a/app/wtf_app.go +++ b/app/wtf_app.go @@ -21,7 +21,7 @@ type WtfApp struct { config *config.Config configFilePath string display *Display - focusTracker wtf.FocusTracker + focusTracker FocusTracker isCustomConfig bool pages *tview.Pages widgets []wtf.Wtfable @@ -39,7 +39,7 @@ func NewWtfApp(app *tview.Application, config *config.Config, configFilePath str wtfApp.widgets = maker.MakeWidgets(wtfApp.app, wtfApp.pages, wtfApp.config) wtfApp.display = NewDisplay(wtfApp.widgets, wtfApp.config) - wtfApp.focusTracker = wtf.NewFocusTracker(wtfApp.app, wtfApp.widgets, wtfApp.config) + wtfApp.focusTracker = NewFocusTracker(wtfApp.app, wtfApp.widgets, wtfApp.config) wtfApp.pages.AddPage("grid", wtfApp.display.Grid, true, true) wtfApp.app.SetRoot(wtfApp.pages, true) diff --git a/main.go b/main.go index fd1cdb02..381820e2 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,7 @@ func main() { wtfApp := app.NewWtfApp(tviewApp, config, flags.Config, flags.HasCustomConfig()) wtfApp.Start() - if err := wtfApp.App().Run(); err != nil { + if err := tviewApp.Run(); err != nil { fmt.Printf("\n%s %v\n", aurora.Red("ERROR"), err) os.Exit(1) } From fccd062b85bc84a4550972bb4256244381076b97 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 28 Jul 2019 06:30:18 -0700 Subject: [PATCH 8/8] Move the ModuleValidator from /wtf to /app --- app/error_messages.go | 19 +++++++++++++++++++ .../module_validator.go | 14 +++++++++++--- app/wtf_app.go | 4 +++- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 app/error_messages.go rename wtf/widget_validation.go => app/module_validator.go (72%) diff --git a/app/error_messages.go b/app/error_messages.go new file mode 100644 index 00000000..76f0e602 --- /dev/null +++ b/app/error_messages.go @@ -0,0 +1,19 @@ +package app + +// This file contains the error messages that get written to the terminal when +// something goes wrong with the configuration process. +// +// As a general rule, if one of these has to be shown the app should then die +// via os.Exit(1) + +import ( + "fmt" + + "github.com/logrusorgru/aurora" +) + +/* -------------------- Unexported Functions -------------------- */ + +func displayError(err error) { + fmt.Printf("%s %s\n\n", aurora.Red("Error:"), err.Error()) +} diff --git a/wtf/widget_validation.go b/app/module_validator.go similarity index 72% rename from wtf/widget_validation.go rename to app/module_validator.go index 36f03123..36ba6275 100644 --- a/wtf/widget_validation.go +++ b/app/module_validator.go @@ -1,15 +1,23 @@ -package wtf +package app import ( "fmt" "os" "github.com/logrusorgru/aurora" + "github.com/wtfutil/wtf/wtf" ) -// ValidateWidgets rolls through all the enabled widgets and looks for configuration errors. +type ModuleValidator struct{} + +func NewModuleValidator() *ModuleValidator { + val := &ModuleValidator{} + return val +} + +// Validate rolls through all the enabled widgets and looks for configuration errors. // If it finds any it stringifies them, writes them to the console, and kills the app gracefully -func ValidateWidgets(widgets []Wtfable) { +func (val *ModuleValidator) Validate(widgets []wtf.Wtfable) { var errStr string hasErrors := false diff --git a/app/wtf_app.go b/app/wtf_app.go index dca52b19..99235c56 100644 --- a/app/wtf_app.go +++ b/app/wtf_app.go @@ -24,6 +24,7 @@ type WtfApp struct { focusTracker FocusTracker isCustomConfig bool pages *tview.Pages + validator *ModuleValidator widgets []wtf.Wtfable } @@ -40,12 +41,13 @@ func NewWtfApp(app *tview.Application, config *config.Config, configFilePath str wtfApp.widgets = maker.MakeWidgets(wtfApp.app, wtfApp.pages, wtfApp.config) wtfApp.display = NewDisplay(wtfApp.widgets, wtfApp.config) wtfApp.focusTracker = NewFocusTracker(wtfApp.app, wtfApp.widgets, wtfApp.config) + wtfApp.validator = NewModuleValidator() wtfApp.pages.AddPage("grid", wtfApp.display.Grid, true, true) wtfApp.app.SetRoot(wtfApp.pages, true) wtfApp.app.SetInputCapture(wtfApp.keyboardIntercept) - wtf.ValidateWidgets(wtfApp.widgets) + wtfApp.validator.Validate(wtfApp.widgets) return &wtfApp }