package main import ( "fmt" "os" "strconv" "strings" "time" timertxt "git.bullercodeworks.com/brian/go-timertxt" ) func (a *AppState) opStatus(args []string) int { if len(*a.TimerList.GetActiveTimers()) == 0 { fmt.Println("No timers running") return 0 } var currDur time.Duration for _, v := range *a.TimerList { if v.ActiveToday() { currDur += v.Duration() } } d := currDur.Round(GetRoundToDuration()) fmt.Printf("%s ( %.2f hrs )\n", time.Now().Format(time.Stamp), DurationToDecimal(d)) for _, v := range *a.TimerList.GetActiveTimers() { fmt.Println(timerToFriendlyString(v)) } return 0 } /** * Just output the time given the filters */ func (a *AppState) opShowTime(args []string) int { list := a.getFilteredTimerList(args) var isActive bool var total time.Duration for _, v := range *list { dur := v.FinishDate.Sub(v.StartDate) if v.FinishDate.IsZero() { dur = time.Now().Sub(v.StartDate) isActive = true } total += dur } total = total.Round(GetRoundToDuration()) if isActive { fmt.Printf("%.2f+\n", DurationToDecimal(total)) } else { fmt.Printf("%.2f\n", DurationToDecimal(total)) } return 0 } /** * List timers for a given time span * By default, only list Today */ func (a *AppState) opListTimers(args []string) int { var includeArchive bool var err error start := time.Time{} end := time.Now() var contextFilters []string var projectFilters []string var allFilters []func(timertxt.Timer) bool if len(args) > 0 { contextFilters, args = getContextsFromSlice(args) projectFilters, args = getProjectsFromSlice(args) } if len(args) > 0 { if args[0] == "--a" { includeArchive = true args = args[1:] } } if len(args) > 0 { if start, err = parseFuzzyTime(args[0]); err != nil { y, m, d := time.Now().Date() start = time.Date(y, m, d, 0, 0, 0, 0, time.Now().Location()) } else { args = args[1:] } if len(args) > 0 { if end, err = parseFuzzyTime(args[0]); err != nil { y, m, d := time.Now().Date() end = time.Date(y, m, d, 23, 59, 59, 0, time.Now().Location()) } else { args = args[1:] } } } if includeArchive { if err = a.LoadDoneList(); err != nil { fmt.Println("Error loading done.txt entries") fmt.Println(err.Error()) return 1 } } list := a.TimerList.GetTimersInRange(start, end) if includeArchive { *list = append(*list, (*a.DoneList.GetTimersInRange(start, end))...) } if len(contextFilters) > 0 { allFilters = append(allFilters, func(t timertxt.Timer) bool { for _, v := range contextFilters { v = strings.TrimPrefix(v, "@") if !t.HasContext(v) { return false } } return true }) } if len(projectFilters) > 0 { allFilters = append(allFilters, func(t timertxt.Timer) bool { for _, v := range projectFilters { v = strings.TrimPrefix(v, "+") if !t.HasProject(v) { return false } } return true }) } doFilters := func(t *timertxt.Timer) bool { for _, v := range allFilters { if !v(*t) { return false } } // If we made it all the way down here, it matches return true } list = list.Filter(doFilters) dayTotals := make(map[string]time.Duration) for _, v := range *list { dur := v.FinishDate.Sub(v.StartDate) if v.FinishDate.IsZero() { dur = time.Now().Sub(v.StartDate) } dayTotals[v.StartDate.Format("2006/01/02")] += dur } var oldDayStr, dayStr string for _, v := range *list { oldDayStr = dayStr dayStr = v.StartDate.Format("2006/01/02") if dayStr != oldDayStr { wrkDur := dayTotals[dayStr].Round(GetRoundToDuration()) fmtStr := dayStr + " ( %.2f )\n" fmt.Printf(fmtStr, DurationToDecimal(wrkDur)) } fmt.Println(" " + timerToFriendlyString(v)) } return 0 } func (a *AppState) opStartTimer(args []string) int { var contexts, projects, strTags []string t := timertxt.NewTimer() if len(args) > 0 { if start, err := parseFuzzyTime(args[0]); err == nil { t.StartDate = start args = args[1:] } } contexts, args = getContextsFromSlice(args) projects, args = getProjectsFromSlice(args) strTags, args = getAdditionalTagsFromSlice(args) for _, v := range contexts { t.Contexts = append(t.Contexts, strings.TrimPrefix(v, "@")) } for _, v := range projects { t.Projects = append(t.Projects, strings.TrimPrefix(v, "+")) } for _, v := range strTags { tgPts := strings.Split(v, ":") t.AdditionalTags[tgPts[0]] = tgPts[1] } a.TimerList.AddTimer(t) if err := a.WriteList(); err != nil { fmt.Println(err.Error()) return 1 } fmt.Println("Started: ", TimerToString(t)) return 0 } func (a *AppState) opToggleTimer(args []string) int { wrk, err := a.getMostRecentTimer() if err != nil { fmt.Print("{\"icon\":\"time\",\"state\":\"Critical\", \"text\": \"Error loading timer entry\"}") return 0 } var startArgs []string if wrk.Finished { // Start a new timer with the same data for _, v := range wrk.Contexts { startArgs = append(startArgs, "@"+v) } for _, v := range wrk.Projects { startArgs = append(startArgs, "+"+v) } for k, v := range wrk.AdditionalTags { startArgs = append(startArgs, k+":"+v) } a.opStartTimer(startArgs) } else { // Stop the active timer a.opStopTimer([]string{}) } return 0 } func (a *AppState) opStopTimer(args []string) int { var err error var wrk time.Time end := time.Now() id := -1 if len(args) > 0 { if wrk, err = parseFuzzyTime(args[0]); err != nil { id, err = strconv.Atoi(args[0]) } else { end = wrk args = args[1:] } } fmt.Println("Stopping at : " + end.Format(time.RFC3339)) var timerIds []int if id == -1 { for _, v := range *a.TimerList.GetActiveTimers() { timerIds = append(timerIds, v.Id) } } else { timerIds = append(timerIds, id) } for _, v := range timerIds { var stopped *timertxt.Timer if stopped, err = a.TimerList.GetTimer(v); err != nil { fmt.Println(err.Error()) } if err = a.SetTimerFinished(v, end); err != nil { fmt.Println(err.Error()) continue } fmt.Println("Stopped Timer:", timerToFriendlyString(stopped)) } if err = a.WriteList(); err != nil { fmt.Println(err.Error()) return 1 } return 0 } func (a *AppState) opSwitchTimer(args []string) int { var timerIds []int var err error end := time.Now() // Stop all running timers and start a new one with the given args for _, v := range *a.TimerList.GetActiveTimers() { timerIds = append(timerIds, v.Id) } fmt.Print("Stopping ", timerIds, "\n") for _, v := range timerIds { var stopped *timertxt.Timer if stopped, err = a.TimerList.GetTimer(v); err != nil { fmt.Println(err.Error()) continue } if err = a.SetTimerFinished(v, end); err != nil { fmt.Println(err.Error()) continue } fmt.Println("Stopped Timer:", timerToFriendlyString(stopped)) } return a.opStartTimer(args) } func (a *AppState) opArchiveTimer(args []string) int { if len(args) > 0 { for _, v := range args { var id int var timer *timertxt.Timer var err error if strings.HasPrefix(v, "@") { // We're archiving by context v = strings.TrimPrefix(v, "@") fmt.Println("Archiving by context:", v) timers := a.TimerList.GetTimersWithContext(v) fmt.Println("Found timers: ", len(*timers)) for _, tmr := range *timers { if tmr.Finished { if err = a.archiveTimer(tmr.Id); err != nil { fmt.Printf("Error archiving timer %d\n", tmr.Id) continue } fmt.Println(a.getDoneTimerString(*tmr)) } else { fmt.Println("Refusing to archive running timer:", tmr.Id) } } } else if strings.HasPrefix(v, "+") { // We're archiving by projcet v = strings.TrimPrefix(v, "+") fmt.Println("Archiving by project:", v) timers := a.TimerList.GetTimersWithProject(v) fmt.Println("Found timers: ", len(*timers)) for _, tmr := range *timers { if tmr.Finished { if err = a.archiveTimer(tmr.Id); err != nil { fmt.Printf("Error archiving timer %d\n", tmr.Id) continue } fmt.Println(a.getDoneTimerString(*tmr)) } else { fmt.Println("Refusing to archive running timer:", tmr.Id) } } } else { // I guess we're archiving by timer id if id, err = strconv.Atoi(v); err != nil { fmt.Printf("Invalid id given: %s\n", v) return 1 } if timer, err = a.TimerList.GetTimer(id); err != nil { fmt.Printf("Error getting timer %d\n", id) return 1 } if timer.Finished { if err = a.archiveTimer(id); err != nil { fmt.Printf("Error archiving timer %d\n", id) return 1 } fmt.Println(a.getDoneTimerString(*timer)) } else { fmt.Println("Refusing to archive running timer:", timer.Id) } } } } else { for _, v := range *a.TimerList { if v.Finished { if err := a.archiveTimer(v.Id); err != nil { fmt.Printf("Error archiving task %d\n", v.Id) return 1 } fmt.Println(a.getDoneTimerString(*v)) } } } return 0 } func (a *AppState) opRemoveTimer(args []string) int { if len(args) == 0 { fmt.Println("No timer id given") return 1 } id, err := strconv.Atoi(args[0]) if err != nil { fmt.Println("Invalid timer id given: " + args[0]) } t, err := a.TimerList.GetTimer(id) if err != nil { fmt.Println("Error getting timer with id: " + args[0]) return 1 } if err = a.TimerList.RemoveTimerById(id); err != nil { fmt.Println("Error Removing Timer: " + err.Error()) return 1 } fmt.Println("Timer removed") fmt.Println(TimerToString(t)) if err := a.WriteList(); err != nil { fmt.Println(err.Error()) return 1 } return 0 } func (a *AppState) opModifyTimer(args []string) int { var timer *timertxt.Timer var contexts, projects []string id, err := strconv.Atoi(args[0]) if err != nil { // We didn't have a timer id, so try to modify the first active timer if len(*a.TimerList.GetActiveTimers()) > 0 { timer = (*a.TimerList.GetActiveTimers())[0] } else { // And we don't have any active timers fmt.Println("No active timers, 'id' must be provided.") return 1 } } else { args = args[1:] if timer, err = a.TimerList.GetTimer(id); err != nil { fmt.Printf("Error getting timer %d\n", id) return 1 } } var start, end time.Time for _, v := range args { pts := strings.Split(v, "=") switch pts[0] { case "beginning", "start": if start, err = parseFuzzyTime(pts[1]); err != nil { fmt.Println("Error parsing start time.") return 1 } case "stop", "finish", "end": if end, err = parseFuzzyTime(pts[1]); err != nil { fmt.Println("Error parsing end time.") return 1 } case "project", "projects": projects = strings.Split(pts[1], ",") case "context", "contexts": contexts = strings.Split(pts[1], ",") } } if len(contexts) > 0 { for k := range contexts { contexts[k] = strings.TrimPrefix(contexts[k], "@") } timer.Contexts = contexts } if len(projects) > 0 { for k := range projects { projects[k] = strings.TrimPrefix(projects[k], "+") } timer.Projects = projects } if !start.IsZero() { timer.StartDate = start } if !end.IsZero() { timer.FinishDate = end timer.Finished = true } fmt.Println("Modified Timer:") fmt.Println(timerToFriendlyString(timer)) if err := a.WriteList(); err != nil { fmt.Println(err.Error()) return 1 } return 0 } func (a *AppState) opFuzzyParse(args []string) int { if len(args) > 0 { if start, err := parseFuzzyTime(args[0]); err == nil { fmt.Println(start.Format(time.RFC3339)) } else { fmt.Println(err.Error()) } } return 0 } func (a *AppState) opConfig(args []string) int { if len(args) == 0 { fmt.Println("Config Keys:") for _, v := range a.config.GetKeyList() { fmt.Println(" " + v) } fmt.Println(" timerpath") } else { if args[0] == "timerpath" { fmt.Println(a.directory + a.fileTimer) } else { fmt.Println(a.config.Get(args[0])) } } return 0 } func (a *AppState) opPrintUsage(args []string) int { for _, v := range a.ValidOperations { for _, vv := range v { fmt.Println(" " + vv) } fmt.Println("") } return 0 } func (a *AppState) opI3Status(args []string) int { state := "Idle" wrk, err := a.getMostRecentTimer() if err != nil { fmt.Print("{\"icon\":\"time\",\"state\":\"Critical\", \"text\": \"Error loading timer entry\"}") return 0 } var text string if wrk.FinishDate.IsZero() { wrkDur := wrk.Duration().Round(time.Minute * 15) hrs := int(wrkDur.Hours()) mins := int(wrkDur.Minutes()) - hrs*60 if hrs > 0 { text = fmt.Sprintf("%dh%dm", hrs, mins) } else { text = fmt.Sprintf("%dm", mins) } if !wrk.Finished { // If the current time is before 7AM, after 5PM, or a weekend, use a Warning state cTime := time.Now() if cTime.Weekday() == time.Sunday || cTime.Weekday() == time.Saturday || cTime.Hour() < 7 || cTime.Hour() > 17 { state = "Warning" } else { state = "Good" } } for _, ctx := range wrk.Contexts { text = fmt.Sprintf("%s @%s", text, ctx) } for _, prj := range wrk.Projects { text = fmt.Sprintf("%s +%s", text, prj) } } else { text = "(" for _, ctx := range wrk.Contexts { text = fmt.Sprintf("%s@%s ", text, ctx) } for _, prj := range wrk.Projects { text = fmt.Sprintf("%s+%s ", text, prj) } if text[len(text)-1] == ' ' { text = text[:len(text)-1] } text = text + ")" getListTotal := func(list *timertxt.TimerList) string { var isActive bool var total time.Duration for _, v := range *list { dur := v.FinishDate.Sub(v.StartDate) if v.FinishDate.IsZero() { dur = time.Now().Sub(v.StartDate) isActive = true } total += dur } total = total.Round(GetRoundToDuration()) if isActive { return fmt.Sprintf("%.2f+", DurationToDecimal(total)) } else { return fmt.Sprintf("%.2f", DurationToDecimal(total)) } } dayList := a.getFilteredTimerList([]string{"--a", BeginningOfDay().Format("2006-01-02"), "@bcw"}) text = text + " d:" + getListTotal(dayList) weekList := a.getFilteredTimerList([]string{"--a", BeginningOfWeek().Format("2006-01-02"), "@bcw"}) text = text + " w:" + getListTotal(weekList) monthList := a.getFilteredTimerList([]string{"--a", BeginningOfMonth().Format("2006-01-02"), "@bcw"}) text = text + " m:" + getListTotal(monthList) } fmt.Printf("{\"icon\":\"time\",\"state\":\"%s\", \"text\": \"%s\"}", state, text) return 0 } func (a *AppState) opEditor(args []string) int { editor := os.Getenv("EDITOR") if editor == "" { fmt.Println("No $EDITOR set") return 1 } fmt.Println(editor, a.directory+a.fileTimer) return 0 }