package main import ( "fmt" "os" "strings" timertxt "git.bullercodeworks.com/brian/go-timertxt" userConfig "github.com/br0xen/user-config" ) type AppState struct { Name string Version int config *userConfig.Config directory string fileTimer string fileDone string fileReport string ValidOperations map[string][]string OpFuncs map[string]func([]string) int TimerList *timertxt.TimerList DoneList *timertxt.TimerList screens []Screen } func NewApp() *AppState { app := &AppState{Name: AppName, Version: AppVersion} app.initialize() app.doVersionCheck() if err := app.LoadTimerList(); err != nil { if len(os.Args) > 1 && os.Args[1] != "--reinit" { panic(err) } } return app } func (a *AppState) filterList(list *timertxt.TimerList, filter string) *timertxt.TimerList { return list.Filter(a.getFilterPredicate(filter)) } func (a *AppState) run(parms []string) int { if len(parms) == 0 || parms[0] == "ui" { // UI Mode return uiLoop() } if fn, ok := a.OpFuncs[parms[0]]; ok { return fn(parms[1:]) } fmt.Println("Unknown Command") return 1 } func (a *AppState) getTimerFile() string { return a.directory + a.fileTimer } func (a *AppState) getDoneFile() string { return a.directory + a.fileDone } func (a *AppState) getReportFile() string { return a.directory + a.fileReport } func (a *AppState) addOperation(name string, desc []string, fn func([]string) int) { a.ValidOperations[name] = desc a.OpFuncs[name] = fn } func (a *AppState) getTimerString(timer timertxt.Timer) string { return fmt.Sprintf("%d. %s", timer.Id, strings.TrimPrefix(timer.String(), "x ")) } func (a *AppState) getDoneTimerString(timer timertxt.Timer) string { return fmt.Sprintf("--. %s", timer.String()) } func (a *AppState) doVersionCheck() { confVer, _ := a.config.GetInt("version") for confVer < a.Version { confVer = a.migrate(confVer, a.Version) } 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 } switch from { case 0: a.initializeConfig() return 1 } // If we get all the way down here, we _must_ be done. return to } func (a *AppState) initialize() { var err error a.config, err = userConfig.NewConfig(a.Name) if err != nil { panic(err) } a.ValidOperations = make(map[string][]string) a.OpFuncs = make(map[string]func([]string) int) a.addOperation("i3status", []string{ "i3status - Output JSON for i3status-rust to consume", }, a.opI3Status, ) a.addOperation("toggle", []string{ "toggle - Pause/Resume the most recent timer", }, a.opToggleTimer, ) 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", " --a - Include done.txt file", " [start] - List entries after this date", " [end] - List entries before this date", " [@contexts] - List entries with the given contexts", " [+projects] - List entries with the given projects", }, a.opListTimers, ) a.addOperation("lsa", []string{ "lsa [start] [end] [contexts] [projects] - Shortcut for 'ls --a'", }, func(args []string) int { return a.opListTimers(append([]string{"--a"}, args...)) }, ) a.addOperation("start", []string{ "start [time] [@contexts...] [+projects...] [tag:value...]", " - Start a timer with the given details", " If the first argument looks like a time,", " the timer will be started then (past or future)", }, a.opStartTimer, ) a.addOperation("stop", []string{ "stop [time] - Stops the current timer", " If the first argument looks like a time,", " the timer will be stopped then (past or future)", }, a.opStopTimer, ) a.addOperation("switch", []string{ "switch [time] [@contexts...] [+projects...] [tag:value...]", " - Stops all active timers and starts a new one", " with the given arguments", }, a.opSwitchTimer, ) a.addOperation("rm", []string{ "rm [id] - Removes the timer with the given id", " WARNING: CANNOT BE UNDONE", }, a.opRemoveTimer, ) a.addOperation("archive", []string{ "archive [id] [@context] [+project] - Archive the timer with the given id,context,project", }, a.opArchiveTimer, ) a.addOperation("status", []string{ "status - Prints the status of all active timers", }, a.opStatus, ) a.addOperation("mod", []string{ "mod [start=] [end=] [projects=] [contexts=]", " - Modify timer with id changes are absolute, so, e.g., if you pass a", " 'projects' tag, the projects on the timer will be replaced with the value", }, a.opModifyTimer, ) a.addOperation("fuzzyparse", []string{ "fuzzyparse [date string] - Parses the passed string and print the RFC3339 result (for testing)", }, a.opFuzzyParse, ) a.addOperation("--reinit", []string{ "--reinit - Reset all Configuration Settings", }, func(args []string) int { a.initializeConfig() return 0 }, ) a.addOperation("-h", []string{ "-h - Print this message", }, a.opPrintUsage, ) a.addOperation("help", []string{ "help - Print this message", }, a.opPrintUsage, ) a.addOperation("config", []string{ "config - Print or edit config", }, a.opConfig, ) a.addOperation("--h", []string{ "--h - Print this message", }, 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") a.fileReport = a.config.Get("reportfile") } func (a *AppState) initializeConfig() { fmt.Println("Initializing " + a.Name) for { var add string if a.directory != "" { add = " (" + a.directory + ")" } fmt.Println("Path to timer.txt" + add + ":") var resp string fmt.Scanln(&resp) if resp == "" && a.directory != "" { resp = a.directory } if resp != "" { if !strings.HasSuffix(resp, "/") { resp = resp + "/" } fmt.Println("Setting timer.txt directory to: " + resp) a.config.Set("directory", resp) break } } a.config.Set("timerfile", "timer.txt") a.config.Set("donefile", "done.txt") a.config.Set("reportfile", "report.txt") }