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) display(ctx context.Context, s string) { go notify(s) dl, ok := ctx.Deadline() if !ok { return } total := time.Until(dl) ticker := time.NewTicker(o.Tick) defer ticker.Stop() defer fmt.Println("") for range ticker.C { fmt.Printf("\r%-20s %s", s, progress(total, time.Until(dl))) select { case <-ctx.Done(): return default: } } } func total(t time.Time) { fmt.Printf("total %v\n", round(time.Since(t))) } func round(d time.Duration) time.Duration { return d - d%time.Second } 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 (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, "do work") } 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 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 total(time.Now()) for s := o.doWork; s != nil; s = s(ctx) { } }