1package main
2
3import (
4 "github.com/nmeum/tracktime/parser"
5
6 "flag"
7 "fmt"
8 "log"
9 "os"
10 "time"
11)
12
13const (
14 DAY = 'd'
15 WEEK = 'w'
16 MONTH = 'm'
17)
18
19var (
20 goal = flag.Int("h", 8, "hours per interval")
21 interval = flag.String("i", "d", "interval for working hours")
22 seconds = flag.Bool("s", false, "output duration in seconds")
23)
24
25var dateLayout string
26
27func max(a, b int) int {
28 if a > b {
29 return a
30 } else {
31 return b
32 }
33}
34
35func durationString(duration time.Duration) string {
36 if *seconds {
37 return fmt.Sprintf("%v", duration.Seconds())
38 } else {
39 return duration.String()
40 }
41}
42
43func intervalString(date time.Time) string {
44 if *interval == "" {
45 fmt.Fprintf(os.Stderr, "invalid interval\n")
46 os.Exit(1)
47 }
48
49 switch (*interval)[0] {
50 case DAY:
51 return date.Format(dateLayout)
52 case WEEK:
53 year, week := date.ISOWeek()
54 return fmt.Sprintf("W%v %v", week, year)
55 case MONTH:
56 year := date.Year()
57 return fmt.Sprintf("%s %v", date.Month(), year)
58 default:
59 fmt.Fprintf(os.Stderr, "unsupported interval: %q\n", *interval)
60 os.Exit(2)
61 }
62
63 panic("unreachable")
64}
65
66func handleEntries(entries []*parser.Entry) {
67 var keys []string
68 var maxdurlen int
69
70 workmap := make(map[string]time.Duration)
71 for _, entry := range entries {
72 key := intervalString(entry.Date)
73 if _, ok := workmap[key]; !ok {
74 keys = append(keys, key)
75 }
76 workmap[key] += entry.Duration
77
78 // Date should always have the same width. Only duration
79 // requires padding based on the maximum duration length.
80 maxdurlen = max(maxdurlen, len(fmt.Sprintf("%v", workmap[key])))
81 }
82
83 var delta, goalHours time.Duration
84 goalHours = time.Duration(*goal) * time.Hour
85
86 // Output in same order as specified in input file
87 for _, key := range keys {
88 hours := workmap[key]
89 delta += (hours - goalHours)
90
91 // Output should always be aligned at the pipe character.
92 fmt.Printf("%v %*v | %v\n", key, maxdurlen, hours, durationString(delta))
93 }
94}
95
96func main() {
97 log.SetFlags(log.Lshortfile)
98 flag.Parse()
99
100 if flag.NArg() != 1 {
101 fmt.Fprintf(os.Stderr, "specify a file to parse\n")
102 os.Exit(1)
103 }
104
105 fp := flag.Arg(0)
106 file, err := os.Open(fp)
107 if err != nil {
108 log.Fatal(err)
109 }
110 defer file.Close()
111
112 dateLayout = parser.DefaultTimeFormat()
113 p := parser.NewParser(dateLayout)
114
115 entries, err := p.ParseEntries(fp, file)
116 if err != nil {
117 log.Fatal(err)
118 }
119
120 handleEntries(entries)
121}