Finished first shot on stats implementation

This commit is contained in:
マリウス 2020-10-16 21:41:19 +01:00
parent 0b4839278c
commit 85fc684aa9
No known key found for this signature in database
GPG key ID: C228EF0A530AF06F
5 changed files with 119 additions and 148 deletions

1
go.mod
View file

@ -6,6 +6,7 @@ require (
github.com/cnf/structhash v0.0.0-20201013183111-a92e111048cd github.com/cnf/structhash v0.0.0-20201013183111-a92e111048cd
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/gookit/color v1.3.1 github.com/gookit/color v1.3.1
github.com/jinzhu/now v1.1.1
github.com/shopspring/decimal v1.2.0 github.com/shopspring/decimal v1.2.0
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.0.0
github.com/tidwall/buntdb v1.1.2 github.com/tidwall/buntdb v1.1.2

2
go.sum
View file

@ -45,6 +45,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=

View file

@ -2,10 +2,10 @@ package z
import ( import (
"fmt" "fmt"
"math"
"time" "time"
"github.com/gookit/color"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/jinzhu/now"
"github.com/gookit/color"
) )
type Statistic struct { type Statistic struct {
@ -14,93 +14,95 @@ type Statistic struct {
Color (func(...interface {}) string) Color (func(...interface {}) string)
} }
type WeekStatistics map[string][]Statistic
type Week struct {
Statistics WeekStatistics
}
type Month struct {
Name string
Weeks [5]Week
}
type Calendar struct { type Calendar struct {
Months [12]Month
} }
func GetOutputBoxForNumber(number int, clr (func(...interface {}) string) ) (string) { func NewCalendar(entries []Entry) (Calendar, error) {
switch(number) { cal := Calendar{}
case 0: return clr(" ")
case 1: return clr(" ▄")
case 2: return clr("▄▄")
case 3: return clr("▄█")
case 4: return clr("██")
}
return clr(" ") for _, entry := range entries {
} endOfBeginDay := now.With(entry.Begin).EndOfDay()
sameDayHours := decimal.NewFromInt(0)
nextDayHours := decimal.NewFromInt(0)
func GetOutputBarForHours(hours decimal.Decimal, stats []Statistic) ([]string) { /*
var bar = []string{ * Apparently the activity end is on a new day.
color.FgGray.Render("····"), * This means we have to split the activity across two days.
color.FgGray.Render("····"), */
color.FgGray.Render("····"), if endOfBeginDay.Before(entry.Finish) == true {
color.FgGray.Render("····"), startOfFinishDay := now.With(entry.Finish).BeginningOfDay()
color.FgGray.Render("····"),
color.FgGray.Render("····"),
}
hoursInt := int((hours.Round(0)).IntPart()) sameDayDuration := endOfBeginDay.Sub(entry.Begin)
rest := ((hours.Round(0)).Mod(decimal.NewFromInt(4))).Round(0) sameDay := sameDayDuration.Hours()
restInt := int(rest.IntPart()) sameDayHours = decimal.NewFromFloat(sameDay)
divisible := hoursInt - restInt nextDayDuration := entry.Finish.Sub(startOfFinishDay)
fullparts := divisible / 4 nextDay := nextDayDuration.Hours()
nextDayHours = decimal.NewFromFloat(nextDay)
colorsFull := make(map[int](func(...interface {}) string))
colorsFullIdx := 0
colorFraction := color.FgWhite.Render
colorFractionPrevAmount := 0.0
for _, stat := range stats {
statHoursInt, _ := stat.Hours.Float64()
statRest := (stat.Hours.Round(0)).Mod(decimal.NewFromInt(4))
statRestFloat, _ := statRest.Float64()
if statRestFloat > colorFractionPrevAmount {
colorFractionPrevAmount = statRestFloat
colorFraction = stat.Color
}
fmt.Printf("%f\n", statHoursInt)
fullColoredParts := int(math.Round(statHoursInt) / 4)
if fullColoredParts == 0 && statHoursInt > colorFractionPrevAmount {
colorFractionPrevAmount = statHoursInt
colorFraction = stat.Color
}
fmt.Printf("Full parts: %d\n", fullColoredParts)
for i := 0; i < fullColoredParts; i++ {
colorsFull[colorsFullIdx] = stat.Color
colorsFullIdx++
}
}
iColor := 0
for i := (len(bar) - 1); i > (len(bar) - 1 - fullparts); i-- {
if iColor < colorsFullIdx {
bar[i] = " " + GetOutputBoxForNumber(4, colorsFull[iColor]) + " "
iColor++
} else { } else {
bar[i] = " " + GetOutputBoxForNumber(4, colorFraction) + " " sameDayDuration := entry.Finish.Sub(entry.Begin)
sameDay := sameDayDuration.Hours()
sameDayHours = decimal.NewFromFloat(sameDay)
}
if sameDayHours.GreaterThan(decimal.NewFromInt(0)) {
month, weeknumber := GetISOWeekInMonth(entry.Begin)
month0 := month - 1
weeknumber0 := weeknumber - 1
weekday := entry.Begin.Weekday()
weekdayName := weekday.String()[:2]
stat := Statistic{
Hours: sameDayHours,
Project: entry.Project,
Color: color.FgCyan.Render,
}
if cal.Months[month0].Weeks[weeknumber0].Statistics == nil {
cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics)
}
cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat)
}
if nextDayHours.GreaterThan(decimal.NewFromInt(0)) {
month, weeknumber := GetISOWeekInMonth(entry.Finish)
month0 := month - 1
weeknumber0 := weeknumber - 1
weekday := entry.Begin.Weekday()
weekdayName := weekday.String()[:2]
stat := Statistic{
Hours: nextDayHours,
Project: entry.Project,
Color: color.FgCyan.Render,
}
if cal.Months[month0].Weeks[weeknumber0].Statistics == nil {
cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics)
}
cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat)
} }
} }
if(restInt > 0) { return cal, nil
bar[(len(bar) - 1 - fullparts)] = " " + GetOutputBoxForNumber(restInt, colorFraction) + " "
}
return bar
} }
func (calendar *Calendar) GetCalendarWeek(timestamp time.Time) (int) { func (calendar *Calendar) GetOutputForWeekCalendar(date time.Time, month int, week int) (string) {
var _, cw = timestamp.ISOWeek()
return cw
}
func (calendar *Calendar) GetOutputForWeekCalendar(cw int, data map[string][]Statistic) (string) {
var output string = "" var output string = ""
var bars [][]string var bars [][]string
var totalHours = decimal.NewFromInt(0) var totalHours = decimal.NewFromInt(0)
@ -109,16 +111,16 @@ func (calendar *Calendar) GetOutputForWeekCalendar(cw int, data map[string][]Sta
for _, day := range days { for _, day := range days {
var dayHours = decimal.NewFromInt(0) var dayHours = decimal.NewFromInt(0)
for _, stat := range data[day] { for _, stat := range calendar.Months[month].Weeks[week].Statistics[day] {
dayHours = dayHours.Add(stat.Hours) dayHours = dayHours.Add(stat.Hours)
totalHours = totalHours.Add(stat.Hours) totalHours = totalHours.Add(stat.Hours)
} }
bar := GetOutputBarForHours(dayHours, data[day]) bar := GetOutputBarForHours(dayHours, calendar.Months[month].Weeks[week].Statistics[day])
bars = append(bars, bar) bars = append(bars, bar)
} }
output = fmt.Sprintf("CW %02d %s H\n", cw, totalHours.StringFixed(2)) output = fmt.Sprintf("CW %02d %s H\n", GetISOCalendarWeek(date), totalHours.StringFixed(2))
for row := 0; row < len(bars[0]); row++ { for row := 0; row < len(bars[0]); row++ {
output = fmt.Sprintf("%s%2d │", output, ((6 - row) * 4)) output = fmt.Sprintf("%s%2d │", output, ((6 - row) * 4))
for col := 0; col < len(bars); col++ { for col := 0; col < len(bars); col++ {

View file

@ -1,11 +1,11 @@
package z package z
import ( import (
"fmt"
"os/user" "os/user"
"regexp" "regexp"
"strconv" "strconv"
"time" "time"
"math"
"errors" "errors"
) )
@ -53,6 +53,7 @@ func GetTimeFormat(timeStr string) (int) {
return -1 return -1
} }
// TODO: Use https://golang.org/pkg/time/#ParseDuration
func RelToTime(timeStr string, ftId int) (time.Time, error) { func RelToTime(timeStr string, ftId int) (time.Time, error) {
var re = regexp.MustCompile(TimeFormats()[ftId]) var re = regexp.MustCompile(TimeFormats()[ftId])
gm := re.FindStringSubmatch(timeStr) gm := re.FindStringSubmatch(timeStr)
@ -100,29 +101,19 @@ func ParseTime(timeStr string) (time.Time, error) {
} }
} }
func OutputAppendRight(leftStr string, rightStr string, pad int) (string) { func GetISOCalendarWeek(date time.Time) (int) {
var output string = "" var _, cw = date.ISOWeek()
var rpos int = 0 return cw
}
left := []rune(leftStr) func GetISOWeekInMonth(date time.Time) (month int, weeknumber int) {
leftLen := len(left) if date.IsZero() {
right := []rune(rightStr) return -1, -1
rightLen := len(right)
for lpos := 0; lpos < leftLen; lpos++ {
if left[lpos] == '\n' || lpos == (leftLen - 1) {
output = fmt.Sprintf("%s%*s", output, pad, "")
for rpos = rpos; rpos < rightLen; rpos++ {
output = fmt.Sprintf("%s%c", output, right[rpos])
if right[rpos] == '\n' {
rpos++
break
}
}
continue
}
output = fmt.Sprintf("%s%c", output, left[lpos])
} }
return output newDay := (date.Day() - int(date.Weekday()) + 1)
addDay := (date.Day() - newDay) * -1
changedDate := date.AddDate(0, 0, addDay)
return int(changedDate.Month()), int(math.Ceil(float64(changedDate.Day()) / 7.0));
} }

View file

@ -3,9 +3,10 @@ package z
import ( import (
"os" "os"
"fmt" "fmt"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/shopspring/decimal" // "github.com/shopspring/decimal"
"github.com/gookit/color" // "github.com/gookit/color"
) )
var statsCmd = &cobra.Command{ var statsCmd = &cobra.Command{
@ -13,55 +14,29 @@ var statsCmd = &cobra.Command{
Short: "Display activity statistics", Short: "Display activity statistics",
Long: "Display statistics on all tracked activities.", Long: "Display statistics on all tracked activities.",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// user := GetCurrentUser() user := GetCurrentUser()
// entries, err := database.ListEntries(user) entries, err := database.ListEntries(user)
// if err != nil { if err != nil {
// fmt.Printf("%s %+v\n", CharError, err) fmt.Printf("%s %+v\n", CharError, err)
// os.Exit(1) os.Exit(1)
// }
// for _, entry := range entries {
// fmt.Printf("%s\n", entry.GetOutput())
// }
var cal Calendar
var data = make(map[string][]Statistic)
data["Mo"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(12.0), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(3.5), Project: "blog", Color: color.FgGreen.Render },
}
data["Tu"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(2.25), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(4.0), Project: "blog", Color: color.FgGreen.Render },
}
data["We"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(10.0), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(1.5), Project: "blog", Color: color.FgGreen.Render },
}
data["Th"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(4.0), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(4.5), Project: "blog", Color: color.FgGreen.Render },
}
data["Fr"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(0.5), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(3.5), Project: "blog", Color: color.FgGreen.Render },
}
data["Sa"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(1.0), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(1.0), Project: "blog", Color: color.FgGreen.Render },
}
data["Su"] = []Statistic {
Statistic{ Hours: decimal.NewFromFloat(10.0), Project: "zeit", Color: color.FgRed.Render },
Statistic{ Hours: decimal.NewFromFloat(0.5), Project: "blog", Color: color.FgGreen.Render },
} }
out := cal.GetOutputForWeekCalendar(1, data) cal, _ := NewCalendar(entries)
out2 := cal.GetOutputForWeekCalendar(2, data)
fmt.Printf("%s\n", OutputAppendRight(out, out2, 10)) today := time.Now()
month, weeknumber := GetISOWeekInMonth(today)
month0 := month - 1
weeknumber0 := weeknumber - 1
thisWeek := cal.GetOutputForWeekCalendar(today, month0, weeknumber0)
oneWeekAgo := today.AddDate(0, 0, -7)
month, weeknumber = GetISOWeekInMonth(oneWeekAgo)
month0 = month - 1
weeknumber0 = weeknumber - 1
previousWeek := cal.GetOutputForWeekCalendar(oneWeekAgo, month0, weeknumber0)
fmt.Printf("%s\n", OutputAppendRight(thisWeek, previousWeek, 16))
return return
}, },