1package main23import (4 "github.com/nmeum/tracktime/parser"56 "flag"7 "fmt"8 "log"9 "os"10 "time"11)1213type TrackedDate struct {14 Duration time.Duration15 BonusWork bool16}1718const (19 DAY = 'd'20 WEEK = 'w'21 MONTH = 'm'22)2324var (25 goal = flag.Int("h", 8, "hours per interval")26 interval = flag.String("i", "d", "interval for working hours")27 seconds = flag.Bool("s", false, "output duration in seconds")28)2930var dateLayout string3132func max(a, b int) int {33 if a > b {34 return a35 } else {36 return b37 }38}3940func durationString(duration time.Duration) string {41 if *seconds {42 return fmt.Sprintf("%v", duration.Seconds())43 } else {44 return duration.String()45 }46}4748func intervalString(date time.Time) string {49 if *interval == "" {50 fmt.Fprintf(os.Stderr, "invalid interval\n")51 os.Exit(1)52 }5354 switch (*interval)[0] {55 case DAY:56 return date.Format(dateLayout)57 case WEEK:58 year, week := date.ISOWeek()59 return fmt.Sprintf("W%v %v", week, year)60 case MONTH:61 year := date.Year()62 return fmt.Sprintf("%s %v", date.Month(), year)63 default:64 fmt.Fprintf(os.Stderr, "unsupported interval: %q\n", *interval)65 os.Exit(2)66 }6768 panic("unreachable")69}7071func handleEntries(entries []*parser.Entry) {72 var keys []string73 var maxdurlen int7475 workmap := make(map[string]TrackedDate)76 for _, entry := range entries {77 key := intervalString(entry.Date)7879 e, ok := workmap[key]80 if !ok {81 keys = append(keys, key)82 } else if e.BonusWork && !entry.BonusWork {83 fmt.Fprintf(os.Stderr, "WARNING: Only some entries for %v are bonus hours\n", entry.Date.String())84 }85 workmap[key] = TrackedDate{e.Duration + entry.Duration, entry.BonusWork}8687 // Date should always have the same width. Only duration88 // requires padding based on the maximum duration length.89 maxdurlen = max(maxdurlen, len(fmt.Sprintf("%v", e.Duration)))90 }9192 var delta, goalHours time.Duration93 goalHours = time.Duration(*goal) * time.Hour9495 // Output in same order as specified in input file96 for _, key := range keys {97 e := workmap[key]98 hours := e.Duration99100 if e.BonusWork {101 delta += hours102 } else {103 delta += (hours - goalHours)104 }105106 // Output should always be aligned at the pipe character.107 fmt.Printf("%v %*v | %v\n", key, maxdurlen, hours, durationString(delta))108 }109}110111func main() {112 log.SetFlags(log.Lshortfile)113 flag.Parse()114115 if flag.NArg() != 1 {116 fmt.Fprintf(os.Stderr, "specify a file to parse\n")117 os.Exit(1)118 }119120 fp := flag.Arg(0)121 file, err := os.Open(fp)122 if err != nil {123 log.Fatal(err)124 }125 defer file.Close()126127 dateLayout = parser.DefaultTimeFormat()128 p := parser.NewParser(dateLayout)129130 entries, err := p.ParseEntries(fp, file)131 if err != nil {132 log.Fatal(err)133 }134135 handleEntries(entries)136}