diff --git a/cmd/gotime-cui/gotime-cui b/cmd/gotime-cui/gotime-cui new file mode 100755 index 0000000..eb582a7 Binary files /dev/null and b/cmd/gotime-cui/gotime-cui differ diff --git a/cmd/gotime-cui/main.go b/cmd/gotime-cui/main.go new file mode 100644 index 0000000..be1939d --- /dev/null +++ b/cmd/gotime-cui/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "os" + + "gogs.bullercodeworks.com/brian/gotime" +) + +func main() { + var dir string + /* + err := termbox.Init() + if err != nil { + panic(err) + } + defer termbox.Close() + */ + + if len(os.Args) > 1 { + dir = os.Args[1] + } + got := gotime.Create(dir) + t := got.GetAllTimers() + for i := range t { + fmt.Println(t[i].ToJsonString()) + } + fmt.Println("=== Start Timer ===") + ts := gotime.CreateStartTimerTxns(&t[len(t)-1]) + for i := range ts { + fmt.Println(ts[i].ToString()) + } + fmt.Println("=== Stop Timer ===") + ts = gotime.CreateStopTimerTxns(&t[len(t)-1]) + for i := range ts { + fmt.Println(ts[i].ToString()) + } +} diff --git a/gotime.go b/gotime.go new file mode 100644 index 0000000..a5b8505 --- /dev/null +++ b/gotime.go @@ -0,0 +1,185 @@ +package gotime + +import ( + "fmt" + "io/ioutil" + "strings" + "time" +) + +type GoTime struct { + Dir string + timers []Timer + files []string + timeFormat string + Tags []string +} + +// Create creates a new instance of GoTime +// with the given data directory +func Create(dir string) *GoTime { + g := &GoTime{Dir: dir} + g.timeFormat = "20060102T150405Z" + + g.files = g.getTimerFiles() + for _, f := range g.files { + g.addTimersFromFile(f) + } + g.ResetIds() + return g +} + +func (g *GoTime) ResetIds() { + // Now set IDs + id := 1 + for i := len(g.timers) - 1; i >= 0; i-- { + g.timers[i].SetId(id) + id++ + } +} + +// IsOn returns true if a timer is currently running +func (g *GoTime) IsOn() bool { + t := g.Status() + if t.End.IsZero() { + return true + } + return false +} + +// Status returns the most recent timer +func (g *GoTime) Status() *Timer { + if len(g.timers) == 0 { + return nil + } + return &g.timers[len(g.timers)-1] +} + +// IsRunning returns if the Status() is running +func (g *GoTime) IsRunning() bool { + return g.Status().IsRunning() +} + +func (g *GoTime) GetAllTimers() []Timer { + return g.timers +} + +// getTimerFiles Returns a string slice of all of the data file names +func (g *GoTime) getTimerFiles() []string { + var ret []string + // Timer files are all files in g.dir/data except undo.data + files, _ := ioutil.ReadDir(g.Dir + "/data/") + for _, f := range files { + if f.Name() != "undo.data" { + ret = append(ret, f.Name()) + } + } + return ret +} + +// addTimersFromFile Adds all timer lines from g.Dir/data/f and adds a timer for it +func (g *GoTime) addTimersFromFile(f string) error { + content, err := ioutil.ReadFile(g.Dir + "/data/" + f) + if err != nil { + fmt.Println(err) + return err + } + cntString := strings.TrimSpace(string(content)) + + lines := strings.Split(cntString, "\n") + for i := range lines { + if err = g.AddTimerFromString(lines[i]); err != nil { + return err + } + } + return err +} + +// AddTimerFromString takes a string in the format of the lines from the data file +// and builds a timer from it. +func (g *GoTime) AddTimerFromString(st string) error { + var err error + t := new(Timer) + flds := strings.Fields(st) + if len(flds) > 1 && flds[0] == "inc" { + // Start Time + if len(flds) >= 2 { + if t.Beg, err = time.Parse(g.timeFormat, flds[1]); err != nil { + return err + } + } + // End Time + if len(flds) >= 4 && flds[2] == "-" { + if t.End, err = time.Parse(g.timeFormat, flds[3]); err != nil { + return err + } + } + var inTags bool + for i := range flds { + if flds[i] == "#" { + inTags = true + continue + } + if inTags { + tg := strings.Trim(flds[i], "\"") + t.Tags = append(t.Tags, tg) + g.AddTag(tg) + } + } + if !t.Beg.IsZero() { + g.timers = append(g.timers, *t) + } + } + return err +} + +// AddTag adds a tag to list of all used tags +func (g *GoTime) AddTag(tg string) { + if !g.HasTag(tg) { + g.Tags = append(g.Tags, tg) + } +} + +// HasTag returns if this tag is in our list +func (g *GoTime) HasTag(tg string) bool { + for i := range g.Tags { + if g.Tags[i] == tg { + return true + } + } + return false +} + +// Start starts a new timer. If one is already active, it stops it first +func (g *GoTime) StartTimer() *Timer { + if g.IsRunning() { + g.StopTimer() + } + t := new(Timer) + t.Beg = time.Now() + g.timers = append(g.timers, *t) + // TODO: Add a line to the YYYY-MM.data file + // TODO: Update the Undo File + g.ResetIds() + + return g.Status() +} + +// Stop stops the currently active timer +func (g *GoTime) StopTimer() *Timer { + if g.IsRunning() { + g.Status().Stop() + } + // TODO: Add a stop time to the last line in the YYYY-MM.data file + // TODO: Update the Undo File + return g.Status() +} + +// AddTag adds a new tag to the most recent timer +func (g *GoTime) AddTagToCurrentTimer(tg string) *Timer { + g.Status().AddTag(tg) + // TODO: Add the tag to the last line in the YYYY-MM.data file + // TODO: Update the Undo File + + return g.Status() +} diff --git a/timer.go b/timer.go new file mode 100644 index 0000000..cf35bfc --- /dev/null +++ b/timer.go @@ -0,0 +1,123 @@ +package gotime + +import ( + "errors" + "strings" + "time" +) + +type Timer struct { + Id int + Beg time.Time + End time.Time + Tags []string +} + +func (t *Timer) Start() { + if t.Beg.IsZero() { + t.Beg = time.Now() + } +} + +func (t *Timer) Stop() { + if t.End.IsZero() { + t.End = time.Now() + } +} + +func (t *Timer) SetId(i int) { + t.Id = i +} + +func (t *Timer) SetStart(tm time.Time) error { + if !t.End.IsZero() && !tm.Before(t.End) { + return errors.New("Beginning time must be before End") + } + t.Beg = tm + return nil +} + +func (t *Timer) SetEnd(tm time.Time) error { + if !tm.After(t.Beg) { + return errors.New("End time must be after Beginning") + } + t.End = tm + return nil +} + +func (t *Timer) IsRunning() bool { + return !t.Beg.IsZero() && t.End.IsZero() +} + +// AddTag adds the tag tg to this timer +func (t *Timer) AddTag(tg string) { + if !t.HasTag(tg) { + t.Tags = append(t.Tags, tg) + } +} + +// RemoveTag removes the tag tg from this timer +func (t *Timer) RemoveTag(tg string) { + for i := 0; i < len(t.Tags); i++ { + if t.Tags[i] == tg { + t.Tags = append(t.Tags[:i], t.Tags[i+1:]...) + } + } +} + +// HasTag returns if this timer has the tag +func (t *Timer) HasTag(tg string) bool { + for i := range t.Tags { + if t.Tags[i] == tg { + return true + } + } + return false +} + +func (t *Timer) ToString() string { + if t.Beg.IsZero() { + return "" + } + timeFormat := "20060102T150405Z" + + ret := "inc " + t.Beg.Format(timeFormat) + if !t.End.IsZero() { + ret += " - " + t.End.Format(timeFormat) + } + if len(t.Tags) > 0 { + ret += " # " + for i := range t.Tags { + if strings.Contains(t.Tags[i], " ") { + ret += "\"" + t.Tags[i] + "\"" + } + ret += " " + } + ret = ret[:len(ret)-1] + } + return ret +} + +func (t *Timer) ToJsonString() string { + if t.Beg.IsZero() { + return "" + } + timeFormat := "20060102T150405Z" + + ret := "{\"start\":\"" + t.Beg.Format(timeFormat) + "\"," + if !t.End.IsZero() { + ret += "\"end\":\"" + t.End.Format(timeFormat) + "\"," + } + if len(t.Tags) > 0 { + ret += "\"tags\":[" + for i := range t.Tags { + ret += "\"" + t.Tags[i] + "\"," + } + // Move trailing ',' to end + ret = ret[:len(ret)-1] + "]," + + } + // Trim trailing , + ret = ret[:len(ret)-1] + "}" + return ret +} diff --git a/transaction.go b/transaction.go new file mode 100644 index 0000000..c754ac5 --- /dev/null +++ b/transaction.go @@ -0,0 +1,73 @@ +package gotime + +import "time" + +type Transaction struct { + tp string + before *Timer + after *Timer +} + +func CreateStartTimerTxns(tmr *Timer) []Transaction { + t := CreateIntervalTxn() + t.after = tmr + return []Transaction{*t} +} + +func CreateStopTimerTxns(tmr *Timer) []Transaction { + t1 := CreateIntervalTxn() + t1.before = tmr + t1.before.End = *new(time.Time) + + t2 := CreateIntervalTxn() + t2.after = tmr + + return []Transaction{*t1, *t2} +} + +func CreateIntervalTxn() *Transaction { + t := new(Transaction) + t.tp = "interval" + t.before = new(Timer) + t.after = new(Timer) + return t +} + +func (t *Transaction) SetBefore(tmr *Timer) { + t.before = tmr +} + +func (t *Transaction) SetAfter(tmr *Timer) { + t.after = tmr +} + +func (t *Transaction) ToString() string { + ret := "txn:\n" + ret += " type: " + t.tp + "\n" + ret += " before: " + t.before.ToJsonString() + "\n" + ret += " after: " + t.after.ToJsonString() + "\n" + return ret +} + +/* + * START Transaction + +txn: + type: interval + before: + after: {"start":"20170111T223025Z","tags":["bcw","bcw-gotime","work"]} + + * + * + * STOP Transaction + +txn: + type: interval + before: {"start":"20170111T201826Z","tags":["bcw","bcw-gotime","work"]} + after: +txn: + type: interval + before: + after: {"start":"20170111T201826Z","end":"20170111T221747Z","tags":["bcw","bcw-gotime","work"]} + +*/