refactor: add lint test and refactor code.

- lint test using golangci-lint.
- lint test added as a job in the testing stage.
- code refactoring based on feedback.
This commit is contained in:
Dan Anglin 2020-01-24 09:51:21 +00:00
parent 0a2b9c483e
commit 99ebc86270
No known key found for this signature in database
GPG key ID: 7AC2B18EC1D09F27
9 changed files with 132 additions and 44 deletions

View file

@ -6,18 +6,28 @@ stages:
variables: variables:
CGO_ENABLED: 0 CGO_ENABLED: 0
.install-make:
before_script:
- apk add --no-cache make
test:unit: test:unit:
artifacts: artifacts:
expire_in: 30 minutes expire_in: 30 minutes
paths: paths:
- code-coverage.html - code-coverage.html
before_script: extends: .install-make
- apk add --no-cache make
image: golang:1.13.6-alpine image: golang:1.13.6-alpine
script: script:
- make test_cover_report - make test_cover_report
stage: test stage: test
test:lint:
extends: .install-make
image: golangci/golangci-lint:v1.23.1-alpine
script:
- make test_lint
stage: test
pages: pages:
artifacts: artifacts:
paths: paths:

75
.golangci.yml Normal file
View file

@ -0,0 +1,75 @@
---
run:
concurrency: 2
timeout: 1m
issues-exit-code: 1
tests: true
output:
format: colored-line-number
print-issues-lines: true
print-linter-name: true
linters-settings:
lll:
line-length: 140
linters:
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- funlen
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godox
- gofmt
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- maligned
- misspell
- nakedret
- prealloc
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- wsl
#disable:
disabe-all: false
fast: false
issues:
exclude-rules:
- path: version.go
linters:
- gochecknoglobals
- path: _test.go
linters:
- gomnd
- scopelint
- path: cases_test.go
linters:
- gochecknoglobals

View file

@ -12,6 +12,9 @@ test_unit:
test_cover_report: test_unit test_cover_report: test_unit
@go tool cover -html=cover.out -o code-coverage.html @go tool cover -html=cover.out -o code-coverage.html
test_lint:
@golangci-lint run --color always
build: build:
@go build -a -v -o $(BIN_FILE) @go build -a -v -o $(BIN_FILE)

View file

@ -28,35 +28,36 @@ import (
const iconPath string = "assets/icon/tomato.png" const iconPath string = "assets/icon/tomato.png"
var notifier *notificator.Notificator
// initNotifier initialises the new desktop notifier. // initNotifier initialises the new desktop notifier.
func initNotifier() { func initNotifier() *notificator.Notificator {
notifier = notificator.New(notificator.Options{ return notificator.New(notificator.Options{
DefaultIcon: getIconPath(), DefaultIcon: getNotificationIconPath(),
AppName: "Pominal", AppName: "Pominal",
}) })
} }
// getIconPath returns the absolute path of the tomoato icon // getNotificationIconPath returns the absolute path of the tomoato icon
// used for desktop notifications. // used for desktop notifications.
// If there is an error getting the path to the executing program // If there is an error getting the path to the executing program
// then an empty string is returned. // then an empty string is returned.
func getIconPath() string { func getNotificationIconPath() string {
var result string var result string
exe, err := os.Executable() exe, err := os.Executable()
if err != nil { if err != nil {
fmt.Printf("ERROR: Unable to determine path to this executable. %s", err.Error()) fmt.Printf("ERROR: Unable to determine path to this executable. %s", err.Error())
return result return result
} }
result = filepath.Dir(exe) + "/" + iconPath result = filepath.Dir(exe) + "/" + iconPath
return result return result
} }
// alert creates a new desktop notification. // alert creates a new desktop notification.
func alert(l string) { func desktopAlert(l string, notifier *notificator.Notificator) {
title := "Pominal notification." title := "Pominal notification."
text := l + " session has started." text := l + " session has started."
notifier.Push(title, text, "", notificator.UR_NORMAL)
_ = notifier.Push(title, text, "", notificator.UR_NORMAL)
} }

View file

@ -135,7 +135,7 @@ var sessionsTestCases = []struct {
}, },
{ {
name: "Test session 4", name: "Test session 4",
description: "When the second work session has finished and the long break has started because the maximum number of work sessions is reached.", description: "When the second work session has finished and the long break has started.",
expectedWorkSession: 2, expectedWorkSession: 2,
expectedCycle: 1, expectedCycle: 1,
expectedLabel: longBreakTimerLabel, expectedLabel: longBreakTimerLabel,

23
main.go
View file

@ -20,30 +20,27 @@ package main
import ( import (
"flag" "flag"
"fmt" "log"
"os" "os"
"time" "time"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
var ( func main() {
var (
workTime string workTime string
shortBreakTime string shortBreakTime string
longBreakTime string longBreakTime string
maxWorkSessions int maxWorkSessions int
printVersion bool printVersion bool
) )
func init() {
flag.StringVar(&workTime, "work", "25m", "sets the timer for your work session.") flag.StringVar(&workTime, "work", "25m", "sets the timer for your work session.")
flag.StringVar(&shortBreakTime, "short-break", "5m", "sets the timer for your short break.") flag.StringVar(&shortBreakTime, "short-break", "5m", "sets the timer for your short break.")
flag.StringVar(&longBreakTime, "long-break", "20m", "sets the timer for your long break.") flag.StringVar(&longBreakTime, "long-break", "20m", "sets the timer for your long break.")
flag.IntVar(&maxWorkSessions, "max-work-sessions", 4, "sets the maximum number of work cycles to complete before taking a long break.") flag.IntVar(&maxWorkSessions, "max-work-sessions", 4, "sets the maximum number of work cycles to complete before taking a long break.")
flag.BoolVar(&printVersion, "version", false, "print version and exit.") flag.BoolVar(&printVersion, "version", false, "print version and exit.")
}
func main() {
flag.Parse() flag.Parse()
if printVersion { if printVersion {
@ -51,24 +48,19 @@ func main() {
os.Exit(0) os.Exit(0)
} }
initNotifier()
w, err := time.ParseDuration(workTime) w, err := time.ParseDuration(workTime)
if err != nil { if err != nil {
fmt.Printf("ERROR: Unable to set the work timer. %s", err.Error()) log.Fatalf("ERROR: Unable to set the work timer. %s", err.Error())
os.Exit(1)
} }
s, err := time.ParseDuration(shortBreakTime) s, err := time.ParseDuration(shortBreakTime)
if err != nil { if err != nil {
fmt.Printf("ERROR: Unable to set the work timer. %s", err.Error()) log.Fatalf("ERROR: Unable to set the work timer. %s", err.Error())
os.Exit(1)
} }
l, err := time.ParseDuration(longBreakTime) l, err := time.ParseDuration(longBreakTime)
if err != nil { if err != nil {
fmt.Printf("ERROR: Unable to set the work timer. %s", err.Error()) log.Fatalf("ERROR: Unable to set the work timer. %s", err.Error())
os.Exit(1)
} }
pominal := NewPominal( pominal := NewPominal(
@ -84,6 +76,7 @@ func main() {
flex := newFlex(infoUI, timerUI) flex := newFlex(infoUI, timerUI)
go pominal.Run(infoUI, timerUI) go pominal.Run(infoUI, timerUI)
if err := app.SetRoot(flex, false).SetFocus(flex).Run(); err != nil { if err := app.SetRoot(flex, false).SetFocus(flex).Run(); err != nil {
panic(err) panic(err)
} }

View file

@ -65,11 +65,13 @@ type Pominal struct {
// NewPominal creates a new pominal instance // NewPominal creates a new pominal instance
func NewPominal(w, s, l float64, m int) Pominal { func NewPominal(w, s, l float64, m int) Pominal {
initWorkSession := 1
initCycle := 1
return Pominal{ return Pominal{
workSession: 1, workSession: initWorkSession,
maxWorkSessions: setMaxWorkSessions(m), maxWorkSessions: setMaxWorkSessions(m),
cycle: 1, cycle: initCycle,
work: setSessionTime(w), work: setSessionTime(w),
shortBreak: setSessionTime(s), shortBreak: setSessionTime(s),
longBreak: setSessionTime(l), longBreak: setSessionTime(l),
@ -79,11 +81,14 @@ func NewPominal(w, s, l float64, m int) Pominal {
// Run Pominal // Run Pominal
func (p *Pominal) Run(infoUI, timerUI *tview.TextView) { func (p *Pominal) Run(infoUI, timerUI *tview.TextView) {
p.stopChan = make(chan struct{}) p.stopChan = make(chan struct{})
p.UpdateSession() p.UpdateSession()
drawInfo(infoUI, p.cycle, p.workSession, p.maxWorkSessions, p.label) drawInfo(infoUI, p.cycle, p.workSession, p.maxWorkSessions, p.label)
t := time.NewTicker(1 * time.Second)
// notifier is used for desktop notifications
notifier := initNotifier()
t := time.NewTicker(time.Second)
infinite: infinite:
for { for {
@ -103,9 +108,9 @@ infinite:
p.maxWorkSessions, p.maxWorkSessions,
p.label, p.label,
) )
alert(p.label) desktopAlert(p.label, notifier)
time.Sleep(1 * time.Second) time.Sleep(time.Second)
t = time.NewTicker(1 * time.Second) t = time.NewTicker(time.Second)
} else { } else {
drawTimer(timerUI, p.countdown) drawTimer(timerUI, p.countdown)
} }
@ -158,6 +163,7 @@ func setSessionTime(s float64) float64 {
if s < min { if s < min {
return min return min
} }
return s return s
} }
@ -170,5 +176,6 @@ func setMaxWorkSessions(w int) int {
if w < min { if w < min {
return min return min
} }
return w return w
} }

View file

@ -71,9 +71,6 @@ func TestRun(t *testing.T) {
infoUI := tview.NewTextView() infoUI := tview.NewTextView()
timerUI := tview.NewTextView() timerUI := tview.NewTextView()
// TODO: Remove when support for disabling desktop notifications is in place.
initNotifier()
go pominal.Run(infoUI, timerUI) go pominal.Run(infoUI, timerUI)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)

4
ui.go
View file

@ -56,5 +56,7 @@ func drawInfo(t *tview.TextView, pominalCycle, workSessions, maxWorkSessions int
func drawTimer(t *tview.TextView, countdown float64) { func drawTimer(t *tview.TextView, countdown float64) {
t.Clear() t.Clear()
fmt.Fprintf(t, "\nTime remaining:\n%02d:%02d", int(countdown)/60, int(countdown)%60)
secondsPerMinute := 60
fmt.Fprintf(t, "\nTime remaining:\n%02d:%02d", int(countdown)/secondsPerMinute, int(countdown)%secondsPerMinute)
} }