diff options
author | Alexander Kiryukhin <a.kiryukhin@mail.ru> | 2021-12-05 17:46:53 +0300 |
---|---|---|
committer | Alexander Kiryukhin <a.kiryukhin@mail.ru> | 2021-12-05 17:46:53 +0300 |
commit | bcdbe68ecde049ef62343584bcc26840322c4864 (patch) | |
tree | 4a02b4da5db29ab3f3526ff475db859293a97646 /internal |
Initial commit
Diffstat (limited to 'internal')
-rw-r--r-- | internal/tracker/tracker.go | 132 | ||||
-rw-r--r-- | internal/tracker/tracker_test.go | 68 | ||||
-rw-r--r-- | internal/tracker/types.go | 31 |
3 files changed, 231 insertions, 0 deletions
diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go new file mode 100644 index 0000000..6d9f058 --- /dev/null +++ b/internal/tracker/tracker.go @@ -0,0 +1,132 @@ +package tracker + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/spf13/afero" +) + +var ( + ErrEntryAlreadyExists = fmt.Errorf("entry with this title already exists") + ErrActivityNotFound = fmt.Errorf("there is no activity with given id") +) + +type Tracker struct { + fs afero.Fs + document *Document +} + +const FileName = "gotrack.json" + +func New(fs afero.Fs) (*Tracker, error) { + t := &Tracker{fs: fs} + if err := t.load(); err != nil { + return nil, err + } + return t, nil +} + +func (t *Tracker) Add(title string, tags []string, contexts []string) (int, error) { + for _, activity := range t.document.Activities { + if strings.ToLower(strings.Trim(activity.Title, " ")) == strings.ToLower(strings.Trim(title, " ")) { + return 0, ErrEntryAlreadyExists + } + } + activity := Activity{ + ID: t.document.LastKey + 1, + Title: title, + Tags: tags, + Context: contexts, + Spans: []*Span{}, + } + + t.document = &Document{ + LastKey: activity.ID, + Activities: append(t.document.Activities, &activity), + } + + return t.document.LastKey, t.save() +} + +func (t *Tracker) Start(id int, comment string) error { + if err := t.Stop(id); err != nil { + return err + } + span := &Span{ + Start: time.Now(), + Comment: comment, + } + activity := t.Activity(id) + activity.Spans = append(activity.Spans, span) + return t.save() +} + +func (t *Tracker) Stop(id int) error { + activity := t.Activity(id) + if activity == nil { + return ErrActivityNotFound + } + if span := activity.Started(); span != nil { + t := time.Now() + span.Stop = &t + } + return t.save() +} + +func (t *Tracker) List(all bool) []*Activity { + if all { + return t.document.Activities + } + return filterActivities(t.document.Activities, func(a *Activity) bool { + return a.Started() != nil + }) +} + +func (t *Tracker) Activity(id int) *Activity { + for _, activity := range t.document.Activities { + if activity.ID == id { + return activity + } + } + return nil +} + +func (t *Tracker) load() (err error) { + t.document = new(Document) + f, err := t.fs.Open(FileName) + defer func() { + err = f.Close() + }() + if err != nil { + f, err = t.fs.Create(FileName) + if err != nil { + return err + } + return nil + } + return json.NewDecoder(f).Decode(t.document) +} + +func (t *Tracker) save() (err error) { + f, err := t.fs.Create(FileName) + if err != nil { + return err + } + defer func() { + err = f.Close() + }() + return json.NewEncoder(f).Encode(t.document) +} + +func filterActivities(list []*Activity, filter func(activity *Activity) bool) []*Activity { + var filtered []*Activity + for _, activity := range list { + if filter(activity) { + filtered = append(filtered, activity) + } + } + return filtered +} diff --git a/internal/tracker/tracker_test.go b/internal/tracker/tracker_test.go new file mode 100644 index 0000000..c116174 --- /dev/null +++ b/internal/tracker/tracker_test.go @@ -0,0 +1,68 @@ +package tracker + +import ( + "testing" + "time" + + "github.com/spf13/afero" +) + +func TestTracker(t *testing.T) { + fs := afero.NewMemMapFs() + tracker, err := New(fs) + if err != nil { + t.Errorf("Must no err, got %v", err) + } + tid1, err := tracker.Add("activity 1", []string{}, []string{}) + if err != nil { + t.Errorf("Must no err, got %v", err) + } + if tid1 != 1 { + t.Errorf("Expected task id = 1, got %d", tid1) + } + tid2, err := tracker.Add("activity 2", []string{"tag1", "tag2"}, []string{"context1"}) + if err != nil { + t.Errorf("Must no err, got %v", err) + } + if tid2 != 2 { + t.Errorf("Expected task id = 2, got %d", tid2) + } + if err = tracker.Start(tid1, "work 1"); err != nil { + t.Errorf("Must no err, got %v", err) + } + list := tracker.List(false) + if len(list) != 1 { + t.Errorf("List %v expected to be from 1 element", list) + } + list2 := tracker.List(true) + if len(list2) != 2 { + t.Errorf("List %v expected to be from 2 elements", list2) + } + <- time.After(2 * time.Second) + if err := tracker.Stop(tid1); err != nil { + t.Errorf("Must no err, got %v", err) + } + list3 := tracker.List(false) + if len(list3) != 0 { + t.Errorf("List %v expected to be from 0 element", list3) + } + list4 := tracker.List(true) + for _, activity := range list4 { + if activity.ID != tid1 { + continue + } + if len(activity.Spans) != 1 { + t.Errorf("List %v expected to be from 1 element", activity.Spans) + } + sp := activity.Spans[0] + if sp.Stop == nil { + t.Errorf("Span end time must be not empty") + } + if !sp.Stop.After(sp.Start) { + t.Errorf("End span must be after start time") + } + if int(sp.Stop.Sub(sp.Start).Seconds()) != 2 { + t.Errorf("difference between %v and %v must be 2 seconds, got %f", sp.Start, sp.Stop, sp.Stop.Sub(sp.Start).Seconds()) + } + } +} diff --git a/internal/tracker/types.go b/internal/tracker/types.go new file mode 100644 index 0000000..22837fa --- /dev/null +++ b/internal/tracker/types.go @@ -0,0 +1,31 @@ +package tracker + +import "time" + +type Activity struct { + ID int `json:"id"` + Title string `json:"title"` + Tags []string `json:"tags"` + Context []string `json:"context"` + Spans []*Span `json:"spans"` +} + +func (a *Activity) Started() *Span { + for _, span := range a.Spans { + if span.Stop == nil { + return span + } + } + return nil +} + +type Span struct { + Start time.Time `json:"start"` + Stop *time.Time `json:"stop,omitempty"` + Comment string `json:"comment,omitempty"` +} + +type Document struct { + LastKey int `json:"last"` + Activities []*Activity `json:"activities"` +} |