diff --git a/app_state.go b/app_state.go index 7d9da3b..c6c7ba9 100644 --- a/app_state.go +++ b/app_state.go @@ -86,6 +86,9 @@ func (a *AppState) doVersionCheck() { a.config.SetInt("version", confVer) } +// Data Migrations +// 0 -> 1: Initialize Config +// 1 -> 2: No migration Needed func (a *AppState) migrate(from, to int) int { if from == to { return to @@ -107,6 +110,17 @@ func (a *AppState) initialize() { } a.ValidOperations = make(map[string][]string) a.OpFuncs = make(map[string]func([]string) int) + a.addOperation("time", + []string{ + "time [contexts] [projects] - Only output the total time value", + " --a - Include done.txt file", + " [start] - List entries after this date", + " [end] - List entries before this date", + " [@contexts] - Filter entries with the given contexts", + " [+projects] - Filter entries with the given projects", + }, + a.opShowTime, + ) a.addOperation("ls", []string{ "ls [--a] [start] [end] [contexts] [projects] - List Timers", @@ -217,6 +231,12 @@ func (a *AppState) initialize() { }, a.opPrintUsage, ) + a.addOperation("editor", + []string{ + "editor - Open the timer file in $EDITOR", + }, + a.opEditor, + ) a.directory = a.config.Get("directory") a.fileTimer = a.config.Get("timerfile") a.fileDone = a.config.Get("donefile") diff --git a/main.go b/main.go index 92602aa..f0b96c5 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import "os" const ( AppName = "gime" - AppVersion = 1 + AppVersion = 2 DefRoundTo = "1m0s" ) diff --git a/timer_ops.go b/timer_ops.go index 847b83a..5146bd9 100644 --- a/timer_ops.go +++ b/timer_ops.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strconv" "strings" "time" @@ -9,6 +10,24 @@ import ( timertxt "github.com/br0xen/go-timertxt" ) +func (a *AppState) opEditor(args []string) int { + editor := os.Getenv("EDITOR") + if editor == "" { + fmt.Println("No $EDITOR set") + return 1 + } + fmt.Println(editor, a.directory+a.fileTimer) + return 0 + /* + err := exec.Command(editor, a.directory+a.fileTimer).Run() + if err != nil { + fmt.Println("Error opening editor: " + err.Error()) + return 1 + } + return 0 + */ +} + func (a *AppState) opStatus(args []string) int { if len(*a.TimerList.GetActiveTimers()) == 0 { fmt.Println("No timers running") @@ -28,6 +47,103 @@ func (a *AppState) opStatus(args []string) int { return 0 } +/** + * Just output the time given the filters + */ +func (a *AppState) opShowTime(args []string) int { + var includeArchive bool + var err error + start := time.Time{} + end := time.Now() + var contextFilters []string + var projectFilters []string + + var allFilters []func(timertxt.Timer) bool + + if len(args) > 0 { + contextFilters, args = getContextsFromSlice(args) + projectFilters, args = getProjectsFromSlice(args) + } + if len(args) > 0 { + if args[0] == "--a" { + includeArchive = true + args = args[1:] + } + } + if len(args) > 0 { + if start, err = parseFuzzyTime(args[0]); err != nil { + y, m, d := time.Now().Date() + start = time.Date(y, m, d, 0, 0, 0, 0, time.Now().Location()) + } else { + args = args[1:] + } + if len(args) > 0 { + if end, err = parseFuzzyTime(args[0]); err != nil { + y, m, d := time.Now().Date() + end = time.Date(y, m, d, 23, 59, 59, 0, time.Now().Location()) + } else { + args = args[1:] + } + } + } + if includeArchive { + if err = a.LoadDoneList(); err != nil { + fmt.Println("Error loading done.txt entries") + fmt.Println(err.Error()) + return 1 + } + } + + list := a.TimerList.GetTimersInRange(start, end) + if includeArchive { + *list = append(*list, (*a.DoneList.GetTimersInRange(start, end))...) + } + if len(contextFilters) > 0 { + allFilters = append(allFilters, func(t timertxt.Timer) bool { + for _, v := range contextFilters { + v = strings.TrimPrefix(v, "@") + if !t.HasContext(v) { + return false + } + } + return true + }) + } + if len(projectFilters) > 0 { + allFilters = append(allFilters, func(t timertxt.Timer) bool { + for _, v := range projectFilters { + v = strings.TrimPrefix(v, "+") + if !t.HasProject(v) { + return false + } + } + return true + }) + } + doFilters := func(t timertxt.Timer) bool { + for _, v := range allFilters { + if !v(t) { + return false + } + } + // If we made it all the way down here, it matches + return true + } + list = list.Filter(doFilters) + + var total time.Duration + for _, v := range *list { + dur := v.FinishDate.Sub(v.StartDate) + if v.FinishDate.IsZero() { + dur = time.Now().Sub(v.StartDate) + } + total += dur + } + total = total.Round(GetRoundToDuration()) + fmt.Printf("%.2f\n", DurationToDecimal(total)) + return 0 +} + /** * List timers for a given time span * By default, only list Today diff --git a/ui_loop.go b/ui_loop.go index 1c2a106..ebaf777 100644 --- a/ui_loop.go +++ b/ui_loop.go @@ -1,3 +1,4 @@ +// +build !windows package main import ( diff --git a/ui_loop_windows.go b/ui_loop_windows.go new file mode 100644 index 0000000..1e8cc12 --- /dev/null +++ b/ui_loop_windows.go @@ -0,0 +1,48 @@ +// +build windows +package main + +import ( + "fmt" + + termbox "github.com/nsf/termbox-go" +) + +func uiLoop() int { + err := termbox.Init() + if err != nil { + fmt.Println(err.Error()) + return 1 + } + termbox.SetOutputMode(termbox.Output256) + app.BuildScreens() + displayScreen := app.screens[ScreenMain] + bundle := Bundle{} + bundle.setValue(MainBundleListKey, MainBundleListRecent) + displayScreen.initialize(bundle) + app.layoutAndDrawScreen(displayScreen) + eventChan := make(chan termbox.Event) + go readUserInput(eventChan) + go checkForUpdate(eventChan) + for { + event := <-eventChan + if event.Type == termbox.EventKey { + if event.Key == termbox.KeyCtrlC { + break + } + newScreenIndex := displayScreen.handleKeyEvent(event) + if newScreenIndex < len(app.screens) { + displayScreen = app.screens[newScreenIndex] + app.layoutAndDrawScreen(displayScreen) + } else { + break + } + } + if event.Type == termbox.EventResize { + displayScreen.initialize(nil) + app.layoutAndDrawScreen(displayScreen) + } + } + termbox.Close() + // Any wrap up should be done here... + return 0 +}