▍ humdrum codex / dots v1.0.0
1.9 KB raw
 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
package day

import (
	"fmt"
	"regexp"
	"strconv"
	"time"
)

var timeRe = regexp.MustCompile(`^(\d{1,2}):(\d{2})$`)
var rangeRe = regexp.MustCompile(`^(\d{1,2}:\d{2})-(\d{1,2}:\d{2})$`)

// IsTimeSyntax reports whether arg looks like a time or time range,
// deciding whether a positional arg is a target or an activity name.
func IsTimeSyntax(arg string) bool {
	return timeRe.MatchString(arg) || rangeRe.MatchString(arg)
}

func parseMinutes(s string) (int, error) {
	m := timeRe.FindStringSubmatch(s)
	if m == nil {
		return 0, fmt.Errorf("invalid time %q (want H:MM)", s)
	}
	h, err := strconv.Atoi(m[1])
	if err != nil {
		return 0, fmt.Errorf("invalid time %q", s)
	}
	min, err := strconv.Atoi(m[2])
	if err != nil {
		return 0, fmt.Errorf("invalid time %q", s)
	}
	if h > 23 || min > 59 {
		return 0, fmt.Errorf("invalid time %q", s)
	}
	return h*60 + min, nil
}

// ParseTarget resolves a time argument to canonical block indices.
// arg "" means the slot containing now. Ranges are end-exclusive.
func ParseTarget(arg string, blockSize int, now time.Time) ([]int, error) {
	if arg == "" {
		return []int{(now.Hour()*60 + now.Minute()) / blockSize}, nil
	}
	if m := rangeRe.FindStringSubmatch(arg); m != nil {
		start, err := parseMinutes(m[1])
		if err != nil {
			return nil, err
		}
		end, err := parseMinutes(m[2])
		if err != nil {
			return nil, err
		}
		if end <= start {
			return nil, fmt.Errorf("range end %s is not after start %s", m[2], m[1])
		}
		first := start / blockSize
		last := (end - 1) / blockSize
		out := make([]int, 0, last-first+1)
		for i := first; i <= last; i++ {
			out = append(out, i)
		}
		return out, nil
	}
	mins, err := parseMinutes(arg)
	if err != nil {
		return nil, err
	}
	return []int{mins / blockSize}, nil
}

// SlotLabel renders a block index as its start time, e.g. "14:30".
func SlotLabel(index, blockSize int) string {
	mins := index * blockSize
	return fmt.Sprintf("%d:%02d", mins/60, mins%60)
}