Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
b63b85bd30 | |||
6ea3390313 | |||
8a9e6c2bec | |||
cf2312525d | |||
13cd41aa3f | |||
b3e09780fb | |||
c62767a5ef | |||
6060aa5d4a | |||
6310dc9f6b | |||
5e83abb73b |
@ -2,6 +2,6 @@
|
||||
|
||||
A Go library for working with timer.txt files.
|
||||
|
||||
A timer.txt file is a plain text file for timekeeping purposes. It stems from the same motivations as todo.txt (http://todotxt.org/).
|
||||
A timer.txt file is a plain text file for timekeeping purposes. It stems from the same motivations as todo.txt (http://todotxt.org/ ).
|
||||
|
||||
This library is very similar to https://github.com/br0xen/go-todotxt, which is a fork of https://github.com/JamesClonk/go-todotxt.
|
||||
|
33
sort.go
33
sort.go
@ -8,28 +8,33 @@ import (
|
||||
|
||||
// Flags for defining sort element and order.
|
||||
const (
|
||||
SORT_UNFINISHED_START = iota
|
||||
SORT_START_DATE_ASC
|
||||
SORT_START_DATE_DESC
|
||||
SORT_FINISH_DATE_ASC
|
||||
SORT_FINISH_DATE_DESC
|
||||
SortUnfinishedStart = iota
|
||||
SortStartDateAsc
|
||||
SortStartDateDesc
|
||||
SortFinishDateAsc
|
||||
SortFinishDateDesc
|
||||
SortError
|
||||
)
|
||||
|
||||
// Sort allows a TimerList to be sorted by certain predefined fields.
|
||||
// See constants SORT_* for fields and sort order.
|
||||
// See constants Sort* for fields and sort order.
|
||||
func (timerlist *TimerList) Sort(sortFlag int) error {
|
||||
switch sortFlag {
|
||||
case SORT_UNFINISHED_START:
|
||||
case SortUnfinishedStart:
|
||||
timerlist.sortByUnfinishedThenStart()
|
||||
case SORT_START_DATE_ASC, SORT_START_DATE_DESC:
|
||||
case SortStartDateAsc, SortStartDateDesc:
|
||||
timerlist.sortByStartDate(sortFlag)
|
||||
case SORT_FINISH_DATE_ASC, SORT_FINISH_DATE_DESC:
|
||||
case SortFinishDateAsc, SortFinishDateDesc:
|
||||
timerlist.sortByFinishDate(sortFlag)
|
||||
default:
|
||||
return errors.New("Unrecognized sort option")
|
||||
}
|
||||
timerlist.SortFlag = sortFlag
|
||||
return nil
|
||||
}
|
||||
func (timerlist *TimerList) refresh() {
|
||||
timerlist.Sort(timerlist.SortFlag)
|
||||
}
|
||||
|
||||
type timerlistSort struct {
|
||||
timerlists TimerList
|
||||
@ -37,15 +42,15 @@ type timerlistSort struct {
|
||||
}
|
||||
|
||||
func (ts *timerlistSort) Len() int {
|
||||
return len(ts.timerlists)
|
||||
return len(ts.timerlists.Timers)
|
||||
}
|
||||
|
||||
func (ts *timerlistSort) Swap(l, r int) {
|
||||
ts.timerlists[l], ts.timerlists[r] = ts.timerlists[r], ts.timerlists[l]
|
||||
ts.timerlists.Timers[l], ts.timerlists.Timers[r] = ts.timerlists.Timers[r], ts.timerlists.Timers[l]
|
||||
}
|
||||
|
||||
func (ts *timerlistSort) Less(l, r int) bool {
|
||||
return ts.by(ts.timerlists[l], ts.timerlists[r])
|
||||
return ts.by(ts.timerlists.Timers[l], ts.timerlists.Timers[r])
|
||||
}
|
||||
|
||||
func (timerlist *TimerList) sortBy(by func(t1, t2 *Timer) bool) *TimerList {
|
||||
@ -73,14 +78,14 @@ func sortByDate(asc bool, date1, date2 time.Time) bool {
|
||||
|
||||
func (timerlist *TimerList) sortByStartDate(order int) *TimerList {
|
||||
timerlist.sortBy(func(t1, t2 *Timer) bool {
|
||||
return sortByDate(order == SORT_START_DATE_ASC, t1.StartDate, t2.StartDate)
|
||||
return sortByDate(order == SortStartDateAsc, t1.StartDate, t2.StartDate)
|
||||
})
|
||||
return timerlist
|
||||
}
|
||||
|
||||
func (timerlist *TimerList) sortByFinishDate(order int) *TimerList {
|
||||
timerlist.sortBy(func(t1, t2 *Timer) bool {
|
||||
return sortByDate(order == SORT_FINISH_DATE_ASC, t1.FinishDate, t2.FinishDate)
|
||||
return sortByDate(order == SortFinishDateAsc, t1.FinishDate, t2.FinishDate)
|
||||
})
|
||||
return timerlist
|
||||
}
|
||||
|
51
timer.go
51
timer.go
@ -19,15 +19,15 @@ var (
|
||||
)
|
||||
|
||||
type Timer struct {
|
||||
Id int // Internal timer id
|
||||
Original string // Original raw timer text
|
||||
StartDate time.Time
|
||||
FinishDate time.Time
|
||||
Finished bool
|
||||
Notes string // Notes part of timer text
|
||||
Projects []string
|
||||
Contexts []string
|
||||
AdditionalTags map[string]string // Addon tags will be available here
|
||||
Id int `json:"id"` // Internal timer id
|
||||
Original string `json:"original"` // Original raw timer text
|
||||
StartDate time.Time `json:"startDate"`
|
||||
FinishDate time.Time `json:"finishDate"`
|
||||
Finished bool `json:"finished"`
|
||||
Notes string `json:"notes"` // Notes part of timer text
|
||||
Projects []string `json:"projects"`
|
||||
Contexts []string `json:"contexts"`
|
||||
AdditionalTags map[string]string `json:"additionalTags"` // Addon tags will be available here
|
||||
}
|
||||
|
||||
// String returns a complete timer string in timer.txt format.
|
||||
@ -212,6 +212,10 @@ func (timer *Timer) HasProject(project string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (timer *Timer) SetTag(name, val string) {
|
||||
timer.AdditionalTags[name] = val
|
||||
}
|
||||
|
||||
func (timer *Timer) HasTag(name string) bool {
|
||||
_, ok := timer.AdditionalTags[name]
|
||||
return ok
|
||||
@ -220,3 +224,32 @@ func (timer *Timer) HasTag(name string) bool {
|
||||
func (timer *Timer) GetTag(name string) string {
|
||||
return timer.AdditionalTags[name]
|
||||
}
|
||||
|
||||
// LooksLike returns true if the passed in timer looks like this
|
||||
func (timer *Timer) LooksLike(tmr *Timer) bool {
|
||||
if tmr.StartDate != timer.StartDate ||
|
||||
tmr.FinishDate != timer.FinishDate ||
|
||||
tmr.Finished != timer.Finished ||
|
||||
tmr.Notes != timer.Notes ||
|
||||
len(tmr.Projects) != len(timer.Projects) ||
|
||||
len(tmr.Contexts) != len(timer.Contexts) ||
|
||||
len(tmr.AdditionalTags) != len(timer.AdditionalTags) {
|
||||
return false
|
||||
}
|
||||
for _, prj := range tmr.Projects {
|
||||
if !timer.HasProject(prj) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, ctx := range tmr.Contexts {
|
||||
if !timer.HasContext(ctx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for key, val := range tmr.AdditionalTags {
|
||||
if !timer.HasTag(key) || timer.GetTag(key) != val {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
231
timerlist.go
231
timerlist.go
@ -6,32 +6,61 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimerList represents a list of timer.txt timer entries.
|
||||
// It is usually loasded from a whole timer.txt file.
|
||||
type TimerList []*Timer
|
||||
// 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 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) Size() int {
|
||||
return len([]*Timer(*timerlist))
|
||||
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()
|
||||
}
|
||||
|
||||
func (timerlist *TimerList) GetTimerSlice() []*Timer {
|
||||
return []*Timer(*timerlist)
|
||||
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 i := range *timerlist {
|
||||
t := ([]*Timer(*timerlist))[i]
|
||||
for _, t := range timerlist.Timers {
|
||||
if t.FinishDate.IsZero() {
|
||||
if t.StartDate.After(latest) {
|
||||
found = t
|
||||
@ -75,20 +104,100 @@ func (timerlist *TimerList) GetTimersWithProject(project string) *TimerList {
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, v := range timerlist.Timers {
|
||||
if v.FinishDate.IsZero() {
|
||||
t = append(t, v)
|
||||
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 {
|
||||
for _, timer := range timerlist.Timers {
|
||||
ret += fmt.Sprintf("%s\n", timer.String())
|
||||
}
|
||||
return ret
|
||||
@ -96,24 +205,25 @@ func (timerlist *TimerList) String() string {
|
||||
|
||||
// AddTimer prepends a Timer to the current TimerList and takes care to set the Timer.Id correctly
|
||||
func (timerlist *TimerList) AddTimer(timer *Timer) {
|
||||
// The new timer is going to be id 1
|
||||
timer.Id = 1
|
||||
for _, t := range *timerlist {
|
||||
// Everything else gets incremented
|
||||
t.Id++
|
||||
}
|
||||
// Now prepend the timer to the slice
|
||||
*timerlist = append(*timerlist, &Timer{})
|
||||
copy((*timerlist)[1:], (*timerlist)[0:])
|
||||
(*timerlist)[0] = 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 {
|
||||
if ([]*Timer(*timerlist))[i].Id == id {
|
||||
return ([]*Timer(*timerlist))[i], nil
|
||||
for i := range timerlist.Timers {
|
||||
if timerlist.Timers[i].Id == id {
|
||||
return timerlist.Timers[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("timer not found")
|
||||
@ -122,38 +232,40 @@ func (timerlist *TimerList) GetTimer(id int) (*Timer, error) {
|
||||
// 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 {
|
||||
var newList TimerList
|
||||
found := false
|
||||
for _, t := range *timerlist {
|
||||
if t.Id != id {
|
||||
newList = append(newList, t)
|
||||
} else {
|
||||
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 = newList
|
||||
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 {
|
||||
var newList TimerList
|
||||
found := false
|
||||
for _, t := range *timerlist {
|
||||
if t.String() != timer.String() {
|
||||
newList = append(newList, t)
|
||||
} else {
|
||||
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 = newList
|
||||
timerlist.Timers = append(timerlist.Timers[:remIdx], timerlist.Timers[remIdx+1:]...)
|
||||
timerlist.refresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -177,18 +289,26 @@ func (timerlist *TimerList) ArchiveTimerToFile(timer Timer, filename string) err
|
||||
// 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 {
|
||||
for _, t := range timerlist.Timers {
|
||||
if predicate(t) {
|
||||
newList = append(newList, 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 = []*Timer{} // Empty timerlist
|
||||
timerlist.Timers = []*Timer{} // Empty timerlist
|
||||
timerId := 1
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
@ -202,12 +322,13 @@ func (timerlist *TimerList) LoadFromFile(file *os.File) error {
|
||||
return err
|
||||
}
|
||||
timer.Id = timerId
|
||||
*timerlist = append(*timerlist, timer)
|
||||
timerId++
|
||||
timerlist.Timers = append(timerlist.Timers, timer)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
timerlist.refresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -216,10 +337,14 @@ func (timerlist *TimerList) WriteToFile(file *os.File) error {
|
||||
writer := bufio.NewWriter(file)
|
||||
_, err := writer.WriteString(timerlist.String())
|
||||
writer.Flush()
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timerlist.ResetIds()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteToFile writes a TimerList to *os.File.
|
||||
// LoadFromFilename loads a TimerList from the filename.
|
||||
func (timerlist *TimerList) LoadFromFilename(filename string) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
@ -231,16 +356,20 @@ func (timerlist *TimerList) LoadFromFilename(filename string) error {
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
func LoadFromFile(file *os.File) (*TimerList, error) {
|
||||
timerlist := TimerList{}
|
||||
if err := timerlist.LoadFromFile(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return timerlist, nil
|
||||
return &timerlist, nil
|
||||
}
|
||||
|
||||
// WriteToFile writes a TimerList to *os.File.
|
||||
@ -249,12 +378,12 @@ func WriteToFile(timerlist *TimerList, file *os.File) error {
|
||||
}
|
||||
|
||||
// LoadFromFilename loads and returns a TimerList from a file (most likely called "timer.txt")
|
||||
func LoadFromFilename(filename string) (TimerList, error) {
|
||||
func LoadFromFilename(filename string) (*TimerList, error) {
|
||||
timerlist := TimerList{}
|
||||
if err := timerlist.LoadFromFilename(filename); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return timerlist, nil
|
||||
return &timerlist, nil
|
||||
}
|
||||
|
||||
// WriteToFilename write a TimerList to the specified file (most likely called "timer.txt")
|
||||
|
Loading…
Reference in New Issue
Block a user