Finished first shot on stats implementation
This commit is contained in:
parent
0b4839278c
commit
85fc684aa9
5 changed files with 119 additions and 148 deletions
1
go.mod
1
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||||
|
|
158
z/calendar.go
158
z/calendar.go
|
@ -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) GetOutputForWeekCalendar(date time.Time, month int, week int) (string) {
|
||||||
}
|
|
||||||
|
|
||||||
func (calendar *Calendar) GetCalendarWeek(timestamp time.Time) (int) {
|
|
||||||
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++ {
|
||||||
|
|
39
z/helpers.go
39
z/helpers.go
|
@ -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)
|
|
||||||
leftLen := len(left)
|
|
||||||
right := []rune(rightStr)
|
|
||||||
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
|
func GetISOWeekInMonth(date time.Time) (month int, weeknumber int) {
|
||||||
|
if date.IsZero() {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
69
z/stats.go
69
z/stats.go
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue