393 lines
9.8 KiB
Go
393 lines
9.8 KiB
Go
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{Timers: []*Timer{}} }
|
|
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
|
|
}
|
|
|
|
func (timerlist *TimerList) GetNextId() int {
|
|
nextId := 0
|
|
for _, v := range timerlist.Timers {
|
|
if v.Id > nextId {
|
|
nextId = v.Id
|
|
}
|
|
}
|
|
return nextId + 1
|
|
}
|
|
|
|
// 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) {
|
|
timer.Id = timerlist.GetNextId()
|
|
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) {
|
|
for _, v := range timers {
|
|
timerlist.AddTimer(v)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
func (timerlist *TimerList) ResetIds() {
|
|
timerId := 1
|
|
for _, v := range timerlist.Timers {
|
|
v.Id = timerId
|
|
timerId++
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
timerlist.ResetIds()
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
if err := ioutil.WriteFile(filename, []byte(timerlist.String()), 0640); err != nil {
|
|
return err
|
|
}
|
|
timerlist.ResetIds()
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|