From 50f3ca779f510f61390ca820ffe622fc9f1eca22 Mon Sep 17 00:00:00 2001 From: Jan Hensel Date: Tue, 9 Jul 2024 16:54:25 +0200 Subject: [PATCH] refactor: Fix a bunch of warnings --- internal/control/cli/cli.go | 1 + internal/control/cli/controller.go | 378 ++++++++++++++--------------- internal/control/cli/timesheet.go | 3 +- internal/control/cli/tui.go | 2 +- internal/control/data.go | 23 +- internal/control/edit/mode.go | 4 - internal/model/date.go | 79 +++--- internal/model/date_test.go | 2 +- internal/model/day.go | 2 - internal/model/event.go | 5 - internal/model/goal.go | 8 +- internal/ui/panes/weather_pane.go | 2 +- internal/weather/weather.go | 72 +++--- 13 files changed, 288 insertions(+), 293 deletions(-) diff --git a/internal/control/cli/cli.go b/internal/control/cli/cli.go index d6b90d49..4a2e1137 100644 --- a/internal/control/cli/cli.go +++ b/internal/control/cli/cli.go @@ -1,3 +1,4 @@ +// Package cli provides the command-line interface for dayplan. package cli type CommandLineOpts struct { diff --git a/internal/control/cli/controller.go b/internal/control/cli/controller.go index 267c9fe8..af1e2b22 100644 --- a/internal/control/cli/controller.go +++ b/internal/control/cli/controller.go @@ -30,19 +30,19 @@ import ( ) // TODO: this absolutely does not belong here -func (t *Controller) getDayFromFileHandler(date model.Date) *model.Day { - t.fhMutex.RLock() - fh, ok := t.FileHandlers[date] - t.fhMutex.RUnlock() +func (c *Controller) getDayFromFileHandler(date model.Date) *model.Day { + c.fhMutex.RLock() + fh, ok := c.FileHandlers[date] + c.fhMutex.RUnlock() if ok { - tmp := fh.Read(t.data.Categories) + tmp := fh.Read(c.data.Categories) return tmp } else { - newHandler := storage.NewFileHandler(t.data.EnvData.BaseDirPath + "/days/" + date.ToString()) - t.fhMutex.Lock() - t.FileHandlers[date] = newHandler - t.fhMutex.Unlock() - tmp := newHandler.Read(t.data.Categories) + newHandler := storage.NewFileHandler(c.data.EnvData.BaseDirPath + "/days/" + date.ToString()) + c.fhMutex.Lock() + c.FileHandlers[date] = newHandler + c.fhMutex.Unlock() + tmp := newHandler.Read(c.data.Categories) return tmp } } @@ -54,7 +54,7 @@ type Controller struct { fhMutex sync.RWMutex FileHandlers map[model.Date]*storage.FileHandler - controllerEvents chan ControllerEvent + controllerEvents chan controllerEvent // TODO: remove, obviously tmpStatusYOffsetGetter func() int @@ -497,7 +497,7 @@ func NewController( }) go func() { <-taskEditorDone - controller.controllerEvents <- ControllerEventTaskEditorExit + controller.controllerEvents <- controllerEventTaskEditorExit }() } tasksInputTree, err := input.ConstructInputTree( @@ -782,7 +782,7 @@ func NewController( }) go func() { <-eventEditorDone - controller.controllerEvents <- ControllerEventEventEditorExit + controller.controllerEvents <- controllerEventEventEditorExit }() }), "o": action.NewSimple(func() string { return "add event after selected" }, func() { @@ -930,9 +930,8 @@ func NewController( ) if err != nil { panic(err) - } else { - ensureEventsPaneTimestampVisible(controller.data.GetCurrentDay().Current.End) } + ensureEventsPaneTimestampVisible(controller.data.GetCurrentDay().Current.End) }), "k": action.NewSimple(func() string { return "move up" }, func() { err := controller.data.GetCurrentDay().MoveEventsPushingBy( @@ -942,9 +941,8 @@ func NewController( ) if err != nil { panic(err) - } else { - ensureEventsPaneTimestampVisible(controller.data.GetCurrentDay().Current.Start) } + ensureEventsPaneTimestampVisible(controller.data.GetCurrentDay().Current.Start) }), "M": action.NewSimple(func() string { return "exit move mode" }, func() { dayEventsPane.PopModalOverlay(); controller.data.EventEditMode = edit.EventEditModeNormal }), "": action.NewSimple(func() string { return "exit move mode" }, func() { dayEventsPane.PopModalOverlay(); controller.data.EventEditMode = edit.EventEditModeNormal }), @@ -1140,7 +1138,7 @@ func NewController( var helpContentRegister func() rootPaneInputTree, err := input.ConstructInputTree( map[input.Keyspec]action.Action{ - "q": action.NewSimple(func() string { return "exit program (unsaved progress is lost)" }, func() { controller.controllerEvents <- ControllerEventExit }), + "q": action.NewSimple(func() string { return "exit program (unsaved progress is lost)" }, func() { controller.controllerEvents <- controllerEventExit }), "P": action.NewSimple(func() string { return "show debug perf pane" }, func() { controller.data.ShowDebug = !controller.data.ShowDebug }), "S": action.NewSimple(func() string { return "open summary" }, func() { controller.data.ShowSummary = true }), "E": action.NewSimple(func() string { return "toggle log" }, func() { controller.data.ShowLog = !controller.data.ShowLog }), @@ -1380,7 +1378,7 @@ func NewController( case ui.ViewDay: dateString = controller.data.CurrentDate.ToString() case ui.ViewWeek: - start, end := controller.data.CurrentDate.Week() + start, end := controller.data.CurrentDate.WeekBounds() dateString = fmt.Sprintf("week %s..%s", start.ToString(), end.ToString()) case ui.ViewMonth: dateString = fmt.Sprintf("%s %d", controller.data.CurrentDate.ToGotime().Month().String(), controller.data.CurrentDate.Year) @@ -1395,7 +1393,7 @@ func NewController( return result case ui.ViewWeek: result := make([]*model.Day, 7) - start, end := controller.data.CurrentDate.Week() + start, end := controller.data.CurrentDate.WeekBounds() for current, i := start, 0; current != end.Next(); current = current.Next() { result[i] = controller.data.Days.GetDay(current) i++ @@ -1457,13 +1455,13 @@ func NewController( controller.data.EventEditMode = edit.EventEditModeNormal coordinatesProvided := (envData.Latitude != "" && envData.Longitude != "") - owmApiKeyProvided := (envData.OwmApiKey != "") + owmAPIKeyProvided := (envData.OWMAPIKey != "") // intialize weather handler if geographic location and api key provided - if coordinatesProvided && owmApiKeyProvided { - controller.data.Weather = *weather.NewHandler(envData.Latitude, envData.Longitude, envData.OwmApiKey) + if coordinatesProvided && owmAPIKeyProvided { + controller.data.Weather = *weather.NewHandler(envData.Latitude, envData.Longitude, envData.OWMAPIKey) } else { - if !owmApiKeyProvided { + if !owmAPIKeyProvided { log.Error().Msg("no OWM API key provided -> no weather data") } if !coordinatesProvided { @@ -1523,67 +1521,61 @@ func NewController( return &controller, nil } -func (t *Controller) ScrollUp(by int) { +// ScrollUp scrolls the main timeline view up by the given number of rows. +func (c *Controller) ScrollUp(by int) { eventviewTopRow := 0 - if t.data.MainTimelineViewParams.ScrollOffset-by >= eventviewTopRow { - t.data.MainTimelineViewParams.ScrollOffset -= by + if c.data.MainTimelineViewParams.ScrollOffset-by >= eventviewTopRow { + c.data.MainTimelineViewParams.ScrollOffset -= by } else { - t.ScrollTop() + c.ScrollTop() } } -func (t *Controller) ScrollDown(by int) { - eventviewBottomRow := t.tmpStatusYOffsetGetter() - if t.data.MainTimelineViewParams.ScrollOffset+by+eventviewBottomRow <= (24 * t.data.MainTimelineViewParams.NRowsPerHour) { - t.data.MainTimelineViewParams.ScrollOffset += by +// ScrollDown scrolls the main timeline view down by the given number of rows. +func (c *Controller) ScrollDown(by int) { + eventviewBottomRow := c.tmpStatusYOffsetGetter() + if c.data.MainTimelineViewParams.ScrollOffset+by+eventviewBottomRow <= (24 * c.data.MainTimelineViewParams.NRowsPerHour) { + c.data.MainTimelineViewParams.ScrollOffset += by } else { - t.ScrollBottom() + c.ScrollBottom() } } -func (t *Controller) ScrollTop() { - t.data.MainTimelineViewParams.ScrollOffset = 0 -} - -func (t *Controller) ScrollBottom() { - eventviewBottomRow := t.tmpStatusYOffsetGetter() - t.data.MainTimelineViewParams.ScrollOffset = 24*t.data.MainTimelineViewParams.NRowsPerHour - eventviewBottomRow +// ScrollTop scrolls the main timeline view to the top. +func (c *Controller) ScrollTop() { + c.data.MainTimelineViewParams.ScrollOffset = 0 } -func (t *Controller) abortEdit() { - t.data.MouseEditState = edit.MouseEditStateNone - t.data.MouseEditedEvent = nil - if t.data.EventEditor != nil { - t.data.EventEditor.Quit() - t.data.EventEditor = nil - } - t.rootPane.PopSubpane() +// ScrollBottom scrolls the main timeline view to the bottom. +func (c *Controller) ScrollBottom() { + eventviewBottomRow := c.tmpStatusYOffsetGetter() + c.data.MainTimelineViewParams.ScrollOffset = 24*c.data.MainTimelineViewParams.NRowsPerHour - eventviewBottomRow } -func (t *Controller) endEdit() { - t.data.MouseEditState = edit.MouseEditStateNone - t.data.MouseEditedEvent = nil - if t.data.EventEditor != nil { - t.data.EventEditor.Write() - t.data.EventEditor.Quit() - t.data.EventEditor = nil +func (c *Controller) endEdit() { + c.data.MouseEditState = edit.MouseEditStateNone + c.data.MouseEditedEvent = nil + if c.data.EventEditor != nil { + c.data.EventEditor.Write() + c.data.EventEditor.Quit() + c.data.EventEditor = nil } - t.data.GetCurrentDay().UpdateEventOrder() - t.rootPane.PopSubpane() // TODO: this will need to be re-done conceptually + c.data.GetCurrentDay().UpdateEventOrder() + c.rootPane.PopSubpane() // TODO: this will need to be re-done conceptually } -func (t *Controller) startMouseMove(eventsInfo *ui.EventsPanePositionInfo) { - t.data.MouseEditState = edit.MouseEditStateMoving - t.data.MouseEditedEvent = eventsInfo.Event - t.data.CurrentMoveStartingOffsetMinutes = eventsInfo.Event.Start.DurationInMinutesUntil(eventsInfo.Time) +func (c *Controller) startMouseMove(eventsInfo *ui.EventsPanePositionInfo) { + c.data.MouseEditState = edit.MouseEditStateMoving + c.data.MouseEditedEvent = eventsInfo.Event + c.data.CurrentMoveStartingOffsetMinutes = eventsInfo.Event.Start.DurationInMinutesUntil(eventsInfo.Time) } -func (t *Controller) startMouseResize(eventsInfo *ui.EventsPanePositionInfo) { - t.data.MouseEditState = edit.MouseEditStateResizing - t.data.MouseEditedEvent = eventsInfo.Event +func (c *Controller) startMouseResize(eventsInfo *ui.EventsPanePositionInfo) { + c.data.MouseEditState = edit.MouseEditStateResizing + c.data.MouseEditedEvent = eventsInfo.Event } -func (t *Controller) startMouseEventCreation(info *ui.EventsPanePositionInfo) { +func (c *Controller) startMouseEventCreation(info *ui.EventsPanePositionInfo) { // find out cursor time start := info.Time @@ -1591,52 +1583,52 @@ func (t *Controller) startMouseEventCreation(info *ui.EventsPanePositionInfo) { // create event at time with cat etc. e := model.Event{} - e.Cat = t.data.CurrentCategory + e.Cat = c.data.CurrentCategory e.Name = "" e.Start = start e.End = start.OffsetMinutes(+10) - err := t.data.GetCurrentDay().AddEvent(&e) + err := c.data.GetCurrentDay().AddEvent(&e) if err != nil { log.Error().Err(err).Interface("event", e).Msg("error occurred adding event") } else { - t.data.MouseEditedEvent = &e - t.data.MouseEditState = edit.MouseEditStateResizing + c.data.MouseEditedEvent = &e + c.data.MouseEditState = edit.MouseEditStateResizing } } -func (t *Controller) goToDay(newDate model.Date) { +func (c *Controller) goToDay(newDate model.Date) { log.Debug().Str("new-date", newDate.ToString()).Msg("going to new date") - t.data.CurrentDate = newDate - t.loadDaysForView(t.data.ActiveView()) + c.data.CurrentDate = newDate + c.loadDaysForView(c.data.ActiveView()) } -func (t *Controller) goToPreviousDay() { - prevDay := t.data.CurrentDate.Prev() - t.goToDay(prevDay) +func (c *Controller) goToPreviousDay() { + prevDay := c.data.CurrentDate.Prev() + c.goToDay(prevDay) } -func (t *Controller) goToNextDay() { - nextDay := t.data.CurrentDate.Next() - t.goToDay(nextDay) +func (c *Controller) goToNextDay() { + nextDay := c.data.CurrentDate.Next() + c.goToDay(nextDay) } // Loads the requested date's day from its file handler, if it has // not already been loaded. -func (t *Controller) loadDay(date model.Date) { - if !t.data.Days.HasDay(date) { +func (c *Controller) loadDay(date model.Date) { + if !c.data.Days.HasDay(date) { // load file - newDay := t.getDayFromFileHandler(date) + newDay := c.getDayFromFileHandler(date) if newDay == nil { panic("newDay nil?!") } var suntimes model.SunTimes - coordinatesProvided := (t.data.EnvData.Latitude != "" && t.data.EnvData.Longitude != "") + coordinatesProvided := (c.data.EnvData.Latitude != "" && c.data.EnvData.Longitude != "") if coordinatesProvided { - latF, parseErrLat := strconv.ParseFloat(t.data.EnvData.Latitude, 64) - lonF, parseErrLon := strconv.ParseFloat(t.data.EnvData.Longitude, 64) + latF, parseErrLat := strconv.ParseFloat(c.data.EnvData.Latitude, 64) + lonF, parseErrLon := strconv.ParseFloat(c.data.EnvData.Longitude, 64) if parseErrLon != nil || parseErrLat != nil { log.Error(). Interface("lon-parse-error", parseErrLon). @@ -1647,7 +1639,7 @@ func (t *Controller) loadDay(date model.Date) { } } - t.data.Days.AddDay(date, newDay, &suntimes) + c.data.Days.AddDay(date, newDay, &suntimes) } } @@ -1656,27 +1648,27 @@ func (t *Controller) loadDay(date model.Date) { // first to last day of the month. // Warning: does not guarantee days will be loaded (non-nil) after // this returns. -func (t *Controller) loadDaysForView(view ui.ActiveView) { +func (c *Controller) loadDaysForView(view ui.ActiveView) { switch view { case ui.ViewDay: - t.loadDay(t.data.CurrentDate) + c.loadDay(c.data.CurrentDate) case ui.ViewWeek: { - monday, sunday := t.data.CurrentDate.Week() + monday, sunday := c.data.CurrentDate.WeekBounds() for current := monday; current != sunday.Next(); current = current.Next() { go func(d model.Date) { - t.loadDay(d) - t.controllerEvents <- ControllerEventRender + c.loadDay(d) + c.controllerEvents <- controllerEventRender }(current) } } case ui.ViewMonth: { - first, last := t.data.CurrentDate.MonthBounds() + first, last := c.data.CurrentDate.MonthBounds() for current := first; current != last.Next(); current = current.Next() { go func(d model.Date) { - t.loadDay(d) - t.controllerEvents <- ControllerEventRender + c.loadDay(d) + c.controllerEvents <- controllerEventRender }(current) } } @@ -1685,26 +1677,26 @@ func (t *Controller) loadDaysForView(view ui.ActiveView) { } } -func (t *Controller) writeModel() { +func (c *Controller) writeModel() { go func() { - t.fhMutex.RLock() - t.FileHandlers[t.data.CurrentDate].Write(t.data.GetCurrentDay()) - t.fhMutex.RUnlock() + c.fhMutex.RLock() + c.FileHandlers[c.data.CurrentDate].Write(c.data.GetCurrentDay()) + c.fhMutex.RUnlock() }() } -func (t *Controller) updateCursorPos(x, y int) { - t.data.CursorPos.X, t.data.CursorPos.Y = x, y +func (c *Controller) updateCursorPos(x, y int) { + c.data.CursorPos.X, c.data.CursorPos.Y = x, y } -func (t *Controller) handleMouseNoneEditEvent(e *tcell.EventMouse) { - t.data.MouseMode = true +func (c *Controller) handleMouseNoneEditEvent(e *tcell.EventMouse) { + c.data.MouseMode = true // get new position x, y := e.Position() - t.updateCursorPos(x, y) + c.updateCursorPos(x, y) - positionInfo := t.rootPane.GetPositionInfo(x, y) + positionInfo := c.rootPane.GetPositionInfo(x, y) if positionInfo == nil { return } @@ -1717,17 +1709,17 @@ func (t *Controller) handleMouseNoneEditEvent(e *tcell.EventMouse) { case *ui.WeatherPanePositionInfo: switch buttons { case tcell.WheelUp: - t.ScrollUp(1) + c.ScrollUp(1) case tcell.WheelDown: - t.ScrollDown(1) + c.ScrollDown(1) } case *ui.TimelinePanePositionInfo: switch buttons { case tcell.WheelUp: - t.ScrollUp(1) + c.ScrollUp(1) case tcell.WheelDown: - t.ScrollDown(1) + c.ScrollDown(1) } case *ui.EventsPanePositionInfo: @@ -1736,11 +1728,11 @@ func (t *Controller) handleMouseNoneEditEvent(e *tcell.EventMouse) { // if button clicked, handle switch buttons { case tcell.Button3: - t.data.GetCurrentDay().RemoveEvent(eventsInfo.Event) + c.data.GetCurrentDay().RemoveEvent(eventsInfo.Event) case tcell.Button2: event := eventsInfo.Event if event != nil && eventsInfo.Time.IsAfter(event.Start) { - t.data.GetCurrentDay().SplitEvent(event, eventsInfo.Time) + c.data.GetCurrentDay().SplitEvent(event, eventsInfo.Time) } case tcell.Button1: @@ -1749,20 +1741,20 @@ func (t *Controller) handleMouseNoneEditEvent(e *tcell.EventMouse) { // creation, resizing or moving switch eventsInfo.EventBoxPart { case ui.EventBoxNowhere: - t.startMouseEventCreation(eventsInfo) + c.startMouseEventCreation(eventsInfo) case ui.EventBoxBottomRight: - t.startMouseResize(eventsInfo) + c.startMouseResize(eventsInfo) case ui.EventBoxInterior: - t.startMouseMove(eventsInfo) + c.startMouseMove(eventsInfo) case ui.EventBoxTopEdge: log.Info().Msgf("would construct editor here, once the programmer has figured out how to do so correctly") } case tcell.WheelUp: - t.ScrollUp(1) + c.ScrollUp(1) case tcell.WheelDown: - t.ScrollDown(1) + c.ScrollDown(1) } @@ -1772,14 +1764,14 @@ func (t *Controller) handleMouseNoneEditEvent(e *tcell.EventMouse) { case tcell.Button1: cat := toolsInfo.Category if cat != nil { - t.data.CurrentCategory = *cat + c.data.CurrentCategory = *cat } } } } -func (t *Controller) handleMouseResizeEditEvent(ev tcell.Event) { +func (c *Controller) handleMouseResizeEditEvent(ev tcell.Event) { switch e := ev.(type) { case *tcell.EventMouse: x, y := e.Position() @@ -1788,25 +1780,25 @@ func (t *Controller) handleMouseResizeEditEvent(ev tcell.Event) { switch buttons { case tcell.Button1: - cursorTime := t.timestampGuesser(x, y) - visualCursorTime := cursorTime.OffsetMinutes(int(t.data.MainTimelineViewParams.DurationOfHeight(1) / time.Minute)) - event := t.data.MouseEditedEvent + cursorTime := c.timestampGuesser(x, y) + visualCursorTime := cursorTime.OffsetMinutes(int(c.data.MainTimelineViewParams.DurationOfHeight(1) / time.Minute)) + event := c.data.MouseEditedEvent var err error - err = t.data.GetCurrentDay().ResizeTo(event, visualCursorTime) + err = c.data.GetCurrentDay().ResizeTo(event, visualCursorTime) if err != nil { log.Warn().Err(err).Msg("unable to resize") } case tcell.ButtonNone: - t.endEdit() + c.endEdit() } - t.updateCursorPos(x, y) + c.updateCursorPos(x, y) } } -func (t *Controller) handleMouseMoveEditEvent(ev tcell.Event) { +func (c *Controller) handleMouseMoveEditEvent(ev tcell.Event) { switch e := ev.(type) { case *tcell.EventMouse: x, y := e.Position() @@ -1815,13 +1807,13 @@ func (t *Controller) handleMouseMoveEditEvent(ev tcell.Event) { switch buttons { case tcell.Button1: - cursorTime := t.timestampGuesser(x, y) - t.data.GetCurrentDay().MoveSingleEventTo(t.data.MouseEditedEvent, cursorTime.OffsetMinutes(-t.data.CurrentMoveStartingOffsetMinutes)) + cursorTime := c.timestampGuesser(x, y) + c.data.GetCurrentDay().MoveSingleEventTo(c.data.MouseEditedEvent, cursorTime.OffsetMinutes(-c.data.CurrentMoveStartingOffsetMinutes)) case tcell.ButtonNone: - t.endEdit() + c.endEdit() } - t.updateCursorPos(x, y) + c.updateCursorPos(x, y) } } @@ -1833,32 +1825,32 @@ func (c *Controller) updateWeather() { } else { log.Debug().Msg("successfully retrieved weather data") } - c.controllerEvents <- ControllerEventRender + c.controllerEvents <- controllerEventRender }() } -type ControllerEvent int +type controllerEvent int const ( - ControllerEventExit ControllerEvent = iota - ControllerEventRender - ControllerEventTaskEditorExit - ControllerEventEventEditorExit + controllerEventExit controllerEvent = iota + controllerEventRender + controllerEventTaskEditorExit + controllerEventEventEditorExit ) // Empties all render events from the channel. // Returns true, if an exit event was encountered so the caller // knows to exit. -func emptyRenderEvents(c chan ControllerEvent) bool { +func emptyRenderEvents(c chan controllerEvent) bool { for { select { case bufferedEvent := <-c: switch bufferedEvent { - case ControllerEventRender: + case controllerEventRender: { // dump extra render events } - case ControllerEventExit: + case controllerEventExit: return true } default: @@ -1867,64 +1859,60 @@ func emptyRenderEvents(c chan ControllerEvent) bool { } } -// Run -func (t *Controller) Run() { +// Run ... +func (c *Controller) Run() { log.Info().Msg("dayplan TUI started") - t.controllerEvents = make(chan ControllerEvent, 32) + c.controllerEvents = make(chan controllerEvent, 32) var wg sync.WaitGroup // Run the main render loop, that renders or exits when prompted accordingly wg.Add(1) go func() { defer wg.Done() - defer t.initializedScreen.Fini() - for { - - select { - case controllerEvent := <-t.controllerEvents: - switch controllerEvent { - case ControllerEventRender: - start := time.Now() - - // empty all further render events before rendering - exitEventEncounteredOnEmpty := emptyRenderEvents(t.controllerEvents) - // exit if an exit event was coming up - if exitEventEncounteredOnEmpty { - return - } - // render - t.rootPane.Draw() + defer c.initializedScreen.Fini() + for controllerEvent := range c.controllerEvents { + switch controllerEvent { + case controllerEventRender: + start := time.Now() + + // empty all further render events before rendering + exitEventEncounteredOnEmpty := emptyRenderEvents(c.controllerEvents) + // exit if an exit event was coming up + if exitEventEncounteredOnEmpty { + return + } + // render + c.rootPane.Draw() - end := time.Now() - t.data.RenderTimes.Add(uint64(end.Sub(start).Microseconds())) + end := time.Now() + c.data.RenderTimes.Add(uint64(end.Sub(start).Microseconds())) - case ControllerEventTaskEditorExit: - if t.data.TaskEditor == nil { - log.Warn().Msgf("got task editor exit event, but no task editor active; likely logic error") - } else { - t.data.TaskEditor = nil - t.rootPane.PopSubpane() - log.Debug().Msgf("removed (presumed) task-editor subpane from root") - go func() { t.controllerEvents <- ControllerEventRender }() - } + case controllerEventTaskEditorExit: + if c.data.TaskEditor == nil { + log.Warn().Msgf("got task editor exit event, but no task editor active; likely logic error") + } else { + c.data.TaskEditor = nil + c.rootPane.PopSubpane() + log.Debug().Msgf("removed (presumed) task-editor subpane from root") + go func() { c.controllerEvents <- controllerEventRender }() + } - case ControllerEventEventEditorExit: - if t.data.EventEditor == nil { - log.Warn().Msgf("got event editor exit event, but no event editor active; likely logic error") - } else { - t.data.EventEditor = nil - t.rootPane.PopSubpane() - log.Debug().Msgf("removed (presumed) event-editor subpane from root") - go func() { t.controllerEvents <- ControllerEventRender }() - } + case controllerEventEventEditorExit: + if c.data.EventEditor == nil { + log.Warn().Msgf("got event editor exit event, but no event editor active; likely logic error") + } else { + c.data.EventEditor = nil + c.rootPane.PopSubpane() + log.Debug().Msgf("removed (presumed) event-editor subpane from root") + go func() { c.controllerEvents <- controllerEventRender }() + } - case ControllerEventExit: - return + case controllerEventExit: + return - default: - log.Error().Interface("event", controllerEvent).Msgf("unhandled controller event") - } + default: + log.Error().Interface("event", controllerEvent).Msgf("unhandled controller event") } } }() @@ -1935,7 +1923,7 @@ func (t *Controller) Run() { now := time.Now() next := now.Round(1 * time.Minute).Add(1 * time.Minute) time.Sleep(time.Until(next)) - t.controllerEvents <- ControllerEventRender + c.controllerEvents <- controllerEventRender } }() @@ -1943,48 +1931,48 @@ func (t *Controller) Run() { // for a redraw (or program exit) after each event. go func() { for { - ev := t.screenEvents.PollEvent() + ev := c.screenEvents.PollEvent() start := time.Now() { switch e := ev.(type) { case *tcell.EventKey: - t.data.MouseMode = false - t.data.MouseEditState = edit.MouseEditStateNone + c.data.MouseMode = false + c.data.MouseEditState = edit.MouseEditStateNone key := input.KeyFromTcellEvent(e) - inputApplied := t.rootPane.ProcessInput(key) + inputApplied := c.rootPane.ProcessInput(key) if !inputApplied { log.Warn().Str("key", key.ToDebugString()).Msg("could not apply key input") } case *tcell.EventMouse: - t.data.MouseMode = true + c.data.MouseMode = true // get new position x, y := e.Position() - t.updateCursorPos(x, y) + c.updateCursorPos(x, y) - switch t.data.MouseEditState { + switch c.data.MouseEditState { case edit.MouseEditStateNone: - t.handleMouseNoneEditEvent(e) + c.handleMouseNoneEditEvent(e) case edit.MouseEditStateResizing: - t.handleMouseResizeEditEvent(ev) + c.handleMouseResizeEditEvent(ev) case edit.MouseEditStateMoving: - t.handleMouseMoveEditEvent(ev) + c.handleMouseMoveEditEvent(ev) } case *tcell.EventResize: - t.syncer.NeedsSync() + c.syncer.NeedsSync() } } end := time.Now() - t.data.EventProcessingTimes.Add(uint64(end.Sub(start).Microseconds())) + c.data.EventProcessingTimes.Add(uint64(end.Sub(start).Microseconds())) - t.controllerEvents <- ControllerEventRender + c.controllerEvents <- controllerEventRender } }() diff --git a/internal/control/cli/timesheet.go b/internal/control/cli/timesheet.go index efc41c33..8bee48d5 100644 --- a/internal/control/cli/timesheet.go +++ b/internal/control/cli/timesheet.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "io/ioutil" "log" "os" "regexp" @@ -58,7 +57,7 @@ func (command *TimesheetCommand) Execute(args []string) error { } // read config from file (for the category priorities) - yamlData, err := ioutil.ReadFile(envData.BaseDirPath + "/" + "config.yaml") + yamlData, err := os.ReadFile(envData.BaseDirPath + "/" + "config.yaml") if err != nil { panic(fmt.Sprintf("can't read config file: '%s'", err)) } diff --git a/internal/control/cli/tui.go b/internal/control/cli/tui.go index 2a83e0fb..e4038371 100644 --- a/internal/control/cli/tui.go +++ b/internal/control/cli/tui.go @@ -79,7 +79,7 @@ func (command *TUICommand) Execute(_ []string) error { } } - envData.OwmApiKey = os.Getenv("OWM_API_KEY") + envData.OWMAPIKey = os.Getenv("OWM_API_KEY") envData.Latitude = os.Getenv("LATITUDE") envData.Longitude = os.Getenv("LONGITUDE") diff --git a/internal/control/data.go b/internal/control/data.go index 3b480485..d836e9c5 100644 --- a/internal/control/data.go +++ b/internal/control/data.go @@ -12,14 +12,15 @@ import ( "github.com/ja-he/dayplan/internal/weather" ) +// EnvData represents the environment data. type EnvData struct { BaseDirPath string - OwmApiKey string + OWMAPIKey string Latitude string Longitude string } -// For a given active view, returns the 'previous', i.E. 'stepping +// PrevView returns the 'previous' view for a given active view, i.E. 'stepping // out' from an inner view to an outer one. // E.g.: Day -> Week -> Month func PrevView(current ui.ActiveView) ui.ActiveView { @@ -35,7 +36,7 @@ func PrevView(current ui.ActiveView) ui.ActiveView { } } -// For a given active view, returns the 'next', i.E. 'stepping into' +// Next view, for a given active view, returns the 'next', i.E. 'stepping into' // from an outer view to an inner one. // E.g.: Month -> Week -> Day func NextView(current ui.ActiveView) ui.ActiveView { @@ -51,20 +52,8 @@ func NextView(current ui.ActiveView) ui.ActiveView { } } -// Returns the active view name as a string. -func toString(av ui.ActiveView) string { - switch av { - case ui.ViewDay: - return "ui.ViewDay" - case ui.ViewWeek: - return "ui.ViewWeek" - case ui.ViewMonth: - return "ui.ViewMonth" - default: - return "unknown" - } -} - +// DayWithInfo represents a day with additional information, such as sunrise / +// sunset times. type DayWithInfo struct { Day *model.Day SunTimes *model.SunTimes diff --git a/internal/control/edit/mode.go b/internal/control/edit/mode.go index 2708c0f6..8fd8d48d 100644 --- a/internal/control/edit/mode.go +++ b/internal/control/edit/mode.go @@ -9,10 +9,6 @@ const ( MouseEditStateResizing ) -func (s MouseEditState) toString() string { - return "TODO" -} - type EventEditMode = int const ( diff --git a/internal/model/date.go b/internal/model/date.go index 50396598..a215e061 100644 --- a/internal/model/date.go +++ b/internal/model/date.go @@ -9,24 +9,28 @@ import ( "github.com/nathan-osman/go-sunrise" ) +// Date represents a date, i.e. a year, month and day. type Date struct { Year int Month int Day int } -type DayAndTime struct { +// DateAndTime represents a date and a time, a datetime. +type DateAndTime struct { Date Date Timestamp Timestamp } -func FromTime(t time.Time) *DayAndTime { - return &DayAndTime{ +// FromTime creates a DateAndTime from a time.Time. +func FromTime(t time.Time) *DateAndTime { + return &DateAndTime{ Date: Date{Year: t.Year(), Month: int(t.Month()), Day: t.Day()}, Timestamp: Timestamp{Hour: t.Hour(), Minute: t.Minute()}, } } +// Prev returns the previous date. func (d Date) Prev() Date { if d.Day == 1 { if d.Month == 1 { @@ -47,6 +51,7 @@ func (d Date) Prev() Date { return d } +// Next returns the next date. func (d Date) Next() Date { if d == d.GetLastOfMonth() { d.Day = 1 @@ -62,6 +67,7 @@ func (d Date) Next() Date { return d } +// Backward returns a date that is `by`-many days before the receiver. func (d Date) Backward(by int) Date { for i := 0; i < by; i++ { d = d.Prev() @@ -69,6 +75,7 @@ func (d Date) Backward(by int) Date { return d } +// Forward returns a date that is `by`-many days after the receiver. func (d Date) Forward(by int) Date { for i := 0; i < by; i++ { d = d.Next() @@ -76,10 +83,13 @@ func (d Date) Forward(by int) Date { return d } +// ToString returns the date as a string in the format "YYYY-MM-DD". func (d Date) ToString() string { return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) } +// Valid returns whether the date is valid. +// A date such as the 31st of February is invalid, for example. func (d Date) Valid() bool { // verify month if d.Month < 1 || @@ -95,6 +105,7 @@ func (d Date) Valid() bool { return true } +// FromString creates a date from a string in the format "YYYY-MM-DD". func FromString(s string) (Date, error) { var result Date var err error @@ -104,7 +115,7 @@ func FromString(s string) (Date, error) { var tmp Date if len(parsed) < 1 || len(parsed[0]) < 3 { - return result, fmt.Errorf("Not enough int matches in day string '%s'", s) + return result, fmt.Errorf("not enough int matches in day string '%s'", s) } year, errY := strconv.ParseInt(parsed[0][1], 10, 32) @@ -116,9 +127,9 @@ func FromString(s string) (Date, error) { case errY != nil: case errM != nil: case errD != nil: - err = fmt.Errorf("Could not convert string '%s' (assuming YYYY-MM-DD format) to integers", s) + err = fmt.Errorf("could not convert string '%s' (assuming YYYY-MM-DD format) to integers", s) case !tmp.Valid(): - err = fmt.Errorf("Day %s (from string '%s') not valid!", tmp.ToString(), s) + err = fmt.Errorf("day %s (from string '%s') not valid", tmp.ToString(), s) default: result.Day = int(day) result.Month = int(month) @@ -152,53 +163,53 @@ func (d Date) getFirstOfMonth() Date { } } -// Whether a date A is after a date B. -func (a Date) IsAfter(b Date) bool { +// IsAfter returns whether a date A is after a date B. +func (d Date) IsAfter(other Date) bool { switch { - case a.Year < b.Year: + case d.Year < other.Year: return false - case a.Year == b.Year: + case d.Year == other.Year: { switch { - case a.Month < b.Month: + case d.Month < other.Month: return false - case a.Month == b.Month: + case d.Month == other.Month: { switch { - case a.Day < b.Day: + case d.Day < other.Day: return false - case a.Day == b.Day: - { - } - case a.Day > b.Day: + case d.Day == other.Day: + return false + case d.Day > other.Day: return true } } - case a.Month > b.Month: + case d.Month > other.Month: return true } } - case a.Year > b.Year: + case d.Year > other.Year: return true } return false } -// Whether a date A is before a date B. -func (a Date) IsBefore(b Date) bool { - return b.IsAfter(a) && a != b +// IsBefore returns whether a date A is before a date B. +func (d Date) IsBefore(other Date) bool { + return other.IsAfter(d) && d != other } -// Returns the number of days from a date A until a date B is reached. +// DaysUntil returns the number of days from a date A until a date B is +// reached. // (e.g. from 2021-12-14 until 2021-12-19 -> 5 days) -// expects b not to be before a -func (a Date) DaysUntil(b Date) int { - if a.IsAfter(b) { +// expects `other` not to be before `d` +func (d Date) DaysUntil(other Date) int { + if d.IsAfter(other) { panic("DaysUntil arg error: a after b") } counter := 0 - for i := a; i != b; i = i.Next() { + for i := d; i != other; i = i.Next() { counter++ } @@ -223,13 +234,14 @@ func (d Date) isLeapYear() bool { return d.Year%4 == 0 && (!(d.Year%100 == 0) || d.Year%400 == 0) } +// Is returns whether the receiver is the same date as the given time. func (d Date) Is(t time.Time) bool { tYear, tMonth, tDay := t.Date() return tYear == d.Year && int(tMonth) == d.Month && tDay == d.Day } -// TODO: rename WeekBounds or similar -func (d Date) Week() (monday Date, sunday Date) { +// WeekBounds returns the monday and sunday of the week the receiver is in. +func (d Date) WeekBounds() (monday Date, sunday Date) { for d.ToWeekday() != time.Monday { d = d.Prev() } @@ -241,7 +253,7 @@ func (d Date) Week() (monday Date, sunday Date) { // // Index here means that 0 is Monday, 6 is Sunday. func (d Date) GetDayInWeek(index int) Date { - start, _ := d.Week() + start, _ := d.WeekBounds() return start.Forward(index) } @@ -253,6 +265,7 @@ func (d Date) GetDayInMonth(index int) Date { return start.Forward(index) } +// MonthBounds returns the first and last date of the month the receiver is in. func (d Date) MonthBounds() (first Date, last Date) { first = d.getFirstOfMonth() last = d.GetLastOfMonth() @@ -260,6 +273,7 @@ func (d Date) MonthBounds() (first Date, last Date) { return first, last } +// ToString returns the weekday as a string. func ToString(w time.Weekday) string { switch w { case time.Sunday: @@ -281,20 +295,25 @@ func ToString(w time.Weekday) string { } } +// ToWeekday returns the weekday of the receiver. func (d Date) ToWeekday() time.Weekday { t := time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, time.UTC) return t.Weekday() } +// ToGotime returns the date as a time.Time with the time set to midnight. func (d Date) ToGotime() time.Time { result := time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, time.Now().Location()) return result } +// SunTimes represents the sunrise and sunset times of a date. type SunTimes struct { Rise, Set Timestamp } +// GetSunTimes returns the sunrise and sunset times for the receiver-date at +// the given location. // Warning: slow (TODO) func (d Date) GetSunTimes(latitude, longitude float64) SunTimes { diff --git a/internal/model/date_test.go b/internal/model/date_test.go index 49fa3fe7..eaee5096 100644 --- a/internal/model/date_test.go +++ b/internal/model/date_test.go @@ -144,7 +144,7 @@ func TestWeek(t *testing.T) { { date := Date{2021, 11, 12} expStart, expEnd := Date{2021, 11, 8}, Date{2021, 11, 14} - resStart, resEnd := date.Week() + resStart, resEnd := date.WeekBounds() if resStart != expStart || resEnd != expEnd { log.Fatalf("%s is bounded by (%s,%s) not (%s,%s)", date.ToString(), expStart.ToString(), expEnd.ToString(), resStart.ToString(), resEnd.ToString()) } diff --git a/internal/model/day.go b/internal/model/day.go index 1b6feadf..4039cb01 100644 --- a/internal/model/day.go +++ b/internal/model/day.go @@ -100,7 +100,6 @@ func (day *Day) CurrentPrev() { if len(day.Events) > 0 { day.Current = day.Events[0] } - return } func (day *Day) CurrentNext() { @@ -117,7 +116,6 @@ func (day *Day) CurrentNext() { if len(day.Events) > 0 { day.Current = day.Events[0] } - return } func (day *Day) UpdateEventOrder() { diff --git a/internal/model/event.go b/internal/model/event.go index 2003e0bb..94a43fba 100644 --- a/internal/model/event.go +++ b/internal/model/event.go @@ -148,11 +148,6 @@ func (event *Event) CanBeResizedBy(delta int) bool { } } -func (e *Event) Snap(minuteResolution int) { - e.Start.Snap(minuteResolution) - e.End.Snap(minuteResolution) -} - // Whether one event A contains another B, i.E. // - B's start is _not before_ A's start and // - B's end is _not after_ A's end diff --git a/internal/model/goal.go b/internal/model/goal.go index 38fb3865..6aabbb07 100644 --- a/internal/model/goal.go +++ b/internal/model/goal.go @@ -47,10 +47,16 @@ func NewRangedGoalFromConfig(cfg []config.RangedGoal) (*RangedGoal, error) { for i := range cfg { start, err := FromString(cfg[i].Start) + if err != nil { + return nil, fmt.Errorf("error parsing start date of range no. %d: %w", i, err) + } end, err := FromString(cfg[i].End) + if err != nil { + return nil, fmt.Errorf("error parsing start date of range no. %d: %w", i, err) + } duration, err := time.ParseDuration(cfg[i].Time) if err != nil { - return nil, err + return nil, fmt.Errorf("error parsing duration of range no. %d: %w", i, err) } else { for j := 0; j < i; j++ { if !((start.IsBefore(result.Entries[j].Start) && end.IsBefore(result.Entries[j].Start)) || (start.IsAfter(result.Entries[j].End) && end.IsAfter(result.Entries[j].End))) { diff --git a/internal/ui/panes/weather_pane.go b/internal/ui/panes/weather_pane.go index 879fac36..7584894d 100644 --- a/internal/ui/panes/weather_pane.go +++ b/internal/ui/panes/weather_pane.go @@ -39,7 +39,7 @@ func (p *WeatherPane) Draw() { break } - index := model.DayAndTime{ + index := model.DateAndTime{ Date: *p.currentDate, Timestamp: timestamp, } diff --git a/internal/weather/weather.go b/internal/weather/weather.go index f9bcde2c..58f76622 100644 --- a/internal/weather/weather.go +++ b/internal/weather/weather.go @@ -1,9 +1,10 @@ +// Package weather provides a handler for fetching weather data from OpenWeatherMap. package weather import ( "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "sync" "time" @@ -11,39 +12,43 @@ import ( "github.com/ja-he/dayplan/internal/model" ) -type OwmWeather struct { - Id int `json:"id"` // 801, +// OWMWeather represents the weather data from OpenWeatherMap. +type OWMWeather struct { + ID int `json:"id"` // 801, Main string `json:"main"` // "Clouds", Description string `json:"description"` // "few clouds", Icon string `json:"icon"` // "02d" } -type OwmHourly struct { +// OWMHourly represents the hourly weather data from OpenWeatherMap. +type OWMHourly struct { Dt uint64 `json:"dt"` // 1630429200, Temp float64 `json:"temp"` // 290.85, - Feels_like float64 `json:"feels_like"` // 290.71, + FeelsLike float64 `json:"feels_like"` // 290.71, Pressure int `json:"pressure"` // 1021, Humidity int `json:"humidity"` // 78, - Dew_point float64 `json:"dew_point"` // 286.97, + DewPoint float64 `json:"dew_point"` // 286.97, Uvi float64 `json:"uvi"` // 0.24, Clouds int `json:"clouds"` // 20, Visibility int `json:"visibility"` // 10000, - Wind_speed float64 `json:"wind_speed"` // 4.01, - Wind_deg int `json:"wind_deg"` // 332, - Wind_gust float64 `json:"wind_gust"` // 6.66, - Weather []OwmWeather `json:"weather"` // + WindSpeed float64 `json:"wind_speed"` // 4.01, + WindDeg int `json:"wind_deg"` // 332, + WindGust float64 `json:"wind_gust"` // 6.66, + Weather []OWMWeather `json:"weather"` // Pop float64 `json:"pop"` // 0 (probability of precipitation) } -type OwmFull struct { - Lat float64 `json:"lat"` // 53.18, - Lon float64 `json:"lon"` // 8.6, - Timezone string `json:"timezone"` // "Europe/Berlin", - Timezone_offset int `json:"timezone_offset"` // 7200, - Hourly []OwmHourly `json:"hourly"` +// OWMFull represents the full weather data from OpenWeatherMap. +type OWMFull struct { + Lat float64 `json:"lat"` // 53.18, + Lon float64 `json:"lon"` // 8.6, + Timezone string `json:"timezone"` // "Europe/Berlin", + TimezoneOffset int `json:"timezone_offset"` // 7200, + Hourly []OWMHourly `json:"hourly"` } -type MyWeather struct { +// Weather represents the weather data. +type Weather struct { Info string TempC float64 Clouds int @@ -52,20 +57,23 @@ type MyWeather struct { PrecipitationProbability float64 } +// Handler is a handler of retrieved weather data and querying for it. type Handler struct { - Data map[model.DayAndTime]MyWeather + Data map[model.DateAndTime]Weather lat, lon string apiKey string mutex sync.Mutex queryCount int } +// NewHandler creates a new weather handler. func NewHandler(lat, lon, key string) *Handler { var h Handler h.lat, h.lon, h.apiKey = lat, lon, key return &h } +// Update updates the weather data. func (h *Handler) Update() error { // Check that we have the params we need to successfully query paramsProvided := (h.lat != "" && h.lon != "" && h.apiKey != "") @@ -76,8 +84,8 @@ func (h *Handler) Update() error { h.mutex.Lock() h.queryCount++ - owmdata, err := GetHourlyInfo(h.lat, h.lon, h.apiKey) - newData := GetWeather(&owmdata) + owmdata, err := getHourlyInfo(h.lat, h.lon, h.apiKey) + newData := convertHourlyDataToTimestamped(&owmdata) if h.Data == nil { h.Data = newData } else { @@ -89,26 +97,22 @@ func (h *Handler) Update() error { return err } -func (h *Handler) GetQueryCount() int { - return h.queryCount -} - func kelvinToCelsius(kelvin float64) (celsius float64) { return kelvin - 273.15 } -func GetWeather(data *[]OwmHourly) map[model.DayAndTime]MyWeather { - result := make(map[model.DayAndTime]MyWeather) +func convertHourlyDataToTimestamped(data *[]OWMHourly) map[model.DateAndTime]Weather { + result := make(map[model.DateAndTime]Weather) for i := range *data { hourly := (*data)[i] t := time.Unix(int64(hourly.Dt), 0) - result[*model.FromTime(t)] = MyWeather{ + result[*model.FromTime(t)] = Weather{ Info: hourly.Weather[0].Description, TempC: kelvinToCelsius(hourly.Temp), Clouds: hourly.Clouds, - WindSpeed: hourly.Wind_speed, + WindSpeed: hourly.WindSpeed, Humidity: hourly.Humidity, PrecipitationProbability: hourly.Pop, } @@ -117,24 +121,24 @@ func GetWeather(data *[]OwmHourly) map[model.DayAndTime]MyWeather { return result } -func GetHourlyInfo(lat, lon, apiKey string) ([]OwmHourly, error) { +func getHourlyInfo(lat, lon, apiKey string) ([]OWMHourly, error) { call := fmt.Sprintf("https://api.openweathermap.org/data/2.5/onecall?lat=%s&lon=%s&exclude=daily,minutely,current,alerts&appid=%s", lat, lon, apiKey) response, err := http.Get(call) if err != nil { - return make([]OwmHourly, 0), err + return make([]OWMHourly, 0), err } - body, err := ioutil.ReadAll(response.Body) + body, err := io.ReadAll(response.Body) if err != nil { - return make([]OwmHourly, 0), err + return make([]OWMHourly, 0), err } - data := OwmFull{} + data := OWMFull{} err = json.Unmarshal(body, &data) if err != nil { - return make([]OwmHourly, 0), err + return make([]OWMHourly, 0), err } return data.Hourly, nil