254 lines
5.5 KiB
Go
Executable File
254 lines
5.5 KiB
Go
Executable File
// Package main has a comment
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/eiannone/keyboard"
|
|
"github.com/everdev/mack"
|
|
)
|
|
|
|
var notified = false
|
|
var alertMessage string
|
|
|
|
var originalStart = time.Now()
|
|
|
|
func main() {
|
|
|
|
var duration string
|
|
var offset string
|
|
var interval string
|
|
var repeat bool
|
|
var invert bool
|
|
var eta bool
|
|
var until string
|
|
|
|
flag.BoolVar(&repeat, "repeat", true, "Whether to repeat the timer on complete")
|
|
flag.BoolVar(&invert, "stopwatch", false, "Use as a stopwatch")
|
|
flag.StringVar(&duration, "duration", "30m", "How long the timer should be")
|
|
flag.StringVar(&offset, "offset", "0m", "How much time the initial time should skip")
|
|
flag.StringVar(&alertMessage, "msg", "Timer up", "Message to display on timer expiration")
|
|
flag.StringVar(&interval, "interval", "500ms", "Interval duration")
|
|
flag.BoolVar(&eta, "eta", false, "Whether to display the ending time")
|
|
flag.StringVar(&until, "until", "", "Overload to make a non-repeating timer until given time as 23:59")
|
|
flag.Parse()
|
|
|
|
if until != "" {
|
|
repeat = false
|
|
offset = "0m"
|
|
now := time.Now()
|
|
targetHourStr := strings.Split(until, ":")[0]
|
|
targetMinStr := strings.Split(until, ":")[1]
|
|
hour, err := strconv.Atoi(targetHourStr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
min, err := strconv.Atoi(targetMinStr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if hour < now.Hour() || (hour == now.Hour() && min < now.Minute()) {
|
|
now = now.Add(time.Hour * 24)
|
|
}
|
|
thenTime := time.Date(now.Year(), now.Month(), now.Day(), hour, min, 0, 0, now.Location())
|
|
duration = thenTime.Sub(time.Now()).String()
|
|
}
|
|
|
|
tickerInterval, err := time.ParseDuration(interval)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
monitor := time.NewTicker(tickerInterval)
|
|
defer monitor.Stop()
|
|
base, err := time.ParseDuration(duration)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var cur time.Duration
|
|
if invert {
|
|
cur = time.Duration(0)
|
|
} else {
|
|
cur = base
|
|
}
|
|
skip, err := time.ParseDuration(offset)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if invert {
|
|
cur += skip
|
|
} else {
|
|
cur -= skip
|
|
}
|
|
|
|
stop := make(chan bool)
|
|
pause := make(chan bool)
|
|
confirm := make(chan bool)
|
|
paused := false
|
|
delim := ':'
|
|
|
|
log.Print("Quit with 'q', Pause with 'p', Reset with 'r'")
|
|
|
|
keych := keyChannel()
|
|
|
|
go func() {
|
|
last := time.Now()
|
|
printTime(&cur, base, &delim, invert, repeat, eta)
|
|
for {
|
|
select {
|
|
case <-monitor.C:
|
|
difference := time.Duration(time.Now().UnixNano() - last.UnixNano())
|
|
last = time.Now()
|
|
if difference > time.Hour*2 {
|
|
keych <- 'p'
|
|
continue
|
|
} else if invert {
|
|
cur += difference
|
|
} else {
|
|
cur -= difference
|
|
}
|
|
case state := <-pause:
|
|
if !state {
|
|
monitor = time.NewTicker(tickerInterval)
|
|
last = time.Now()
|
|
} else {
|
|
monitor.Stop()
|
|
}
|
|
case <-stop:
|
|
log.Print()
|
|
confirm <- true
|
|
return
|
|
}
|
|
printTime(&cur, base, &delim, invert, repeat, eta)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
b := <-keych
|
|
switch b {
|
|
case 'q':
|
|
stop <- true
|
|
<-confirm
|
|
return
|
|
case 'p':
|
|
paused = !paused
|
|
pause <- paused
|
|
if paused {
|
|
keych <- 'z'
|
|
}
|
|
case 'r':
|
|
skip = time.Duration(0)
|
|
originalStart = time.Now()
|
|
if invert {
|
|
cur = time.Duration(0)
|
|
} else {
|
|
cur = base
|
|
}
|
|
notified = false
|
|
printTime(&cur, base, &delim, invert, repeat, eta)
|
|
case 'z':
|
|
log.Print()
|
|
printTime(&cur, base, &delim, invert, repeat, eta)
|
|
}
|
|
}
|
|
}
|
|
|
|
func printTime(pRemains *time.Duration, target time.Duration, delim *rune, reverse bool, repeat bool, eta bool) {
|
|
var final string
|
|
remains := *pRemains
|
|
sec := remains.Seconds()
|
|
min := int(sec / 60.0)
|
|
seconds := int(sec) % 60
|
|
hrs := min / 60
|
|
min = min % 60
|
|
if *delim == ':' {
|
|
*delim = ' '
|
|
} else {
|
|
*delim = ':'
|
|
}
|
|
if reverse {
|
|
remains := target - remains
|
|
rMin := int(remains.Seconds() / 60.0)
|
|
rSec := int(remains.Seconds()) % 60
|
|
rHrs := rMin / 60
|
|
rMin = rMin % 60
|
|
tdelim := byte(*delim)
|
|
if remains < 0 {
|
|
rMin = 0
|
|
rSec = 0
|
|
rHrs = 0
|
|
tdelim = ':'
|
|
go alertTime(pRemains, target, reverse, repeat)
|
|
}
|
|
at := fmt.Sprintf("%2d%c%02d%c%02d ", hrs, byte(*delim), min, byte(*delim), seconds)
|
|
rem := fmt.Sprintf("%2d%c%02d%c%02d ", rHrs, tdelim, rMin, tdelim, rSec)
|
|
rem = fmt.Sprintf("%s \tAt: %s ", rem, at)
|
|
if eta {
|
|
rem = fmt.Sprintf("%s \tETA: %s", rem, time.Unix(0, (time.Now().UnixNano()+target.Nanoseconds()-pRemains.Nanoseconds())).Format("3:04"))
|
|
}
|
|
final = rem
|
|
} else {
|
|
rem := fmt.Sprintf("%2d%c%02d%c%02d ", hrs, byte(*delim), min, byte(*delim), seconds)
|
|
final = rem + " "
|
|
if eta {
|
|
final = fmt.Sprintf("%s \tETA: %s", final, time.Unix(0, time.Now().UnixNano()+pRemains.Nanoseconds()).Format("3:04"))
|
|
}
|
|
if remains < 0 {
|
|
go alertTime(pRemains, target, reverse, repeat)
|
|
}
|
|
}
|
|
fmt.Printf("\r%s", final)
|
|
}
|
|
|
|
func alertTime(pRemains *time.Duration, target time.Duration, reverse, repeat bool) {
|
|
if !notified {
|
|
notified = true
|
|
_, err := mack.Alert(fmt.Sprintf("%s\nTimer for %s done", alertMessage, target.String()))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if repeat {
|
|
originalStart = time.Now()
|
|
if reverse {
|
|
for *pRemains > 0 {
|
|
*pRemains -= target
|
|
}
|
|
*pRemains += target
|
|
} else {
|
|
for *pRemains < 0 {
|
|
*pRemains += target
|
|
}
|
|
}
|
|
notified = false
|
|
}
|
|
}
|
|
}
|
|
|
|
func keyChannel() chan rune {
|
|
ch := make(chan rune, 20)
|
|
go func() {
|
|
if err := keyboard.Open(); err != nil {
|
|
panic(err)
|
|
}
|
|
for {
|
|
b, _, err := keyboard.GetKey()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
by := rune(b)
|
|
if by == 'q' {
|
|
keyboard.Close()
|
|
ch <- by
|
|
return
|
|
}
|
|
ch <- by
|
|
}
|
|
}()
|
|
return ch
|
|
}
|