Remove commands from library
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -24,3 +24,6 @@ _testmain.go | ||||
| *.test | ||||
| *.prof | ||||
|  | ||||
| # Gomobile files | ||||
| gime-sources.jar | ||||
| gime.aar | ||||
|   | ||||
| @@ -1,287 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.bullercodeworks.com/brian/gime" | ||||
| 	userConfig "github.com/br0xen/user-config" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	AppName       = "gime" | ||||
| 	AppVersion    = 1 | ||||
| 	DefDBName     = "gime.db" | ||||
| 	DefArchDBName = "gimearch.db" | ||||
| ) | ||||
|  | ||||
| var validOperations map[string][]string | ||||
| var opFuncs map[string]func([]string) int | ||||
| var timeEntries *gime.TimeEntryCollection | ||||
| var gdb *gime.GimeDB | ||||
| var cfg *userConfig.Config | ||||
|  | ||||
| var fuzzyFormats []string | ||||
|  | ||||
| func main() { | ||||
| 	var ret int | ||||
| 	initialize() | ||||
| 	var parms []string | ||||
|  | ||||
| 	if len(os.Args) > 1 { | ||||
| 		parms = os.Args[1:] | ||||
| 		parms[0] = matchParameter(parms[0]) | ||||
| 	} else { | ||||
| 		// If no parameters were passed, 'help' | ||||
| 		parms = append(parms, "help") | ||||
| 	} | ||||
|  | ||||
| 	if fn, ok := opFuncs[parms[0]]; ok { | ||||
| 		ret = fn(parms[1:]) | ||||
| 	} else { | ||||
| 		fmt.Println("Unknown command") | ||||
| 		ret = 1 | ||||
| 	} | ||||
| 	os.Exit(ret) | ||||
| } | ||||
|  | ||||
| func loadRecentTimeEntries() { | ||||
| 	timeEntries = gdb.LoadTimeEntryCollection(gime.TypeRecent) | ||||
| } | ||||
|  | ||||
| func cmdPrintHelp(args []string) int { | ||||
| 	//if len(args) == 0 { | ||||
| 	fmt.Println("gime-export - An export utility for the gime application\n") | ||||
| 	fmt.Println("Usage: gime-export [format] [[start date/time]-[end date/time]] [tags...]") | ||||
| 	for _, v := range validOperations { | ||||
| 		for vi := range v { | ||||
| 			fmt.Println("  ", v[vi]) | ||||
| 		} | ||||
| 		fmt.Println("") | ||||
| 	} | ||||
| 	//} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func cmdPrintFHGTimesheet(args []string) int { | ||||
| 	loadRecentTimeEntries() | ||||
|  | ||||
| 	var beg, end time.Time | ||||
| 	searchTags := []string{} | ||||
|  | ||||
| 	for _, opt := range args { | ||||
| 		var tmpBeg, tmpEnd time.Time | ||||
|  | ||||
| 		// Do our best to figure out what timers the user wants to list | ||||
| 		var err error | ||||
| 		if strings.Contains(opt, "-") { | ||||
| 			pts := strings.Split(opt, "-") | ||||
| 			if len(pts[0]) > 0 { | ||||
| 				// This should be the starting date | ||||
| 				tmpBeg, err = parseFuzzyTime(pts[0]) | ||||
| 				if err != nil { | ||||
| 					// We couldn't parse it as a time, | ||||
| 					// Probably this is just a tag | ||||
| 					searchTags = append(searchTags, opt) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			if len(pts[1]) > 0 { | ||||
| 				// This should be the ending date | ||||
| 				tmpEnd, err = parseFuzzyTime(pts[1]) | ||||
| 				if err != nil { | ||||
| 					searchTags = append(searchTags, opt) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			// Tag filters | ||||
| 			searchTags = append(searchTags, opt) | ||||
| 		} | ||||
| 		if !tmpBeg.IsZero() || !tmpEnd.IsZero() { | ||||
| 			beg, end = tmpBeg, tmpEnd | ||||
| 		} | ||||
| 	} | ||||
| 	if end.IsZero() { | ||||
| 		end = time.Now() | ||||
| 	} | ||||
|  | ||||
| 	timeSpanFilter := func(t *gime.TimeEntry) bool { | ||||
| 		return t.GetStart().After(beg) && t.GetEnd().Before(end) | ||||
| 	} | ||||
| 	tagFilter := func(t *gime.TimeEntry) bool { | ||||
| 		for i := range searchTags { | ||||
| 			if !t.HasTag(searchTags[i]) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	compoundFilter := func(t *gime.TimeEntry) bool { | ||||
| 		// Otherwise we want to filter timespan and tags | ||||
| 		return timeSpanFilter(t) && tagFilter(t) | ||||
| 	} | ||||
|  | ||||
| 	ttl := fmt.Sprintf("%s - %s", beg.Format("2006/01/02"), end.Format("2006/01/02")) | ||||
| 	if len(searchTags) > 0 { | ||||
| 		ttl = fmt.Sprint(ttl, " ", searchTags) | ||||
| 	} | ||||
| 	fmt.Println(ttl) | ||||
|  | ||||
| 	timerCollection := filterTimerCollection(timeEntries, compoundFilter) | ||||
|  | ||||
| 	dayTimes := make(map[string]float64) | ||||
| 	for i := beg; i.Before(end); i = i.Add(time.Hour * 24) { | ||||
| 		dayTimes[i.Format("2006/01/02")] = 0 | ||||
| 	} | ||||
|  | ||||
| 	for i := timerCollection.Length() - 1; i >= 0; i-- { | ||||
| 		wrkTimer := timerCollection.Get(i) | ||||
| 		dur := wrkTimer.GetEnd().Sub(wrkTimer.GetStart()).Round(time.Minute * 15) | ||||
| 		dayTimes[wrkTimer.GetStart().Format("2006/01/02")] += (dur.Minutes() / 60) | ||||
| 	} | ||||
|  | ||||
| 	out := [2]string{"", ""} | ||||
| 	for i := beg; i.Before(end); i = i.Add(time.Hour * 24) { | ||||
| 		dayString := i.Format("2006/01/02") | ||||
| 		out[0] += fmt.Sprint(dayString, ",") | ||||
| 		out[1] += fmt.Sprint(dayTimes[dayString], ",") | ||||
| 	} | ||||
| 	fmt.Println(out[0][:len(out[0])-1]) | ||||
| 	fmt.Println(out[1][:len(out[1])-1]) | ||||
|  | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func initialize() { | ||||
| 	var err error | ||||
| 	validOperations = make(map[string][]string) | ||||
| 	opFuncs = make(map[string]func([]string) int) | ||||
|  | ||||
| 	opFuncs["help"] = cmdPrintHelp | ||||
| 	validOperations["help"] = []string{ | ||||
| 		"help                       - Prints this", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["fhgts"] = cmdPrintFHGTimesheet | ||||
| 	validOperations["fhgts"] = []string{ | ||||
| 		"fhgts [duration] [tags]    - Output entries in Flint Hills Group timesheet format", | ||||
| 		"                             (csv, daily totals, decimal format, 1/4 hour rounding)", | ||||
| 	} | ||||
|  | ||||
| 	// Load the Config | ||||
| 	cfg, err = userConfig.NewConfig(AppName) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err.Error()) | ||||
| 		fmt.Println("Creating new config") | ||||
| 		cfg.Save() | ||||
| 	} | ||||
| 	// If dbdir isn't set, set it to the config directory | ||||
| 	if cfg.Get("dbdir") == "" { | ||||
| 		cfg.Set("dbdir", cfg.GetConfigPath()+"/") | ||||
| 	} | ||||
| 	// If dbname isn't set, set it to the default database filename | ||||
| 	if cfg.Get("dbname") == "" { | ||||
| 		cfg.Set("dbname", DefDBName) | ||||
| 	} | ||||
| 	// If dbarchivename isn't set, set it to the default archive database filename | ||||
| 	if cfg.Get("dbarchname") == "" { | ||||
| 		cfg.Set("dbarchname", DefArchDBName) | ||||
| 	} | ||||
| 	if gdb, err = gime.LoadDatabase(cfg.Get("dbdir"), cfg.Get("dbname"), cfg.Get("dbarchname")); err != nil { | ||||
| 		fmt.Println("Error loading the database") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	fuzzyFormats = []string{ | ||||
| 		"1504", | ||||
| 		"15:04", // Kitchen, 24hr | ||||
| 		time.Kitchen, | ||||
| 		time.RFC3339, | ||||
| 		"2006-01-02T15:04:05", // RFC3339 without timezone | ||||
| 		"2006-01-02T15:04",    // RFC3339 without seconds or timezone | ||||
| 		time.Stamp, | ||||
| 		"02 Jan 06 15:04:05", // RFC822 with second | ||||
| 		time.RFC822, | ||||
| 		"01/02/2006 15:04",    // U.S. Format | ||||
| 		"01/02/2006 15:04:05", // U.S. Format with seconds | ||||
| 		"01/02/06 15:04",      // U.S. Format, short year | ||||
| 		"01/02/06 15:04:05",   // U.S. Format, short year, with seconds | ||||
| 		"2006-01-02", | ||||
| 		"2006-01-02 15:04", | ||||
| 		"2006-01-02 15:04:05", | ||||
| 		"20060102", | ||||
| 		"20060102 15:04", | ||||
| 		"20060102 15:04:05", | ||||
| 		"20060102 1504", | ||||
| 		"20060102 150405", | ||||
| 		"20060102T15:04", | ||||
| 		"20060102T15:04:05", | ||||
| 		"20060102T1504", | ||||
| 		"20060102T150405", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func matchParameter(in string) string { | ||||
| 	var chkParms []string | ||||
| 	for k := range validOperations { | ||||
| 		chkParms = append(chkParms, k) | ||||
| 	} | ||||
| 	var nextParms []string | ||||
| 	for i := range in { | ||||
| 		for _, p := range chkParms { | ||||
| 			if p[i] == in[i] { | ||||
| 				nextParms = append(nextParms, p) | ||||
| 			} | ||||
| 		} | ||||
| 		// If we get here and there is only one parameter left, return it | ||||
| 		chkParms = nextParms | ||||
| 		if len(nextParms) == 1 { | ||||
| 			break | ||||
| 		} | ||||
| 		// Otherwise, loop | ||||
| 		nextParms = []string{} | ||||
| 	} | ||||
| 	if len(chkParms) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return chkParms[0] | ||||
| } | ||||
|  | ||||
| func parseFuzzyTime(t string) (time.Time, error) { | ||||
| 	var ret time.Time | ||||
| 	var err error | ||||
| 	for i := range fuzzyFormats { | ||||
| 		ret, err = time.Parse(fuzzyFormats[i], t) | ||||
| 		if err == nil { | ||||
| 			// Make sure it's in the local timezone | ||||
| 			tz := time.Now().Format("Z07:00") | ||||
| 			t = ret.Format("2006-01-02T15:04:05") + tz | ||||
| 			if ret, err = time.Parse(time.RFC3339, t); err != nil { | ||||
| 				return ret, err | ||||
| 			} | ||||
| 			// Check for zero on year/mo/day | ||||
| 			if ret.Year() == 0 && ret.Month() == time.January && ret.Day() == 1 { | ||||
| 				ret = ret.AddDate(time.Now().Year(), int(time.Now().Month())-1, time.Now().Day()-1) | ||||
| 			} | ||||
| 			return ret, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return time.Time{}, errors.New("Unable to parse time: " + t) | ||||
| } | ||||
|  | ||||
| // filterTimerCollection takes a collection and a function that it runs every entry through | ||||
| // If the function returns true for the entry, it adds it to a new collection to be returned | ||||
| func filterTimerCollection(c *gime.TimeEntryCollection, fn func(t *gime.TimeEntry) bool) *gime.TimeEntryCollection { | ||||
| 	ret := new(gime.TimeEntryCollection) | ||||
| 	for i := 0; i < c.Length(); i++ { | ||||
| 		if fn(c.Get(i)) { | ||||
| 			ret.Push(c.Get(i)) | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
							
								
								
									
										2
									
								
								cmd/gime/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								cmd/gime/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +0,0 @@ | ||||
| gime | ||||
| *.db | ||||
| @@ -1,4 +0,0 @@ | ||||
| gime | ||||
| ==== | ||||
|  | ||||
| This folder contains the cli application for gime | ||||
| @@ -1,251 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.bullercodeworks.com/brian/gime" | ||||
| ) | ||||
|  | ||||
| // filterTimerCollection takes a collection and a function that it runs every entry through | ||||
| // If the function returns true for the entry, it adds it to a new collection to be returned | ||||
| func filterTimerCollection(c *gime.TimeEntryCollection, fn func(t *gime.TimeEntry) bool) *gime.TimeEntryCollection { | ||||
| 	ret := new(gime.TimeEntryCollection) | ||||
| 	for i := 0; i < c.Length(); i++ { | ||||
| 		if fn(c.Get(i)) { | ||||
| 			ret.Push(c.Get(i)) | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func TimerCollectionToString(c *gime.TimeEntryCollection) string { | ||||
| 	var ret string | ||||
| 	for i := 0; i < c.Length(); i++ { | ||||
| 		ret += TimerToString(c.Get(i)) | ||||
| 		if i < c.Length()-1 { | ||||
| 			ret += "\n" | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // TimerToString takes a TimeEntry and gives a nicely formatted string | ||||
| func TimerToString(t *gime.TimeEntry) string { | ||||
| 	var ret string | ||||
| 	var end string | ||||
| 	if t.StartsToday() { | ||||
| 		ret = t.GetStart().Format("15:04 - ") | ||||
| 		end = "**:**" | ||||
| 	} else { | ||||
| 		ret = t.GetStart().Format("2006/01/02 15:04:05 - ") | ||||
| 		end = "**:**:**" | ||||
| 	} | ||||
| 	if !t.GetEnd().IsZero() { | ||||
| 		if t.EndsToday() { | ||||
| 			end = t.GetEnd().Format("15:04") | ||||
| 		} else { | ||||
| 			end = t.GetEnd().Format("2006/01/02 15:04:05") | ||||
| 		} | ||||
| 	} | ||||
| 	ret += end | ||||
| 	tags := t.GetTags() | ||||
| 	if tags.Length() > 0 { | ||||
| 		ret += " [ " | ||||
| 		for i := 0; i < tags.Length(); i++ { | ||||
| 			ret += tags.Get(i) + " " | ||||
| 		} | ||||
| 		ret += "]" | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func InferTimerDetailString(t *gime.TimeEntry) string { | ||||
| 	diffEnd := time.Now() | ||||
| 	if !t.GetEnd().IsZero() { | ||||
| 		diffEnd = t.GetEnd() | ||||
| 	} | ||||
| 	if int(diffEnd.Sub(t.GetStart())) >= (int(time.Hour) * diffEnd.Hour()) { | ||||
| 		return TimerDetailToLongString(t) | ||||
| 	} | ||||
| 	return TimerDetailToString(t) | ||||
| } | ||||
|  | ||||
| func TimerDetailToString(t *gime.TimeEntry) string { | ||||
| 	ret := t.GetStart().Format("15:04") | ||||
| 	if t.GetEnd().IsZero() { | ||||
| 		ret += " (" + padLeft(sinceToString(t.GetStart()), len("00h 00m 00s")) + ") " | ||||
| 	} else { | ||||
| 		ret += " (" + padLeft(diffToString(t.GetStart(), t.GetEnd()), len("00h 00m 00s")) + ") " | ||||
| 	} | ||||
| 	if t.GetTags().Length() > 0 { | ||||
| 		ret += " [ " | ||||
| 		for j := 0; j < t.GetTags().Length(); j++ { | ||||
| 			ret += t.GetTags().Get(j) | ||||
| 			if j < t.GetTags().Length()-1 { | ||||
| 				ret += ", " | ||||
| 			} | ||||
| 		} | ||||
| 		ret += " ] " | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // ...ToLongString includes year/month/day | ||||
| func TimerDetailToLongString(t *gime.TimeEntry) string { | ||||
| 	ret := t.GetStart().Format(time.Stamp) | ||||
| 	if t.GetEnd().IsZero() { | ||||
| 		ret += " (" + padLeft(sinceToString(t.GetStart()), len("0000y 00m 00d 00h 00m 00s")) + ") " | ||||
| 	} else { | ||||
| 		ret += " (" + padLeft(diffToString(t.GetStart(), t.GetEnd()), len("0000y 00m 00d 00h 00m 00s")) + ") " | ||||
| 	} | ||||
| 	if t.GetTags().Length() > 0 { | ||||
| 		ret += " [ " | ||||
| 		for j := 0; j < t.GetTags().Length(); j++ { | ||||
| 			ret += t.GetTags().Get(j) | ||||
| 			if j < t.GetTags().Length()-1 { | ||||
| 				ret += ", " | ||||
| 			} | ||||
| 		} | ||||
| 		ret += " ] " | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // findTimerById takes a timer id and returns the TimeEntry and the type string | ||||
| // of the entry corresponding to that id | ||||
| // It searches TypeCurrent -> TypeRecent -> TypeArchive | ||||
| func findTimerById(tmrId int) (*gime.TimeEntry, int, error) { | ||||
| 	// Find the timer for this tmrId | ||||
| 	var prevNum, numLoaded int | ||||
| 	for i := range gdb.AllTypes { | ||||
| 		timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i]) | ||||
| 		numLoaded += timeCollection.Length() | ||||
| 		if numLoaded > tmrId { | ||||
| 			// This is the set that it's in, the index we need is tmrId - prevNum | ||||
| 			return timeCollection.Get(tmrId - prevNum), gdb.AllTypes[i], nil | ||||
| 		} | ||||
| 		prevNum = numLoaded | ||||
| 	} | ||||
| 	return nil, gime.TypeAll, errors.New("Unable to find timer with id: " + strconv.Itoa(tmrId)) | ||||
| } | ||||
|  | ||||
| func parseFuzzyTime(t string) (time.Time, error) { | ||||
| 	var ret time.Time | ||||
| 	var err error | ||||
| 	for i := range fuzzyFormats { | ||||
| 		ret, err = time.Parse(fuzzyFormats[i], t) | ||||
| 		if err == nil { | ||||
| 			// Make sure it's in the local timezone | ||||
| 			tz := time.Now().Format("Z07:00") | ||||
| 			t = ret.Format("2006-01-02T15:04:05") + tz | ||||
| 			if ret, err = time.Parse(time.RFC3339, t); err != nil { | ||||
| 				return ret, err | ||||
| 			} | ||||
| 			// Check for zero on year/mo/day | ||||
| 			if ret.Year() == 0 && ret.Month() == time.January && ret.Day() == 1 { | ||||
| 				ret = ret.AddDate(time.Now().Year(), int(time.Now().Month())-1, time.Now().Day()-1) | ||||
| 			} | ||||
| 			return ret, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return time.Time{}, errors.New("Unable to parse time: " + t) | ||||
| } | ||||
|  | ||||
| func sinceToString(tm time.Time) string { | ||||
| 	return diffToString(tm, time.Now()) | ||||
| } | ||||
|  | ||||
| func diffToString(tm1, tm2 time.Time) string { | ||||
| 	ret := "" | ||||
| 	yr, mo, dy, hr, mn, sc := diff(tm1, tm2) | ||||
| 	higher := false | ||||
|  | ||||
| 	if yr > 0 { | ||||
| 		ret += fmt.Sprintf("%4dy ", yr) | ||||
| 		higher = true | ||||
| 	} | ||||
| 	if mo > 0 || higher { | ||||
| 		ret += fmt.Sprintf("%2dm ", mo) | ||||
| 		higher = true | ||||
| 	} | ||||
| 	if dy > 0 || higher { | ||||
| 		ret += fmt.Sprintf("%2dd ", dy) | ||||
| 		higher = true | ||||
| 	} | ||||
| 	if hr > 0 || higher { | ||||
| 		ret += fmt.Sprintf("%2dh ", hr) | ||||
| 		higher = true | ||||
| 	} | ||||
| 	if mn > 0 || higher { | ||||
| 		ret += fmt.Sprintf("%2dm ", mn) | ||||
| 		higher = true | ||||
| 	} | ||||
| 	if sc > 0 || higher { | ||||
| 		ret += fmt.Sprintf("%2ds", sc) | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func padRight(st string, l int) string { | ||||
| 	for len(st) < l { | ||||
| 		st = st + " " | ||||
| 	} | ||||
| 	return st | ||||
| } | ||||
|  | ||||
| func padLeft(st string, l int) string { | ||||
| 	for len(st) < l { | ||||
| 		st = " " + st | ||||
| 	} | ||||
| 	return st | ||||
| } | ||||
|  | ||||
| func diff(a, b time.Time) (year, month, day, hour, min, sec int) { | ||||
| 	if a.Location() != b.Location() { | ||||
| 		b = b.In(a.Location()) | ||||
| 	} | ||||
| 	if a.After(b) { | ||||
| 		a, b = b, a | ||||
| 	} | ||||
| 	y1, M1, d1 := a.Date() | ||||
| 	y2, M2, d2 := b.Date() | ||||
|  | ||||
| 	h1, m1, s1 := a.Clock() | ||||
| 	h2, m2, s2 := b.Clock() | ||||
|  | ||||
| 	year = int(y2 - y1) | ||||
| 	month = int(M2 - M1) | ||||
| 	day = int(d2 - d1) | ||||
| 	hour = int(h2 - h1) | ||||
| 	min = int(m2 - m1) | ||||
| 	sec = int(s2 - s1) | ||||
|  | ||||
| 	// Normalize negative values | ||||
| 	if sec < 0 { | ||||
| 		sec += 60 | ||||
| 		min-- | ||||
| 	} | ||||
| 	if min < 0 { | ||||
| 		min += 60 | ||||
| 		hour-- | ||||
| 	} | ||||
| 	if hour < 0 { | ||||
| 		hour += 24 | ||||
| 		day-- | ||||
| 	} | ||||
| 	if day < 0 { | ||||
| 		// days in month: | ||||
| 		t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC) | ||||
| 		day += 32 - t.Day() | ||||
| 		month-- | ||||
| 	} | ||||
| 	if month < 0 { | ||||
| 		month += 12 | ||||
| 		year-- | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										495
									
								
								cmd/gime/main.go
									
									
									
									
									
								
							
							
						
						
									
										495
									
								
								cmd/gime/main.go
									
									
									
									
									
								
							| @@ -1,495 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.bullercodeworks.com/brian/gime" | ||||
| 	userConfig "github.com/br0xen/user-config" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	AppName       = "gime" | ||||
| 	AppVersion    = 1 | ||||
| 	DefDBName     = "gime.db" | ||||
| 	DefArchDBName = "gimearch.db" | ||||
| ) | ||||
|  | ||||
| var validOperations map[string][]string | ||||
| var opFuncs map[string]func([]string) int | ||||
| var timeEntries *gime.TimeEntryCollection | ||||
| var gdb *gime.GimeDB | ||||
| var cfg *userConfig.Config | ||||
|  | ||||
| var fuzzyFormats []string | ||||
|  | ||||
| func main() { | ||||
| 	var ret int | ||||
| 	initialize() | ||||
| 	var parms []string | ||||
|  | ||||
| 	if len(os.Args) > 1 { | ||||
| 		parms = os.Args[1:] | ||||
| 		parms[0] = matchParameter(parms[0]) | ||||
| 	} else { | ||||
| 		// If no parameters were passed, just print the status | ||||
| 		parms = append(parms, "status") | ||||
| 	} | ||||
|  | ||||
| 	if fn, ok := opFuncs[parms[0]]; ok { | ||||
| 		ret = fn(parms[1:]) | ||||
| 	} else { | ||||
| 		fmt.Println("Unknown command") | ||||
| 		ret = 1 | ||||
| 	} | ||||
| 	os.Exit(ret) | ||||
| } | ||||
|  | ||||
| func loadActiveTimeEntries() { | ||||
| 	timeEntries = gdb.LoadTimeEntryCollection(gime.TypeCurrent) | ||||
| } | ||||
|  | ||||
| func loadActiveAndRecentTimeEntries() { | ||||
| 	timeEntries = gdb.LoadTimeEntryCollection(gime.TypeNoArchive) | ||||
| } | ||||
|  | ||||
| func getMostRecentTimeEntry() (*gime.TimeEntry, error) { | ||||
| 	return gdb.GetLatestTimeEntry() | ||||
| } | ||||
|  | ||||
| func cmdDoArchive(args []string) int { | ||||
| 	if len(args) == 0 { | ||||
| 		fmt.Println("Nothing to do") | ||||
| 		return 1 | ||||
| 	} | ||||
| 	bef, err := parseFuzzyTime(args[0]) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("Error parsing time") | ||||
| 		return 1 | ||||
| 	} | ||||
| 	ret := 0 | ||||
| 	fmt.Print("Archive all timers before ", bef) | ||||
| 	loadActiveAndRecentTimeEntries() | ||||
| 	for i := 0; i < timeEntries.Length(); i++ { | ||||
| 		tst := timeEntries.Get(i) | ||||
| 		if tst.GetEnd().Before(bef) { | ||||
| 			fmt.Print(".") | ||||
| 			if err = gdb.ArchiveTimeEntry(tst.GetUUID()); err != nil { | ||||
| 				fmt.Print("Error archiving entry (", tst.GetUUID(), ")", err.Error()) | ||||
| 				ret = 1 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	fmt.Println("Done") | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func cmdDoConfig(args []string) int { | ||||
| 	if len(args) == 0 { | ||||
| 		fmt.Println("Invalid configuration options passed") | ||||
| 		return 1 | ||||
| 	} | ||||
|  | ||||
| 	for _, opt := range args { | ||||
| 		if !strings.Contains(opt, "=") { | ||||
| 			// Single word triggers | ||||
| 			switch opt { | ||||
| 			case "reset": | ||||
| 				fmt.Println("Resetting Configuration...") | ||||
| 				cfg.Set("dbdir", cfg.GetConfigPath()+"/") | ||||
| 				cfg.Set("dbname", DefDBName) | ||||
| 				cfg.Set("dbarchname", DefArchDBName) | ||||
| 			case "list": | ||||
| 				fmt.Println("Current " + AppName + " config") | ||||
| 				for _, v := range cfg.GetKeyList() { | ||||
| 					fmt.Println("  " + v + ": " + cfg.Get(v)) | ||||
| 				} | ||||
| 			case "dbpath": | ||||
| 				fmt.Println(cfg.Get("dbdir") + cfg.Get("dbname")) | ||||
| 			} | ||||
| 		} else { | ||||
| 			// Key=Value options | ||||
| 			pts := strings.Split(opt, "=") | ||||
| 			if len(pts) == 2 { | ||||
| 				switch pts[0] { | ||||
| 				case "dbdir": | ||||
| 					val := pts[1] | ||||
| 					if val[len(val)-1] != '/' { | ||||
| 						val = val + "/" | ||||
| 					} | ||||
| 					cfg.Set("dbdir", val) | ||||
| 				case "dbname": | ||||
| 					cfg.Set("dbname", pts[1]) | ||||
| 				case "dbarchname": | ||||
| 					cfg.Set("dbarchname", pts[1]) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func cmdPrintDetail(args []string) int { | ||||
| 	fmt.Println("Not implemented yet.") | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func cmdPrintHelp(args []string) int { | ||||
| 	if len(args) == 0 { | ||||
| 		fmt.Println("gime - A simple timekeeping application\n") | ||||
| 		fmt.Println("Usage: gime [@timerID] [operation] [tags...]") | ||||
| 		for _, v := range validOperations { | ||||
| 			for vi := range v { | ||||
| 				fmt.Println("  ", v[vi]) | ||||
| 			} | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 	} else { | ||||
| 		switch args[0] { | ||||
| 		case "formats": | ||||
| 			fmt.Println("Supported date/time formats:") | ||||
| 			for i := range fuzzyFormats { | ||||
| 				fmt.Println(" ", fuzzyFormats[i]) | ||||
| 			} | ||||
| 		} | ||||
| 		fmt.Println("") | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func cmdPrintList(args []string) int { | ||||
| 	loadActiveAndRecentTimeEntries() | ||||
|  | ||||
| 	useDefaultFilter := true | ||||
|  | ||||
| 	var beg, end time.Time | ||||
| 	searchTags := []string{} | ||||
|  | ||||
| 	for _, opt := range args { | ||||
| 		var tmpBeg, tmpEnd time.Time | ||||
|  | ||||
| 		// Do our best to figure out what timers the user wants to list | ||||
| 		var err error | ||||
| 		if strings.Contains(opt, "-") { | ||||
| 			useDefaultFilter = false | ||||
| 			pts := strings.Split(opt, "-") | ||||
| 			if len(pts[0]) > 0 { | ||||
| 				// This should be the starting date | ||||
| 				tmpBeg, err = parseFuzzyTime(pts[0]) | ||||
| 				if err != nil { | ||||
| 					// We couldn't parse it as a time, | ||||
| 					// Probably this is just a tag | ||||
| 					searchTags = append(searchTags, opt) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			if len(pts[1]) > 0 { | ||||
| 				// This should be the ending date | ||||
| 				tmpEnd, err = parseFuzzyTime(pts[1]) | ||||
| 				if err != nil { | ||||
| 					searchTags = append(searchTags, opt) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			// Tag filters | ||||
| 			searchTags = append(searchTags, opt) | ||||
| 		} | ||||
| 		if !tmpBeg.IsZero() || !tmpEnd.IsZero() { | ||||
| 			beg, end = tmpBeg, tmpEnd | ||||
| 		} | ||||
| 	} | ||||
| 	if end.IsZero() { | ||||
| 		end = time.Now() | ||||
| 	} | ||||
|  | ||||
| 	// By default, list all entries ending today or still running | ||||
| 	defaultFilter := func(t *gime.TimeEntry) bool { | ||||
| 		return t.EndsToday() || t.IsRunning() | ||||
| 	} | ||||
|  | ||||
| 	timeSpanFilter := func(t *gime.TimeEntry) bool { | ||||
| 		return t.GetStart().After(beg) && t.GetEnd().Before(end) | ||||
| 	} | ||||
| 	tagFilter := func(t *gime.TimeEntry) bool { | ||||
| 		for i := range searchTags { | ||||
| 			if !t.HasTag(searchTags[i]) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	compoundFilter := func(t *gime.TimeEntry) bool { | ||||
| 		// If we didn't get any other filter specifications, just use the default | ||||
| 		if useDefaultFilter { | ||||
| 			return defaultFilter(t) | ||||
| 		} | ||||
| 		// Otherwise we want to filter timespan and tags | ||||
| 		return timeSpanFilter(t) && tagFilter(t) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println(time.Now().Format("2006/01/02")) | ||||
| 	str := TimerCollectionToString(filterTimerCollection(timeEntries, compoundFilter)) | ||||
| 	str = "  " + strings.Replace(str, "\n", "\n  ", -1) | ||||
| 	fmt.Println(str) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func cmdListTags(args []string) int { | ||||
| 	loadActiveAndRecentTimeEntries() | ||||
| 	var allTags []string | ||||
| 	for i := 0; i < timeEntries.Length(); i++ { | ||||
| 		tc := timeEntries.Get(i).GetTags() | ||||
| 		for j := 0; j < tc.Length(); j++ { | ||||
| 			tg := tc.Get(j) | ||||
| 			var found bool | ||||
| 			for tst := range allTags { | ||||
| 				if allTags[tst] == tg { | ||||
| 					found = true | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if !found { | ||||
| 				allTags = append(allTags, tg) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	sort.Sort(sort.StringSlice(allTags)) | ||||
| 	for i := range allTags { | ||||
| 		fmt.Println(allTags[i]) | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func cmdPrintStatus(args []string) int { | ||||
| 	loadActiveTimeEntries() | ||||
| 	curr := time.Now() | ||||
| 	fmt.Println("Current Time:", curr.Format(time.Stamp)) | ||||
| 	if timeEntries.Length() == 0 { | ||||
| 		fmt.Println("No timer running") | ||||
| 	} else { | ||||
| 		fmt.Print("Active Timers (", timeEntries.Length(), ")\n") | ||||
| 		// Find the longest start time & longest duration | ||||
| 		short := true | ||||
| 		for i := 0; i < timeEntries.Length(); i++ { | ||||
| 			v := timeEntries.Get(i) | ||||
| 			if v.GetStart().Day() != curr.Day() { | ||||
| 				short = false | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for i := 0; i < timeEntries.Length(); i++ { | ||||
| 			v := timeEntries.Get(i) | ||||
| 			if short { | ||||
| 				fmt.Printf("  @%d %s\n", i, TimerDetailToString(v)) | ||||
| 			} else { | ||||
| 				fmt.Printf("  @%d %s\n", i, TimerDetailToLongString(v)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func cmdDoFuzzyParse(args []string) int { | ||||
| 	if len(args) == 0 { | ||||
| 		return 1 | ||||
| 	} | ||||
| 	var t time.Time | ||||
| 	var err error | ||||
| 	if t, err = parseFuzzyTime(args[0]); err != nil { | ||||
| 		fmt.Println(err.Error()) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	fmt.Println(t.Format(time.RFC3339)) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func initialize() { | ||||
| 	var err error | ||||
| 	validOperations = make(map[string][]string) | ||||
| 	opFuncs = make(map[string]func([]string) int) | ||||
|  | ||||
| 	opFuncs["cont"] = cmdContinueTimer | ||||
| 	validOperations["cont"] = []string{ | ||||
| 		"cont [time]             - Continue the last stopped timer", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["config"] = cmdDoConfig | ||||
| 	validOperations["config"] = []string{ | ||||
| 		"config [command]        - Perform configuration", | ||||
| 		"       list             - List current configuration", | ||||
| 		"       reset            - Reset current configuration", | ||||
| 		"       Configuration Options:", | ||||
| 		"         dbdir=[database directory]", | ||||
| 		"         dbname=[database filename]", | ||||
| 		"         dbarchname=[archive database filename]", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["detail"] = cmdPrintDetail | ||||
| 	validOperations["detail"] = []string{ | ||||
| 		"detail @id              - Print details about a timer", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["delete"] = cmdDeleteTimer | ||||
| 	validOperations["delete"] = []string{ | ||||
| 		"delete @id              - Delete a timer", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["end"] = cmdStopTimer | ||||
| 	validOperations["end"] = []string{ | ||||
| 		"end                     - The same as stop", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["help"] = cmdPrintHelp | ||||
| 	validOperations["help"] = []string{ | ||||
| 		"help                    - Print this", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["list"] = cmdPrintList | ||||
| 	validOperations["list"] = []string{ | ||||
| 		"list [duration] [+tags] - List time entries", | ||||
| 		"                          valid values of [duration] include:", | ||||
| 		"                            day   - List all entries for the current day", | ||||
| 		"                            week  - List all entries for the current week", | ||||
| 		"                            month - List all entries for the current month", | ||||
| 		"                            year  - List all entries for the current year", | ||||
| 		"                          To list entries by tag, preceed the tags with a +", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["ls"] = cmdPrintList | ||||
| 	validOperations["ls"] = []string{ | ||||
| 		"ls [duration] [+tags]   - The same as list", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["remove"] = cmdDeleteTimer | ||||
| 	validOperations["remove"] = []string{ | ||||
| 		"remove @id              - See 'delete'", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["status"] = cmdPrintStatus | ||||
| 	validOperations["status"] = []string{ | ||||
| 		"status                  - Print the status of all active timers", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["start"] = cmdStartTimer | ||||
| 	validOperations["start"] = []string{ | ||||
| 		"start [time] [tags ...] - Start a timer with the given tags (space separated)", | ||||
| 		"                          If the first sub-argument given looks like a time,", | ||||
| 		"                          the timer will be started then (past or future).", | ||||
| 		"                          If a timer is already running it'll be stopped", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["stop"] = cmdStopTimer | ||||
| 	validOperations["stop"] = []string{ | ||||
| 		"stop [time]             - Stops the current timer", | ||||
| 		"                          If the first sub-argument given looks like a time,", | ||||
| 		"                          the timer will be stopped then (past or future).", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["switch"] = cmdSwitchTimer | ||||
| 	validOperations["switch"] = []string{ | ||||
| 		"switch [tags ...]       - Stop all currently running timers and start a new", | ||||
| 		"                          one with the given tags", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["archive"] = cmdDoArchive | ||||
| 	validOperations["archive"] = []string{ | ||||
| 		"archive [date]          - Archive all entries older than the given date", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["fuzzyparse"] = cmdDoFuzzyParse | ||||
| 	validOperations["fuzzyparse"] = []string{ | ||||
| 		"fuzzyparse              - Parse the next argument as a date/time and print", | ||||
| 		"                          the RFC3339 result. (Basically for testing)", | ||||
| 	} | ||||
|  | ||||
| 	opFuncs["tags"] = cmdListTags | ||||
| 	validOperations["tags"] = []string{ | ||||
| 		"tags                    - List all tags that have been used in non-archived", | ||||
| 		"                          time entries", | ||||
| 	} | ||||
|  | ||||
| 	// Load the Config | ||||
| 	cfg, err = userConfig.NewConfig(AppName) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err.Error()) | ||||
| 		fmt.Println("Creating new config") | ||||
| 		cfg.Save() | ||||
| 	} | ||||
| 	// If dbdir isn't set, set it to the config directory | ||||
| 	if cfg.Get("dbdir") == "" { | ||||
| 		cfg.Set("dbdir", cfg.GetConfigPath()+"/") | ||||
| 	} | ||||
| 	// If dbname isn't set, set it to the default database filename | ||||
| 	if cfg.Get("dbname") == "" { | ||||
| 		cfg.Set("dbname", DefDBName) | ||||
| 	} | ||||
| 	// If dbarchivename isn't set, set it to the default archive database filename | ||||
| 	if cfg.Get("dbarchname") == "" { | ||||
| 		cfg.Set("dbarchname", DefArchDBName) | ||||
| 	} | ||||
| 	if gdb, err = gime.LoadDatabase(cfg.Get("dbdir"), cfg.Get("dbname"), cfg.Get("dbarchname")); err != nil { | ||||
| 		fmt.Println("Error loading the database") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	fuzzyFormats = []string{ | ||||
| 		"1504", | ||||
| 		"15:04", // Kitchen, 24hr | ||||
| 		time.Kitchen, | ||||
| 		time.RFC3339, | ||||
| 		"2006-01-02T15:04:05", // RFC3339 without timezone | ||||
| 		"2006-01-02T15:04",    // RFC3339 without seconds or timezone | ||||
| 		time.Stamp, | ||||
| 		"02 Jan 06 15:04:05", // RFC822 with second | ||||
| 		time.RFC822, | ||||
| 		"01/02/2006 15:04",    // U.S. Format | ||||
| 		"01/02/2006 15:04:05", // U.S. Format with seconds | ||||
| 		"01/02/06 15:04",      // U.S. Format, short year | ||||
| 		"01/02/06 15:04:05",   // U.S. Format, short year, with seconds | ||||
| 		"2006-01-02", | ||||
| 		"2006-01-02 15:04", | ||||
| 		"2006-01-02 15:04:05", | ||||
| 		"20060102", | ||||
| 		"20060102 15:04", | ||||
| 		"20060102 15:04:05", | ||||
| 		"20060102 1504", | ||||
| 		"20060102 150405", | ||||
| 		"20060102T15:04", | ||||
| 		"20060102T15:04:05", | ||||
| 		"20060102T1504", | ||||
| 		"20060102T150405", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func matchParameter(in string) string { | ||||
| 	var chkParms []string | ||||
| 	for k := range validOperations { | ||||
| 		chkParms = append(chkParms, k) | ||||
| 	} | ||||
| 	var nextParms []string | ||||
| 	for i := range in { | ||||
| 		for _, p := range chkParms { | ||||
| 			if p[i] == in[i] { | ||||
| 				nextParms = append(nextParms, p) | ||||
| 			} | ||||
| 		} | ||||
| 		// If we get here and there is only one parameter left, return it | ||||
| 		chkParms = nextParms | ||||
| 		if len(nextParms) == 1 { | ||||
| 			break | ||||
| 		} | ||||
| 		// Otherwise, loop | ||||
| 		nextParms = []string{} | ||||
| 	} | ||||
| 	if len(chkParms) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return chkParms[0] | ||||
| } | ||||
| @@ -1,169 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.bullercodeworks.com/brian/gime" | ||||
| ) | ||||
|  | ||||
| func cmdContinueTimer(args []string) int { | ||||
| 	// Get the last running timer and start a new one with the same tags | ||||
| 	te, err := getMostRecentTimeEntry() | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err.Error()) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	tagColl := te.GetTags() | ||||
| 	var tags []string | ||||
| 	for i := 0; i < tagColl.Length(); i++ { | ||||
| 		tags = append(tags, tagColl.Get(i)) | ||||
| 	} | ||||
| 	args = append(args, tags...) | ||||
| 	return cmdStartTimer(args) | ||||
| } | ||||
|  | ||||
| // switchTimer performs a stop on any currently running timers | ||||
| // and starts a new timer with the given arguments | ||||
| func cmdSwitchTimer(args []string) int { | ||||
| 	loadActiveTimeEntries() | ||||
| 	tm := time.Now() | ||||
| 	if timeEntries.Length() > 0 { | ||||
| 		fmt.Println("Stopped Timers:") | ||||
| 	} | ||||
| 	for i := 0; i < timeEntries.Length(); i++ { | ||||
| 		tmr := timeEntries.Get(i) | ||||
| 		tmr.SetEnd(tm) | ||||
| 		if err := gdb.UpdateTimeEntry(tmr); err != nil { | ||||
| 			fmt.Println(err.Error()) | ||||
| 			return 1 | ||||
| 		} | ||||
| 		fmt.Println("  " + TimerToString(tmr)) | ||||
| 	} | ||||
| 	fmt.Println("Started Timer:") | ||||
| 	return cmdStartTimer(args) | ||||
| } | ||||
|  | ||||
| // cmdStartTimer takes a list of arguments and returns the return code | ||||
| // to be passed along to os.Exit | ||||
| func cmdStartTimer(args []string) int { | ||||
| 	var err error | ||||
| 	var tm time.Time | ||||
| 	tagStart := 0 | ||||
| 	if len(args) > 0 { | ||||
| 		// Check if the first argument looks like a date/time | ||||
| 		tm, err = parseFuzzyTime(args[0]) | ||||
| 	} | ||||
|  | ||||
| 	if len(args) == 0 || err != nil { | ||||
| 		// Just start it now | ||||
| 		tm = time.Now() | ||||
| 	} else { | ||||
| 		tagStart = 1 | ||||
| 	} | ||||
| 	var entry *gime.TimeEntry | ||||
|  | ||||
| 	var timerArgs []string | ||||
| 	if tagStart < len(args) { | ||||
| 		timerArgs = args[tagStart:] | ||||
| 	} | ||||
| 	tc := new(gime.TagCollection) | ||||
| 	for i := range timerArgs { | ||||
| 		tc.Push(timerArgs[i]) | ||||
| 	} | ||||
| 	if entry, err = gime.CreateTimeEntry(tm, time.Time{}, tc); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	if err = gdb.SaveTimeEntry(entry); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	fmt.Println("  " + TimerToString(entry)) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // cmdStopTimer takes parameters that describe which times to stop | ||||
| func cmdStopTimer(args []string) int { | ||||
| 	// args[0] should either be a timer id (starting with '@') or 'all' | ||||
| 	var err error | ||||
| 	tm := time.Now() | ||||
| 	actTimers := gdb.LoadTimeEntryCollection(gime.TypeCurrent) | ||||
| 	var tmr *gime.TimeEntry | ||||
| 	stopId := "@0" // By default, stop the first timer | ||||
| 	for i := range args { | ||||
| 		if args[i] == "all" || args[i][0] == '@' { | ||||
| 			stopId = args[i] | ||||
| 			continue | ||||
| 		} | ||||
| 		tmpTm, err := parseFuzzyTime(args[i]) | ||||
| 		if err == nil { | ||||
| 			// We found a time | ||||
| 			tm = tmpTm | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 	if stopId != "all" { | ||||
| 		// Find the timer that we're stopping | ||||
| 		timerId, err := strconv.Atoi(stopId[1:]) | ||||
| 		if err != nil { | ||||
| 			fmt.Println("Error parsing timer id:", err.Error()) | ||||
| 			return 1 | ||||
| 		} | ||||
| 		tmr, _, err = findTimerById(timerId) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err.Error()) | ||||
| 			return 1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	stopTimer := func(tmr *gime.TimeEntry, at time.Time) int { | ||||
| 		tmr.SetEnd(at) | ||||
| 		if err = gdb.UpdateTimeEntry(tmr); err != nil { | ||||
| 			fmt.Println(err.Error()) | ||||
| 			return 1 | ||||
| 		} | ||||
| 		fmt.Println("Stopped:", InferTimerDetailString(tmr)) | ||||
| 		return 0 | ||||
| 	} | ||||
| 	if stopId == "all" { | ||||
| 		var ret int | ||||
| 		for i := 0; i < actTimers.Length(); i++ { | ||||
| 			ret += stopTimer(actTimers.Get(i), tm) | ||||
| 		} | ||||
| 		if ret > 0 { | ||||
| 			return 1 // One or more stop operations failed | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} | ||||
| 	// Just stop the one timer | ||||
| 	return stopTimer(tmr, tm) | ||||
| } | ||||
|  | ||||
| // cmdDeleteTimer takes parameters that describe the timers to be deleted. | ||||
| func cmdDeleteTimer(args []string) int { | ||||
| 	var err error | ||||
| 	if len(args) < 1 || args[0][0] != '@' { | ||||
| 		fmt.Println("Couldn't determine which timer(s) to delete") | ||||
| 		return 1 | ||||
| 	} | ||||
|  | ||||
| 	// We've got a timer id to delete | ||||
| 	timerId, err := strconv.Atoi(args[0][1:]) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("Error parsing timer id: " + err.Error()) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	tmr, tp, err := findTimerById(timerId) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err.Error()) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	if gdb.RemoveTimeEntry(tmr.GetUUID()) != nil { | ||||
| 		fmt.Println("Error removing entry " + gime.TypeToString(tp) + "." + tmr.GetUUID()) | ||||
| 		return 1 | ||||
| 	} | ||||
| 	fmt.Println("Deleted Time Entry: " + TimerToString(tmr)) | ||||
| 	return 0 | ||||
| } | ||||
| @@ -30,10 +30,10 @@ func (gdb *GimeDB) GetLatestTimeEntry() (*TimeEntry, error) { | ||||
| 	return ret, err | ||||
| } | ||||
|  | ||||
| // FindTimeEntryByUUID searches all entries | ||||
| // findTimeEntryAndTypeByUUID searches all entries | ||||
| // for the time entry with the given uuid, return the TimeEntry, the type, and/or and error | ||||
| // Types are searched TypeCurrent -> TypeRecent -> TypeArchive | ||||
| func (gdb *GimeDB) FindTimeEntryByUUID(uuid string) (*TimeEntry, int, error) { | ||||
| func (gdb *GimeDB) findTimeEntryAndTypeByUUID(uuid string) (*TimeEntry, int, error) { | ||||
| 	for i := range gdb.AllTypes { | ||||
| 		timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i]) | ||||
| 		for j := 0; j < timeCollection.Length(); j++ { | ||||
| @@ -45,6 +45,14 @@ func (gdb *GimeDB) FindTimeEntryByUUID(uuid string) (*TimeEntry, int, error) { | ||||
| 	return nil, TypeAll, errors.New("Unable to find time entry with uuid " + uuid) | ||||
| } | ||||
|  | ||||
| // FindTimeEntryByUUID searches all entries | ||||
| // for the time entry with the given uuid, return the TimeEntry, the type, and/or and error | ||||
| // Types are searched TypeCurrent -> TypeRecent -> TypeArchive | ||||
| func (gdb *GimeDB) FindTimeEntryByUUID(uuid string) (*TimeEntry, error) { | ||||
| 	te, _, err := gdb.findTimeEntryAndTypeByUUID(uuid) | ||||
| 	return te, err | ||||
| } | ||||
|  | ||||
| // SaveTimeEntry creates a time entry in the database | ||||
| // If TimeEntry.end is zero, then it puts it in TypeCurrent | ||||
| func (gdb *GimeDB) SaveTimeEntry(te *TimeEntry) error { | ||||
| @@ -81,7 +89,7 @@ func (gdb *GimeDB) SaveTimeEntryType(tp int, te *TimeEntry) error { | ||||
|  | ||||
| // ArchiveTimeEntry takes a time from TypeRecent and moves it to TypeArchive | ||||
| func (gdb *GimeDB) ArchiveTimeEntry(uuid string) error { | ||||
| 	archTime, tp, err := gdb.FindTimeEntryByUUID(uuid) | ||||
| 	archTime, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid) | ||||
| 	if tp != TypeRecent { | ||||
| 		return errors.New("Couldn't find timer to archive in the 'Recent' bucket") | ||||
| 	} | ||||
| @@ -106,7 +114,7 @@ func (gdb *GimeDB) UpdateTimeEntry(te *TimeEntry) error { | ||||
|  | ||||
| // RemoveTimeEntry removes a time entry with the given uuid from the database | ||||
| func (gdb *GimeDB) RemoveTimeEntry(uuid string) error { | ||||
| 	fndEntry, tp, err := gdb.FindTimeEntryByUUID(uuid) | ||||
| 	fndEntry, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid) | ||||
| 	if err != nil { | ||||
| 		return errors.New("Unable to find time entry with uuid " + uuid) | ||||
| 	} | ||||
|   | ||||
| @@ -106,15 +106,15 @@ func (t *TimeEntry) Equals(tst *TimeEntry) bool { | ||||
| // StartsToday returns if the timer's start time is today | ||||
| func (t *TimeEntry) StartsToday() bool { | ||||
| 	currTime := time.Now() | ||||
| 	dur := currTime.Hour()*int(time.Hour) + currTime.Minute()*int(time.Minute) | ||||
| 	return int(time.Since(t.GetStart())) < dur | ||||
| 	dur := int64(currTime.Hour())*int64(time.Hour) + int64(currTime.Minute())*int64(time.Minute) | ||||
| 	return int64(time.Since(t.GetStart())) < dur | ||||
| } | ||||
|  | ||||
| // StartsToday returns if the timer's end time is today | ||||
| func (t *TimeEntry) EndsToday() bool { | ||||
| 	currTime := time.Now() | ||||
| 	dur := currTime.Hour()*int(time.Hour) + currTime.Minute()*int(time.Minute) | ||||
| 	return int(time.Since(t.GetEnd())) < dur | ||||
| 	dur := int64(currTime.Hour())*int64(time.Hour) + int64(currTime.Minute())*int64(time.Minute) | ||||
| 	return int64(time.Since(t.GetEnd())) < dur | ||||
| } | ||||
|  | ||||
| // IsRunning returns if the timer is still running | ||||
|   | ||||
		Reference in New Issue
	
	Block a user