aboutsummaryrefslogtreecommitdiff
path: root/main.go
blob: 85520dd275ad937013e9dc78b1b7f88d58f626b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"time"
)

type Opt struct {
	Work  time.Duration
	Short time.Duration
	Long  time.Duration
	Day   time.Duration
	Tick  time.Duration
	Runs  int
	run   int
}

type stateFn func(context.Context) stateFn

func (o *Opt) doWork(ctx context.Context) stateFn {
	select {
	case <-ctx.Done():
		return nil
	default:
		ctx, cancel := context.WithTimeout(ctx, o.Work)
		defer cancel()
		o.display(ctx, "working session")
	}
	if o.run++; o.run%o.Runs == 0 {
		return o.longBreak
	}
	return o.shortBreak
}

func (o *Opt) shortBreak(ctx context.Context) stateFn {
	select {
	case <-ctx.Done():
		return nil
	default:
		ctx, cancel := context.WithTimeout(ctx, o.Short)
		defer cancel()
		o.display(ctx, "short break")
	}
	return o.doWork
}

func (o *Opt) longBreak(ctx context.Context) stateFn {
	select {
	case <-ctx.Done():
		return nil
	default:
		ctx, cancel := context.WithTimeout(ctx, o.Long)
		defer cancel()
		o.display(ctx, "long break")
	}
	return o.doWork
}

func (o *Opt) display(ctx context.Context, s string) {
	dl, ok := ctx.Deadline()
	if !ok {
		return
	}
	total := time.Until(dl)
	ticker := time.NewTicker(o.Tick)
	defer ticker.Stop()
	defer fmt.Println("")
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			fmt.Printf("\r%-20s %s", s, progress(total, time.Until(dl)))
		}
	}
}

func progress(total, left time.Duration) string {
	width := 40
	if left < 0 {
		left = 0
	}
	todo := width * int(left) / int(total)
	s := fmt.Sprintf("%3d%% |", 100-100*int(left)/int(total))
	s += strings.Repeat("*", width-todo) + strings.Repeat(" ", todo)
	if left > 0 {
		s += fmt.Sprintf("| %9s", round(left))
	} else {
		s += fmt.Sprintf("| %9s", "done")
	}
	return s
}

func round(d time.Duration) time.Duration { return d - d%time.Second }

func main() {
	var o Opt
	flag.DurationVar(&o.Work, "work", 25*time.Minute, "work time")
	flag.DurationVar(&o.Short, "short", 5*time.Minute, "short break")
	flag.DurationVar(&o.Long, "long", 15*time.Minute, "long break")
	flag.DurationVar(&o.Day, "day", 12*time.Hour, "work day")
	flag.DurationVar(&o.Tick, "tick", time.Second, "update interval")
	flag.IntVar(&o.Runs, "runs", 4, "work runs")
	flag.Parse()

	ctx, cancel := context.WithTimeout(context.Background(), o.Day)

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	go func() {
		<-sig
		cancel()
	}()

	defer func(t time.Time) {
		fmt.Printf("total %v\n", round(time.Since(t)))
	}(time.Now())

	for s := o.doWork; s != nil; s = s(ctx) {
	}
}