pominal/pominal.go
Dan Anglin 19eb9b20be
feat: support configuring Pominal with config file
This commit adds a feature to load Pominal
configuration from a JSON configuration file.

A new data type called PominalConfig was created for
the purpose of configuring Pominal.
A new factory function called newPominalConfig was
created which takes the path to the JSON configuration
file and the flag overrides and generates the new
PominalConfig value.

Here, the JSON config is parsed to create the initial
configuration and any flag overrides can override
the corresponding fields in the PominalConfig object.
Currently only the sessions times and the maximum work
sessions per Pominal cycle have flag overrides.

Additionally users can now configure custom notification
messages for each session type from the configuration file.
There are currently no flag overrides for these.

This commit resolves dananglin/Pominal#1
2020-02-13 12:42:55 +00:00

231 lines
6.2 KiB
Go

/*
Pominal
Copyright (C) 2020 Daniel Anglin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"time"
"github.com/rivo/tview"
)
type Pominal struct {
// workSession is a counter for work sessions.
workSession int
// maxWorkSessions sets the number of work sessions to complete
// before running the countdown timer for each long break.
maxWorkSessions int
// cycle represents a Pominal cycle. A Pominal cycle increments after
// every long break.
cycle int
// countdown is the decrementing counter for Pominal.
countdown float64
// work is the time (represented in seconds) for the work sessions.
work float64
// shortBreak is the time (represented in seconds)
// for the short break sessions.
shortBreak float64
// longBreak is the time (represented in seconds)
//for the long break sessions.
longBreak float64
// workNotificationMsg is the desktop notification
// message when a work session has started.
workNotificationMsg string
// shortBreakNotificationMsg is the desktop notification
// message when a short break session has started.
shortBreakNotificationMsg string
// longBreakNotificationMsg is the desktop notification
// message when a long break session has started.
longBreakNotificationMsg string
// currentNotificationMsg is used for desktop notifications.
// This will be equal to workNotificationMsg, shortBreakNotificationMsg
// or longBreakNotificationMsg depending on the session in progress.
currentNotificationMsg string
// label labels Pominal based on the session.
label string
// stopChan is a no-data channel used to stop Pominal.
stopChan chan struct{}
}
// NewPominal creates a new pominal instance
func newPominal(config PominalConfig) (Pominal, error) {
initWorkSession := 1
initCycle := 1
maxWorkSessions := setMaxWorkSessions(config.MaxWorkSessions)
work, err := setSessionTime(config.Sessions.Work.Duration, defaultWorkTime)
if err != nil {
return Pominal{}, err
}
shortBreak, err := setSessionTime(config.Sessions.ShortBreak.Duration, defaultShortBreakTime)
if err != nil {
return Pominal{}, err
}
longBreak, err := setSessionTime(config.Sessions.LongBreak.Duration, defaultLongBreakTime)
if err != nil {
return Pominal{}, err
}
p := Pominal{
workSession: initWorkSession,
maxWorkSessions: maxWorkSessions,
cycle: initCycle,
work: work,
shortBreak: shortBreak,
longBreak: longBreak,
workNotificationMsg: config.Sessions.Work.NotificationMessage,
shortBreakNotificationMsg: config.Sessions.ShortBreak.NotificationMessage,
longBreakNotificationMsg: config.Sessions.LongBreak.NotificationMessage,
stopChan: nil,
}
return p, nil
}
// Run Pominal
func (p *Pominal) Run(infoUI, timerUI *tview.TextView) {
p.stopChan = make(chan struct{})
p.UpdateSession()
drawInfo(infoUI, p.cycle, p.workSession, p.maxWorkSessions, p.label)
// notifier is used for desktop notifications
notifier := initNotifier()
t := time.NewTicker(time.Second)
infinite:
for {
select {
case <-p.stopChan:
t.Stop()
break infinite
case <-t.C:
p.countdown--
if p.countdown < 0 {
t.Stop()
p.UpdateSession()
drawInfo(
infoUI,
p.cycle,
p.workSession,
p.maxWorkSessions,
p.label,
)
desktopAlert(p.label, p.currentNotificationMsg, notifier)
time.Sleep(time.Second)
t = time.NewTicker(time.Second)
} else {
drawTimer(timerUI, p.countdown)
}
}
}
}
// Stop sends a signal to the Run method to stop the timer.
// Afterwards the process returns from the Run method.
func (p *Pominal) Stop() {
close(p.stopChan)
}
// UpdateSession resets Pominal's countdown
// based on the next session. If a 'short break'
// session is over the work session counter is incremented.
// If a 'long break' session is over then the Pominal cycle
// is incremented and the work session counter is reset
// to 1.
func (p *Pominal) UpdateSession() {
switch p.label {
case workTimerLabel:
if p.workSession >= p.maxWorkSessions {
p.countdown = p.longBreak
p.label = longBreakTimerLabel
p.currentNotificationMsg = p.longBreakNotificationMsg
} else {
p.countdown = p.shortBreak
p.label = shortBreakTimerLabel
p.currentNotificationMsg = p.shortBreakNotificationMsg
}
case shortBreakTimerLabel:
p.workSession++
p.countdown = p.work
p.label = workTimerLabel
p.currentNotificationMsg = p.workNotificationMsg
case longBreakTimerLabel:
p.cycle++
p.workSession = 1
p.countdown = p.work
p.label = workTimerLabel
p.currentNotificationMsg = p.workNotificationMsg
default:
p.countdown = p.work
p.label = workTimerLabel
p.currentNotificationMsg = p.workNotificationMsg
}
}
// setSessionTime parses the configured session time and
// returns the session time represented in seconds.
// If the session time is unconfigured then the default for
// that particular session is returned.
// If the value is less than the minimum session time
// then the minimum is returned instead.
// An error is returned if the configured session time cannot be parsed.
func setSessionTime(s, defaultSessionTime string) (float64, error) {
if len(s) == 0 {
s = defaultSessionTime
}
sTime, err := time.ParseDuration(s)
if err != nil {
return float64(0), err
}
sTimeInSecs := sTime.Seconds()
if sTimeInSecs < minimumSessionTime {
sTimeInSecs = minimumSessionTime
}
return sTimeInSecs, nil
}
// setMaxWorkSessions checks and returns the configured
// number of work session per Pominal cycle.
// The default value is returned if the argument is 0
// or less.
func setMaxWorkSessions(w int) int {
if w <= 0 {
w = defaultMaxWorkSessions
}
return w
}