2018-01-25 15:52:31 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-01-25 16:20:30 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-01-25 15:52:31 +00:00
|
|
|
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)
|
|
|
|
|
2018-01-25 16:20:30 +00:00
|
|
|
opFuncs["help"] = cmdPrintHelp
|
|
|
|
validOperations["help"] = []string{
|
|
|
|
"help - Prints this",
|
|
|
|
}
|
|
|
|
|
2018-01-25 15:52:31 +00:00
|
|
|
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
|
|
|
|
}
|