package timertxt import ( "bufio" "errors" "fmt" "io/ioutil" "os" "sort" "strings" "time" ) // TimerList represents a list of timer.txt timer entries. // It is usually loaded from a whole timer.txt file. type TimerList struct { Timers []*Timer `json:"timers"` SortFlag int `json:"sortFlag"` } // NewTimerList creates a new empty TimerList. func NewTimerList() *TimerList { return &TimerList{} } func (timerlist *TimerList) Size() int { return len(timerlist.Timers) } func (timerlist *TimerList) GetTimerSlice() []*Timer { return timerlist.Timers } func (timerlist *TimerList) Contains(t *Timer) bool { for _, tmr := range timerlist.Timers { if tmr == t { return true } } return false } func (timerlist *TimerList) GetActiveOrMostRecent() (*Timer, error) { var found *Timer var latest time.Time activeTimers := timerlist.Filter(func(t *Timer) bool { return !t.Finished }) if len(activeTimers.Timers) > 0 { return activeTimers.GetMostRecentTimer() } for _, t := range timerlist.Timers { if t.FinishDate.IsZero() { if t.StartDate.After(latest) { found = t latest = t.StartDate } } else { if t.FinishDate.After(latest) { latest = t.FinishDate found = t } } } if found == nil { return nil, errors.New("No timer found") } return found, nil } func (timerlist *TimerList) GetMostRecentTimer() (*Timer, error) { var found *Timer var latest time.Time for _, t := range timerlist.Timers { if t.FinishDate.IsZero() { if t.StartDate.After(latest) { found = t latest = t.StartDate } } else { if t.FinishDate.After(latest) { latest = t.FinishDate found = t } } } if found == nil { return nil, errors.New("No timer found") } return found, nil } func (timerlist *TimerList) GetTimersInRange(start, end time.Time) *TimerList { fltr := func(t *Timer) bool { if t.StartDate.Before(end) && t.StartDate.After(start) { return true } if t.FinishDate.Before(end) && t.FinishDate.After(start) { return true } return false } return timerlist.Filter(fltr) } func (timerlist *TimerList) GetTimersWithContext(context string) *TimerList { return timerlist.Filter(func(t *Timer) bool { return t.HasContext(context) }) } func (timerlist *TimerList) GetTimersWithProject(project string) *TimerList { return timerlist.Filter(func(t *Timer) bool { return t.HasProject(project) }) } func (timerlist *TimerList) GetContexts() []string { var ret []string added := make(map[string]bool) for _, tmr := range timerlist.Timers { for _, c := range tmr.Contexts { if !added[c] { ret = append(ret, c) added[c] = true } } } sort.Strings(ret) return ret } func (timerlist *TimerList) GetProjects() []string { var ret []string added := make(map[string]bool) for _, tmr := range timerlist.Timers { for _, p := range tmr.Projects { if !added[p] { ret = append(ret, p) added[p] = true } } } sort.Strings(ret) return ret } func (timerlist *TimerList) GetTagKVList() []string { var ret []string added := make(map[string]bool) for _, tmr := range timerlist.Timers { for k, v := range tmr.AdditionalTags { tag := fmt.Sprintf("%s:%s", k, v) if !added[tag] { ret = append(ret, tag) added[tag] = true } } } sort.Strings(ret) return ret } func (timerlist *TimerList) GetTagKeys() []string { var ret []string added := make(map[string]bool) for _, tmr := range timerlist.Timers { for k := range tmr.AdditionalTags { if !added[k] { ret = append(ret, k) added[k] = true } } } sort.Strings(ret) return ret } func (timerlist *TimerList) GetTagValuesForKey(key string) []string { var ret []string added := make(map[string]bool) for _, tmr := range timerlist.Timers { if v, ok := tmr.AdditionalTags[key]; ok && !added[v] { ret = append(ret, v) added[v] = true } } sort.Strings(ret) return ret } func (timerlist *TimerList) GetActiveTimers() *TimerList { t := *NewTimerList() for _, v := range timerlist.Timers { if v.FinishDate.IsZero() { t.Timers = append(t.Timers, v) } } return &t } // String returns a complete list of timers in timer.txt format. func (timerlist *TimerList) String() string { var ret string for _, timer := range timerlist.Timers { ret += fmt.Sprintf("%s\n", timer.String()) } return ret } // AddTimer prepends a Timer to the current TimerList and takes care to set the Timer.Id correctly func (timerlist *TimerList) AddTimer(timer *Timer) { timerlist.Timers = append(timerlist.Timers, timer) timerlist.refresh() } // AddTimers adds all passed in timers to the list, sorts the list, then updates the Timer.Id values. func (timerlist *TimerList) AddTimers(timers []*Timer) { timerlist.Timers = append(timerlist.Timers, timers...) timerlist.Sort(SortStartDateAsc) } func (timerlist *TimerList) Combine(other *TimerList) { timerlist.AddTimers(other.Timers) } // GetTimer returns the Timer with the given timer 'id' from the TimerList. // Returns an error if Timer could not be found. func (timerlist *TimerList) GetTimer(id int) (*Timer, error) { for i := range timerlist.Timers { if timerlist.Timers[i].Id == id { return timerlist.Timers[i], nil } } return nil, errors.New("timer not found") } // RemoveTimerById removes any Timer with given Timer 'id' from the TimerList. // Returns an error if no Timer was removed. func (timerlist *TimerList) RemoveTimerById(id int) error { found := false var remIdx int var t *Timer for remIdx, t = range timerlist.Timers { if t.Id == id { found = true break } } if !found { return errors.New("timer not found") } timerlist.Timers = append(timerlist.Timers[:remIdx], timerlist.Timers[remIdx+1:]...) timerlist.refresh() return nil } // RemoveTimer removes any Timer from the TimerList with the same String representation as the given Timer. // Returns an error if no Timer was removed. func (timerlist *TimerList) RemoveTimer(timer Timer) error { found := false var remIdx int var t *Timer for remIdx, t = range timerlist.Timers { if t.String() == timer.String() { found = true break } } if !found { return errors.New("timer not found") } timerlist.Timers = append(timerlist.Timers[:remIdx], timerlist.Timers[remIdx+1:]...) timerlist.refresh() return nil } // ArchiveTimerToFile removes the timer from the active list and concatenates it to // the passed in filename // Return an err if any part of that fails func (timerlist *TimerList) ArchiveTimerToFile(timer Timer, filename string) error { if err := timerlist.RemoveTimer(timer); err != nil { return err } f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) if err != nil { return err } defer f.Close() _, err = f.WriteString(timer.String() + "\n") return err } // Filter filters the current TimerList for the given predicate (a function that takes a timer as input and returns a // bool), and returns a new TimerList. The original TimerList is not modified. func (timerlist *TimerList) Filter(predicate func(*Timer) bool) *TimerList { var newList TimerList for _, t := range timerlist.Timers { if predicate(t) { newList.Timers = append(newList.Timers, t) } } return &newList } // LoadFromFile loads a TimerList from *os.File. // Note: This will clear the current TimerList and overwrite it's contents with whatever is in *os.File. func (timerlist *TimerList) LoadFromFile(file *os.File) error { timerlist.Timers = []*Timer{} // Empty timerlist timerId := 1 scanner := bufio.NewScanner(file) for scanner.Scan() { text := strings.Trim(scanner.Text(), "\t\n\r") // Read Line // Ignore blank lines if text == "" { continue } timer, err := ParseTimer(text) if err != nil { return err } timer.Id = timerId timerId++ timerlist.Timers = append(timerlist.Timers, timer) } if err := scanner.Err(); err != nil { return err } timerlist.refresh() return nil } // WriteToFile writes a TimerList to *os.File func (timerlist *TimerList) WriteToFile(file *os.File) error { writer := bufio.NewWriter(file) _, err := writer.WriteString(timerlist.String()) writer.Flush() return err } // LoadFromFilename loads a TimerList from the filename. func (timerlist *TimerList) LoadFromFilename(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() return timerlist.LoadFromFile(file) } // WriteToFilename writes a TimerList to the specified file (most likely called "timer.txt"). func (timerlist *TimerList) WriteToFilename(filename string) error { return ioutil.WriteFile(filename, []byte(timerlist.String()), 0640) } // LoadFromFile loads and returns a TimerList from *os.File. func LoadFromFile(file *os.File) (*TimerList, error) { timerlist := TimerList{} if err := timerlist.LoadFromFile(file); err != nil { return nil, err } return &timerlist, nil } // WriteToFile writes a TimerList to *os.File. func WriteToFile(timerlist *TimerList, file *os.File) error { return timerlist.WriteToFile(file) } // LoadFromFilename loads and returns a TimerList from a file (most likely called "timer.txt") func LoadFromFilename(filename string) (*TimerList, error) { timerlist := TimerList{} if err := timerlist.LoadFromFilename(filename); err != nil { return nil, err } return &timerlist, nil } // WriteToFilename write a TimerList to the specified file (most likely called "timer.txt") func WriteToFilename(timerlist *TimerList, filename string) error { return timerlist.WriteToFilename(filename) }