Break into multi files for more robust and less panicky
parent
7a99f2af50
commit
7fe3934f07
|
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicStopwatch struct {
|
||||||
|
*Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBasicStopwatch(t *Timer) string {
|
||||||
|
b := &BasicStopwatch{Timer: t}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BasicStopwatch) left() string {
|
||||||
|
cur := t.config.Duration - t.Remaining()
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%02d:%02d:%02d",
|
||||||
|
int(cur.Hours()),
|
||||||
|
int(cur.Minutes()),
|
||||||
|
int(cur.Seconds())%60,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BasicStopwatch) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s",
|
||||||
|
t.left(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicTimer struct {
|
||||||
|
*Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBasicTimer(t *Timer) string {
|
||||||
|
b := &BasicTimer{Timer: t}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BasicTimer) left() string {
|
||||||
|
cur := t.Remaining()
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%02d:%02d:%02d",
|
||||||
|
int(cur.Hours()),
|
||||||
|
int(cur.Minutes()),
|
||||||
|
int(cur.Seconds())%60,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BasicTimer) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s",
|
||||||
|
t.left(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/args"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Repeat bool
|
||||||
|
Stopwatch bool
|
||||||
|
ETA bool
|
||||||
|
TS bool
|
||||||
|
Done bool
|
||||||
|
Duration time.Duration
|
||||||
|
Offset time.Duration
|
||||||
|
Msg string
|
||||||
|
Interval time.Duration
|
||||||
|
Until time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig() (Config, error) {
|
||||||
|
as := args.NewArgSet()
|
||||||
|
|
||||||
|
as.Append(args.TIME, "until", "deadline", time.Time{})
|
||||||
|
as.Append(args.BOOL, "repeat", "whether to loop", true)
|
||||||
|
as.Append(args.BOOL, "stopwatch", "count up to time instead of down", false)
|
||||||
|
as.Append(args.BOOL, "eta", "whether to display deadline", false)
|
||||||
|
as.Append(args.BOOL, "ts", "whether to display timestamp", false)
|
||||||
|
as.Append(args.BOOL, "done", "whether to display done or not", false)
|
||||||
|
as.Append(args.DURATION, "duration", "how long the base time is", time.Minute*30)
|
||||||
|
as.Append(args.DURATION, "offset", "offset from the base time", time.Second*0)
|
||||||
|
as.Append(args.STRING, "msg", "message to display", "Timer up")
|
||||||
|
as.Append(args.DURATION, "interval", "refresh interval", time.Millisecond*500)
|
||||||
|
|
||||||
|
if err := as.Parse(); err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := Config{
|
||||||
|
Repeat: as.Get("repeat").GetBool(),
|
||||||
|
Stopwatch: as.Get("stopwatch").GetBool(),
|
||||||
|
ETA: as.Get("eta").GetBool(),
|
||||||
|
TS: as.Get("ts").GetBool(),
|
||||||
|
Done: as.Get("done").GetBool(),
|
||||||
|
Duration: as.Get("duration").GetDuration(),
|
||||||
|
Offset: as.Get("offset").GetDuration(),
|
||||||
|
Msg: as.Get("msg").GetString(),
|
||||||
|
Interval: as.Get("interval").GetDuration(),
|
||||||
|
Until: as.Get("until").GetTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.Until.IsZero() {
|
||||||
|
config.Repeat = false
|
||||||
|
config.Duration = time.Until(config.Until) + config.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/eiannone/keyboard"
|
||||||
|
|
||||||
|
type Keyboard struct {
|
||||||
|
Events chan byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeyboard() (*Keyboard, error) {
|
||||||
|
err := keyboard.Open()
|
||||||
|
return &Keyboard{
|
||||||
|
Events: make(chan byte),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Keyboard) Listen() {
|
||||||
|
defer func() {
|
||||||
|
recover()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
r, _, err := keyboard.GetKey()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b := byte(r)
|
||||||
|
k.Events <- b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Keyboard) Next() byte {
|
||||||
|
return <-k.Events
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Keyboard) Close() {
|
||||||
|
keyboard.Close()
|
||||||
|
close(k.Events)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyboardNext(t *testing.T) {
|
||||||
|
defer fakeInput(t)()
|
||||||
|
k, err := NewKeyboard()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
go k.Listen()
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
t.Log(k.Next())
|
||||||
|
}
|
||||||
|
k.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyboardEvents(t *testing.T) {
|
||||||
|
defer fakeInput(t)()
|
||||||
|
k, err := NewKeyboard()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
go k.Listen()
|
||||||
|
i := 0
|
||||||
|
for b := range k.Events {
|
||||||
|
i += 1
|
||||||
|
t.Log(b)
|
||||||
|
if i == 5 {
|
||||||
|
k.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeInput(t *testing.T) func() {
|
||||||
|
if !strings.Contains(fmt.Sprint(os.Args), "-test.v=true") {
|
||||||
|
t.Fatal("-v not passed, requries manual testing for input")
|
||||||
|
}
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
@ -1,253 +1,59 @@
|
||||||
// Package main has a comment
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"os"
|
||||||
"strconv"
|
"os/signal"
|
||||||
"strings"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/eiannone/keyboard"
|
|
||||||
"github.com/everdev/mack"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var notified = false
|
|
||||||
var alertMessage string
|
|
||||||
|
|
||||||
var originalStart = time.Now()
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
config, err := NewConfig()
|
||||||
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor := time.NewTicker(tickerInterval)
|
k, err := NewKeyboard()
|
||||||
defer monitor.Stop()
|
|
||||||
base, err := time.ParseDuration(duration)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
var cur time.Duration
|
go k.Listen()
|
||||||
if invert {
|
defer k.Close()
|
||||||
cur = time.Duration(0)
|
|
||||||
} else {
|
tp := NewTickPrinter(config.Interval)
|
||||||
cur = base
|
go tp.Start()
|
||||||
}
|
defer tp.Stop()
|
||||||
skip, err := time.ParseDuration(offset)
|
|
||||||
|
timer, err := NewTimer(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if invert {
|
tp.Message <- timer.String
|
||||||
cur += skip
|
|
||||||
} else {
|
sigc := make(chan os.Signal)
|
||||||
cur -= skip
|
signal.Notify(sigc, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go listen(k, tp, timer, func() { close(sigc) })
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigc:
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stop := make(chan bool)
|
func listen(k *Keyboard, tp *TickPrinter, timer *Timer, stop func()) {
|
||||||
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 {
|
for {
|
||||||
b := <-keych
|
key := <-k.Events
|
||||||
switch b {
|
switch key {
|
||||||
case 'q':
|
|
||||||
stop <- true
|
|
||||||
<-confirm
|
|
||||||
return
|
|
||||||
case 'p':
|
case 'p':
|
||||||
paused = !paused
|
timer.TogglePause()
|
||||||
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':
|
case 'z':
|
||||||
log.Print()
|
fmt.Printf("\n")
|
||||||
printTime(&cur, base, &delim, invert, repeat, eta)
|
tp.Flush()
|
||||||
|
case 'r':
|
||||||
|
timer.Reset()
|
||||||
|
case 'q':
|
||||||
|
stop()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TickPrinter struct {
|
||||||
|
Message chan func() string
|
||||||
|
msg func() string
|
||||||
|
lastLen int
|
||||||
|
Interval time.Duration
|
||||||
|
stop chan struct{}
|
||||||
|
flush chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTickPrinter(interval time.Duration) *TickPrinter {
|
||||||
|
return &TickPrinter{
|
||||||
|
Message: make(chan func() string),
|
||||||
|
msg: func() string {
|
||||||
|
return time.Now().Format("15:04:05")
|
||||||
|
},
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
flush: make(chan struct{}),
|
||||||
|
Interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TickPrinter) Start() {
|
||||||
|
ticker := time.NewTicker(t.Interval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.flush:
|
||||||
|
t.Print()
|
||||||
|
case <-ticker.C:
|
||||||
|
t.Print()
|
||||||
|
case msg := <-t.Message:
|
||||||
|
t.msg = msg
|
||||||
|
case <-t.stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TickPrinter) Stop() {
|
||||||
|
close(t.stop)
|
||||||
|
time.Sleep(t.Interval)
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TickPrinter) Flush() {
|
||||||
|
t.flush <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TickPrinter) Print() {
|
||||||
|
fmt.Printf("\r%s", strings.Repeat(" ", t.lastLen))
|
||||||
|
msg := t.msg()
|
||||||
|
l := len(msg)
|
||||||
|
if len(msg) < t.lastLen {
|
||||||
|
msg += strings.Repeat(" ", t.lastLen-len(msg))
|
||||||
|
}
|
||||||
|
t.lastLen = l
|
||||||
|
fmt.Printf("\r%s", msg)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTickPrinter(t *testing.T) {
|
||||||
|
defer testOut(t)()
|
||||||
|
name := os.Stdout.Name()
|
||||||
|
|
||||||
|
tp := NewTickPrinter(time.Millisecond * 250)
|
||||||
|
go tp.Start()
|
||||||
|
time.Sleep(time.Millisecond * 300)
|
||||||
|
tp.Stop()
|
||||||
|
b, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ts, err := time.Parse("15:04:05", strings.TrimSpace(string(b)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if (time.Now().Minute()*60+time.Now().Hour())-(ts.Minute()*60+ts.Hour()) > 1 {
|
||||||
|
t.Fatal(ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOut(t *testing.T) func() {
|
||||||
|
wasOut := os.Stdout
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if os.Stdout, err = ioutil.TempFile(os.TempDir(), "testOut*"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
os.Stdout = wasOut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Typed func(*Timer) string
|
||||||
|
|
||||||
|
type Timer struct {
|
||||||
|
config Config
|
||||||
|
From time.Time
|
||||||
|
Typed Typed
|
||||||
|
paused bool
|
||||||
|
offset time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimer(config Config) (*Timer, error) {
|
||||||
|
t := &Timer{From: time.Now().Add(time.Duration(-1) * config.Offset), config: config}
|
||||||
|
return t, t.setTyped(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) TogglePause() {
|
||||||
|
if t.paused {
|
||||||
|
t.Resume()
|
||||||
|
} else {
|
||||||
|
t.Pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Resume() {
|
||||||
|
t.paused = false
|
||||||
|
t.From = time.Now().Add(time.Duration(-1) * t.offset)
|
||||||
|
t.offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Pause() {
|
||||||
|
t.paused = true
|
||||||
|
t.offset = time.Since(t.From)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) setTyped(config Config) error {
|
||||||
|
if config.Stopwatch {
|
||||||
|
t.Typed = NewBasicStopwatch
|
||||||
|
} else {
|
||||||
|
t.Typed = NewBasicTimer
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.ETA {
|
||||||
|
t.Typed = WithETA(t.Typed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Done {
|
||||||
|
t.Typed = WithDone(t.Typed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.TS {
|
||||||
|
t.Typed = WithTS(t.Typed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Remaining() time.Duration {
|
||||||
|
return time.Until(t.Deadline()) + time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Deadline() time.Time {
|
||||||
|
from := t.From
|
||||||
|
if t.paused {
|
||||||
|
from = time.Now().Add(time.Duration(-1) * t.offset)
|
||||||
|
}
|
||||||
|
return from.Add(t.config.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Ack() {
|
||||||
|
for t.Done() {
|
||||||
|
t.From = t.From.Add(t.config.Duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Reset() {
|
||||||
|
t.From = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Done() bool {
|
||||||
|
return time.Now().After(t.From.Add(t.config.Duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) String() string {
|
||||||
|
return t.Typed(t)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithETA(typed Typed) Typed {
|
||||||
|
return func(t *Timer) string {
|
||||||
|
s := typed(t)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s\tETA: %s",
|
||||||
|
s,
|
||||||
|
t.Deadline().Format("15:04:05"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDone(typed Typed) Typed {
|
||||||
|
return func(t *Timer) string {
|
||||||
|
s := typed(t)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s\tDone: %v",
|
||||||
|
s,
|
||||||
|
t.Done(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTS(typed Typed) Typed {
|
||||||
|
return func(t *Timer) string {
|
||||||
|
s := typed(t)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s: %s",
|
||||||
|
time.Now().Format("15:04:05"),
|
||||||
|
s,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue