Initial Commit
This commit is contained in:
150
timer.go
Normal file
150
timer.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package timertxt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// DateLayout is used for formatting time.Time into timer.txt date format and vice-versa.
|
||||
DateLayout = time.RFC3339
|
||||
|
||||
addonTagRx = regexp.MustCompile(`(^|\s+)([\w-]+):(\S+)`) // Match additional tags date: '... due:2012-12-12 ...'
|
||||
contextRx = regexp.MustCompile(`(^|\s+)@(\S+)`) // Match contexts: '@Context ...' or '... @Context ...'
|
||||
projectRx = regexp.MustCompile(`(^|\s+)\+(\S+)`) // Match projects: '+Project...' or '... +Project ...')
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// String returns a complete timer string in timer.txt format.
|
||||
//
|
||||
// Contexts, Projects, and additional tags are alphabetically sorted,
|
||||
// and appended at the end in the following order:
|
||||
// Contexts, Projects, Tags
|
||||
//
|
||||
// For example:
|
||||
// "2019-02-15T11:43:00-0600 Working on Go Library @home @personal +timertxt customTag1:Important! due:Today"
|
||||
// "x 2019-02-15T10:00:00-0600 2019-02-15T06:00:00-0600 Creating Go Library Repo @home @personal +timertxt customTag1:Important! due:Today"
|
||||
func (timer Timer) String() string {
|
||||
var text string
|
||||
if timer.Finished {
|
||||
text += "x "
|
||||
if timer.HasFinishDate() {
|
||||
text += fmt.Sprintf("%s ", timer.FinishDate.Format(DateLayout))
|
||||
}
|
||||
}
|
||||
text += fmt.Sprintf("%s ", timer.StartDate.Format(DateLayout))
|
||||
text += timer.Notes
|
||||
if len(timer.Contexts) > 0 {
|
||||
sort.Strings(timer.Contexts)
|
||||
for _, context := range timer.Contexts {
|
||||
text += fmt.Sprintf(" @%s", context)
|
||||
}
|
||||
}
|
||||
if len(timer.Projects) > 0 {
|
||||
sort.Strings(timer.Projects)
|
||||
for _, project := range timer.Projects {
|
||||
text += fmt.Sprintf(" +%s", project)
|
||||
}
|
||||
}
|
||||
if len(timer.AdditionalTags) > 0 {
|
||||
// Sort map alphabetically by keys
|
||||
keys := make([]string, 0, len(timer.AdditionalTags))
|
||||
for key := range timer.AdditionalTags {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
text += fmt.Sprintf(" %s:%s", key, timer.AdditionalTags[key])
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// NewTimer creates a new empty Timer with default values. (StartDate is set to Now())
|
||||
func NewTimer() Timer {
|
||||
timer := Timer{}
|
||||
timer.StartDate = time.Now()
|
||||
return timer
|
||||
}
|
||||
|
||||
// ParseTimer parses the input text string into a Timer struct
|
||||
func ParseTimer(text string) (*Timer, error) {
|
||||
var err error
|
||||
timer := Timer{}
|
||||
timer.Original = strings.Trim(text, "\t\n\r ")
|
||||
originalParts := strings.Fields(timer.Original)
|
||||
|
||||
// Check for finished
|
||||
if originalParts[0] == "x" {
|
||||
timer.Finished = true
|
||||
// If it's finished, there _must_ be a finished date
|
||||
if timer.FinishDate, err = time.Parse(DateLayout, originalParts[1]); err != nil {
|
||||
return nil, errors.New("Timer marked finished, but failed to parse FinishDate: " + err.Error())
|
||||
}
|
||||
originalParts = originalParts[2:]
|
||||
}
|
||||
if timer.StartDate, err = time.Parse(DateLayout, originalParts[0]); err != nil {
|
||||
return nil, errors.New("Unable to parse StartDate: " + err.Error())
|
||||
}
|
||||
originalParts = originalParts[1:]
|
||||
var notes []string
|
||||
for _, v := range originalParts {
|
||||
if strings.HasPrefix("@", v) {
|
||||
// Contexts
|
||||
timer.Contexts = append(timer.Contexts, v)
|
||||
} else if strings.HasPrefix("+", v) {
|
||||
// Projects
|
||||
timer.Projects = append(timer.Projects, v)
|
||||
} else if strings.Contains(":", v) {
|
||||
// Additional tags
|
||||
tagPts := strings.Split(v, ":")
|
||||
if tagPts[0] != "" && tagPts[1] != "" {
|
||||
timer.AdditionalTags[tagPts[0]] = tagPts[1]
|
||||
}
|
||||
} else {
|
||||
notes = append(notes, v)
|
||||
}
|
||||
}
|
||||
timer.Notes = strings.Join(notes, " ")
|
||||
|
||||
return timer, nil
|
||||
}
|
||||
|
||||
// Timer returns a complete timer string in timer.txt format.
|
||||
// See *Timer.String() for further information
|
||||
func (timer *Timer) Timer() string {
|
||||
return timer.String()
|
||||
}
|
||||
|
||||
// Finish sets Timer.Finished to true if the timer hasn't already been finished.
|
||||
// Also sets Timer.FinishDate to time.Now()
|
||||
func (timer *Timer) Finish() bool {
|
||||
if !timer.Finished {
|
||||
timer.Finished = true
|
||||
timer.FinishDate = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// Reopen sets Timer.Finished to 'false' if the timer was finished
|
||||
// Also resets Timer.FinishDate
|
||||
func (timer *Timer) Reopen() {
|
||||
if timer.Finished {
|
||||
timer.Finished = false
|
||||
timer.FinishDate = time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC) // time.IsZero() value
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user