Update .editorconfig, run gofumpt -w ./

This commit is contained in:
マリウス
2025-02-19 10:32:24 -05:00
parent d6f9f8efeb
commit 866ee4ac6d
28 changed files with 1735 additions and 1727 deletions
-2
View File
@@ -4,8 +4,6 @@ root = true
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
insert_final_newline = true
max_line = 80
+143 -142
View File
@@ -1,201 +1,202 @@
package z
import (
"fmt"
"time"
"strings"
"github.com/shopspring/decimal"
"github.com/jinzhu/now"
// "github.com/gookit/color"
"fmt"
"strings"
"time"
"github.com/jinzhu/now"
"github.com/shopspring/decimal"
// "github.com/gookit/color"
)
type Statistic struct {
Hours decimal.Decimal
Project string
Color (func(...interface {}) string)
Hours decimal.Decimal
Project string
Color (func(...interface{}) string)
}
type WeekStatistics map[string][]Statistic
type Week struct {
Statistics WeekStatistics
Statistics WeekStatistics
}
type Month struct {
Name string
Weeks [5]Week
Name string
Weeks [5]Week
}
type Calendar struct {
Months [12]Month
Distribution map[string]Statistic
TotalHours decimal.Decimal
Months [12]Month
Distribution map[string]Statistic
TotalHours decimal.Decimal
}
func NewCalendar(entries []Entry) (Calendar, error) {
cal := Calendar{}
cal := Calendar{}
cal.Distribution = make(map[string]Statistic)
cal.Distribution = make(map[string]Statistic)
projects := make(map[string]Project)
projects := make(map[string]Project)
for _, entry := range entries {
var entryFinish time.Time
endOfBeginDay := now.With(entry.Begin).EndOfDay()
sameDayHours := decimal.NewFromInt(0)
nextDayHours := decimal.NewFromInt(0)
for _, entry := range entries {
var entryFinish time.Time
endOfBeginDay := now.With(entry.Begin).EndOfDay()
sameDayHours := decimal.NewFromInt(0)
nextDayHours := decimal.NewFromInt(0)
projectId := GetIdFromName(entry.Project)
projectId := GetIdFromName(entry.Project)
if projects[projectId].Name == "" {
project, err := database.GetProject(entry.User, entry.Project)
if err != nil {
return cal, err
}
if projects[projectId].Name == "" {
project, err := database.GetProject(entry.User, entry.Project)
if err != nil {
return cal, err
}
projects[projectId] = project
}
projects[projectId] = project
}
if entry.Finish.IsZero() {
entryFinish = time.Now()
} else {
entryFinish = entry.Finish
}
if entry.Finish.IsZero() {
entryFinish = time.Now()
} else {
entryFinish = entry.Finish
}
/*
* Apparently the activity end is on a new day.
* This means we have to split the activity across two days.
*/
if endOfBeginDay.Before(entryFinish) == true {
startOfFinishDay := now.With(entryFinish).BeginningOfDay()
/*
* Apparently the activity end is on a new day.
* This means we have to split the activity across two days.
*/
if endOfBeginDay.Before(entryFinish) == true {
startOfFinishDay := now.With(entryFinish).BeginningOfDay()
sameDayDuration := endOfBeginDay.Sub(entry.Begin)
sameDay := sameDayDuration.Hours()
sameDayHours = decimal.NewFromFloat(sameDay)
sameDayDuration := endOfBeginDay.Sub(entry.Begin)
sameDay := sameDayDuration.Hours()
sameDayHours = decimal.NewFromFloat(sameDay)
nextDayDuration := entryFinish.Sub(startOfFinishDay)
nextDay := nextDayDuration.Hours()
nextDayHours = decimal.NewFromFloat(nextDay)
nextDayDuration := entryFinish.Sub(startOfFinishDay)
nextDay := nextDayDuration.Hours()
nextDayHours = decimal.NewFromFloat(nextDay)
} else {
sameDayDuration := entryFinish.Sub(entry.Begin)
sameDay := sameDayDuration.Hours()
sameDayHours = decimal.NewFromFloat(sameDay)
}
} else {
sameDayDuration := entryFinish.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]
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: GetColorFnFromHex(projects[projectId].Color),
}
stat := Statistic{
Hours: sameDayHours,
Project: entry.Project,
Color: GetColorFnFromHex(projects[projectId].Color),
}
if cal.Months[month0].Weeks[weeknumber0].Statistics == nil {
cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics)
}
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)
}
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(entryFinish)
month0 := month - 1
weeknumber0 := weeknumber - 1
weekday := entry.Begin.Weekday()
weekdayName := weekday.String()[:2]
if nextDayHours.GreaterThan(decimal.NewFromInt(0)) {
month, weeknumber := GetISOWeekInMonth(entryFinish)
month0 := month - 1
weeknumber0 := weeknumber - 1
weekday := entry.Begin.Weekday()
weekdayName := weekday.String()[:2]
stat := Statistic{
Hours: nextDayHours,
Project: entry.Project,
Color: GetColorFnFromHex(projects[projectId].Color),
}
stat := Statistic{
Hours: nextDayHours,
Project: entry.Project,
Color: GetColorFnFromHex(projects[projectId].Color),
}
if cal.Months[month0].Weeks[weeknumber0].Statistics == nil {
cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics)
}
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)
}
cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat)
}
var dist = cal.Distribution[entry.Project]
dist.Project = entry.Project
dist.Hours = dist.Hours.Add(sameDayHours)
dist.Hours = dist.Hours.Add(nextDayHours)
dist.Color = GetColorFnFromHex(projects[projectId].Color)
cal.Distribution[entry.Project] = dist
dist := cal.Distribution[entry.Project]
dist.Project = entry.Project
dist.Hours = dist.Hours.Add(sameDayHours)
dist.Hours = dist.Hours.Add(nextDayHours)
dist.Color = GetColorFnFromHex(projects[projectId].Color)
cal.Distribution[entry.Project] = dist
// fmt.Printf("Same Day: %s \n Next Day: %s \n Project Hours: %s\n", sameDayHours.String(), nextDayHours.String(), dist.Hours.String())
cal.TotalHours = cal.TotalHours.Add(sameDayHours)
cal.TotalHours = cal.TotalHours.Add(nextDayHours)
}
// fmt.Printf("Same Day: %s \n Next Day: %s \n Project Hours: %s\n", sameDayHours.String(), nextDayHours.String(), dist.Hours.String())
cal.TotalHours = cal.TotalHours.Add(sameDayHours)
cal.TotalHours = cal.TotalHours.Add(nextDayHours)
}
return cal, nil
return cal, nil
}
func (calendar *Calendar) GetOutputForWeekCalendar(date time.Time, month int, week int) (string) {
var output string = ""
var bars [][]string
var totalHours = decimal.NewFromInt(0)
func (calendar *Calendar) GetOutputForWeekCalendar(date time.Time, month int, week int) string {
var output string = ""
var bars [][]string
totalHours := decimal.NewFromInt(0)
var days = []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
for _, day := range days {
var dayHours = decimal.NewFromInt(0)
days := []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
for _, day := range days {
dayHours := decimal.NewFromInt(0)
for _, stat := range calendar.Months[month].Weeks[week].Statistics[day] {
dayHours = dayHours.Add(stat.Hours)
totalHours = totalHours.Add(stat.Hours)
}
for _, stat := range calendar.Months[month].Weeks[week].Statistics[day] {
dayHours = dayHours.Add(stat.Hours)
totalHours = totalHours.Add(stat.Hours)
}
if dayHours.GreaterThan(decimal.NewFromInt(24)) {
fmt.Printf("%s %s of week %d in month %d has more than 24h tracked; cutting at 24h now\n", CharError, day, (month+1), (week+1))
dayHours = decimal.NewFromInt(24)
}
if dayHours.GreaterThan(decimal.NewFromInt(24)) {
fmt.Printf("%s %s of week %d in month %d has more than 24h tracked; cutting at 24h now\n", CharError, day, (month + 1), (week + 1))
dayHours = decimal.NewFromInt(24)
}
bar := GetOutputBarForHours(dayHours, calendar.Months[month].Weeks[week].Statistics[day])
bars = append(bars, bar)
}
bar := GetOutputBarForHours(dayHours, calendar.Months[month].Weeks[week].Statistics[day])
bars = append(bars, bar)
}
output = fmt.Sprintf("CW %02d %s H\n", GetISOCalendarWeek(date), fmtHours(totalHours))
for row := 0; row < len(bars[0]); row++ {
output = fmt.Sprintf("%s%2d │", output, ((6 - row) * 4))
for col := 0; col < len(bars); col++ {
output = fmt.Sprintf("%s%s", output, bars[col][row])
}
output = fmt.Sprintf("%s\n", output)
}
output = fmt.Sprintf("%s └────────────────────────────\n %s %s %s %s %s %s %s\n",
output, days[0], days[1], days[2], days[3], days[4], days[5], days[6])
output = fmt.Sprintf("CW %02d %s H\n", GetISOCalendarWeek(date), fmtHours(totalHours))
for row := 0; row < len(bars[0]); row++ {
output = fmt.Sprintf("%s%2d │", output, ((6 - row) * 4))
for col := 0; col < len(bars); col++ {
output = fmt.Sprintf("%s%s", output, bars[col][row])
}
output = fmt.Sprintf("%s\n", output)
}
output = fmt.Sprintf("%s └────────────────────────────\n %s %s %s %s %s %s %s\n",
output, days[0], days[1], days[2], days[3], days[4], days[5], days[6])
return output
return output
}
func (calendar *Calendar) GetOutputForDistribution() (string) {
var output string = ""
func (calendar *Calendar) GetOutputForDistribution() string {
var output string = ""
// fmt.Printf("%s\n", calendar.TotalHours.String())
// fmt.Printf("%s\n", calendar.TotalHours.String())
var bar string = ""
for _, stat := range calendar.Distribution {
divided := stat.Hours.Div(calendar.TotalHours)
percentage := divided.Mul(decimal.NewFromInt(100))
hoursStr := fmtHours(stat.Hours)
percentageStr := percentage.StringFixed(2)
var bar string = ""
for _, stat := range calendar.Distribution {
divided := stat.Hours.Div(calendar.TotalHours)
percentage := divided.Mul(decimal.NewFromInt(100))
hoursStr := fmtHours(stat.Hours)
percentageStr := percentage.StringFixed(2)
dividedByBarLength := percentage.Div(decimal.NewFromInt(100))
percentageForBar := dividedByBarLength.Mul(decimal.NewFromInt(80))
percentageForBarInt := int(percentageForBar.Round(0).IntPart())
dividedByBarLength := percentage.Div(decimal.NewFromInt(100))
percentageForBar := dividedByBarLength.Mul(decimal.NewFromInt(80))
percentageForBarInt := int(percentageForBar.Round(0).IntPart())
bar = fmt.Sprintf("%s%s", bar, stat.Color(strings.Repeat("█", percentageForBarInt)))
bar = fmt.Sprintf("%s%s", bar, stat.Color(strings.Repeat("█", percentageForBarInt)))
output = fmt.Sprintf("%s%s%*s H / %*s %%\n", output, stat.Color(stat.Project), (68 - len(stat.Project)), hoursStr, 5, percentageStr)
}
output = fmt.Sprintf("%s%s%*s H / %*s %%\n", output, stat.Color(stat.Project), (68 - len(stat.Project)), hoursStr, 5, percentageStr)
}
output = fmt.Sprintf("DISTRIBUTION\n\n%s\n\n%s\n", bar, output)
return output
output = fmt.Sprintf("DISTRIBUTION\n\n%s\n\n%s\n", bar, output)
return output
}
+8 -9
View File
@@ -1,19 +1,18 @@
package z
const (
FlagNoColors string = "no-colors"
FlagDebug string = "debug"
FlagNoColors string = "no-colors"
FlagDebug string = "debug"
)
const (
TFAbsTwelveHour int = 0
TFAbsTwentyfourHour int = 1
TFRelHourMinute int = 2
TFRelHourFraction int = 3
TFAbsTwelveHour int = 0
TFAbsTwentyfourHour int = 1
TFRelHourMinute int = 2
TFRelHourFraction int = 3
)
const (
FinishWithMetadata int = 0
FinishOnlyTime int = 1
FinishWithMetadata int = 0
FinishOnlyTime int = 1
)
+217 -217
View File
@@ -1,321 +1,321 @@
package z
import (
"encoding/json"
"errors"
"log"
"sort"
"strings"
"encoding/json"
"errors"
"log"
"sort"
"strings"
"github.com/google/uuid"
"github.com/tidwall/buntdb"
"github.com/spf13/viper"
"github.com/google/uuid"
"github.com/spf13/viper"
"github.com/tidwall/buntdb"
)
type Database struct {
DB *buntdb.DB
DB *buntdb.DB
}
func InitDatabase() (*Database, error) {
dbfile := viper.GetString("db")
if dbfile == "" {
return nil, errors.New("please `export ZEIT_DB` to the location the zeit database should be stored at")
}
dbfile := viper.GetString("db")
if dbfile == "" {
return nil, errors.New("please `export ZEIT_DB` to the location the zeit database should be stored at")
}
db, err := buntdb.Open(dbfile)
if err != nil {
return nil, err
}
db, err := buntdb.Open(dbfile)
if err != nil {
return nil, err
}
db.CreateIndex("task", "*", buntdb.IndexJSON("task"))
db.CreateIndex("project", "*", buntdb.IndexJSON("project"))
db.CreateIndex("task", "*", buntdb.IndexJSON("task"))
db.CreateIndex("project", "*", buntdb.IndexJSON("project"))
database := Database{db}
return &database, nil
database := Database{db}
return &database, nil
}
func (database *Database) NewID() (string) {
id, err := uuid.NewRandom()
if err != nil {
log.Fatalln("could not generate UUID: %+v", err)
}
return id.String()
func (database *Database) NewID() string {
id, err := uuid.NewRandom()
if err != nil {
log.Fatalln("could not generate UUID: %+v", err)
}
return id.String()
}
func (database *Database) AddEntry(user string, entry Entry, setRunning bool) (string, error) {
id := database.NewID()
id := database.NewID()
entryJson, jsonerr := json.Marshal(entry)
if jsonerr != nil {
return id, jsonerr
}
entryJson, jsonerr := json.Marshal(entry)
if jsonerr != nil {
return id, jsonerr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
if setRunning == true {
_, _, seterr := tx.Set(user + ":status:running", id, nil)
if seterr != nil {
return seterr
}
}
_, _, seterr := tx.Set(user + ":entry:" + id, string(entryJson), nil)
if seterr != nil {
return seterr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
if setRunning == true {
_, _, seterr := tx.Set(user+":status:running", id, nil)
if seterr != nil {
return seterr
}
}
_, _, seterr := tx.Set(user+":entry:"+id, string(entryJson), nil)
if seterr != nil {
return seterr
}
return nil
})
return nil
})
return id, dberr
return id, dberr
}
func (database *Database) GetEntry(user string, entryId string) (Entry, error) {
var entry Entry
var entry Entry
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":entry:" + entryId)
if err != nil {
return err
}
json.Unmarshal([]byte(value), &entry)
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":entry:" + entryId)
if err != nil {
return err
}
json.Unmarshal([]byte(value), &entry)
entry.ID = entryId
return nil
})
entry.ID = entryId
return nil
})
return entry, dberr
return entry, dberr
}
func (database *Database) UpdateEntry(user string, entry Entry) (string, error) {
entryJson, jsonerr := json.Marshal(entry)
if jsonerr != nil {
return entry.ID, jsonerr
}
entryJson, jsonerr := json.Marshal(entry)
if jsonerr != nil {
return entry.ID, jsonerr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, seerr := tx.Set(user + ":entry:" + entry.ID, string(entryJson), nil)
if seerr != nil {
return seerr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, seerr := tx.Set(user+":entry:"+entry.ID, string(entryJson), nil)
if seerr != nil {
return seerr
}
return nil
})
return nil
})
return entry.ID, dberr
return entry.ID, dberr
}
func (database *Database) FinishEntry(user string, entry Entry) (string, error) {
entryJson, jsonerr := json.Marshal(entry)
if jsonerr != nil {
return entry.ID, jsonerr
}
entryJson, jsonerr := json.Marshal(entry)
if jsonerr != nil {
return entry.ID, jsonerr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
runningEntryId, grerr := tx.Get(user + ":status:running")
if grerr != nil {
return errors.New("no currently running entry found!")
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
runningEntryId, grerr := tx.Get(user + ":status:running")
if grerr != nil {
return errors.New("no currently running entry found!")
}
if runningEntryId != entry.ID {
return errors.New("specified entry is not currently running!")
}
if runningEntryId != entry.ID {
return errors.New("specified entry is not currently running!")
}
_, _, srerr := tx.Set(user + ":status:running", "", nil)
if srerr != nil {
return srerr
}
_, _, srerr := tx.Set(user+":status:running", "", nil)
if srerr != nil {
return srerr
}
_, _, seerr := tx.Set(user + ":entry:" + entry.ID, string(entryJson), nil)
if seerr != nil {
return seerr
}
_, _, seerr := tx.Set(user+":entry:"+entry.ID, string(entryJson), nil)
if seerr != nil {
return seerr
}
return nil
})
return nil
})
return entry.ID, dberr
return entry.ID, dberr
}
func (database *Database) EraseEntry(user string, id string) (error) {
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
return err
}
func (database *Database) EraseEntry(user string, id string) error {
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
return err
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
if runningEntryId == id {
_, _, seterr := tx.Set(user + ":status:running", "", nil)
if seterr != nil {
return seterr
}
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
if runningEntryId == id {
_, _, seterr := tx.Set(user+":status:running", "", nil)
if seterr != nil {
return seterr
}
}
_, delerr := tx.Delete(user + ":entry:" + id)
if delerr != nil {
return delerr
}
_, delerr := tx.Delete(user + ":entry:" + id)
if delerr != nil {
return delerr
}
return nil
})
return nil
})
return dberr
return dberr
}
func (database *Database) GetRunningEntryId(user string) (string, error) {
var runningId string = ""
var runningId string = ""
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":status:running")
if errors.Is(err, buntdb.ErrNotFound) {
return nil
}
if err != nil {
return err
}
runningId = value
return nil
})
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":status:running")
if errors.Is(err, buntdb.ErrNotFound) {
return nil
}
if err != nil {
return err
}
runningId = value
return nil
})
return runningId, dberr
return runningId, dberr
}
func (database *Database) ListEntries(user string) ([]Entry, error) {
var entries []Entry
var entries []Entry
dberr := database.DB.View(func(tx *buntdb.Tx) error {
tx.AscendKeys(user + ":entry:*", func(key, value string) bool {
var entry Entry
json.Unmarshal([]byte(value), &entry)
dberr := database.DB.View(func(tx *buntdb.Tx) error {
tx.AscendKeys(user+":entry:*", func(key, value string) bool {
var entry Entry
json.Unmarshal([]byte(value), &entry)
entry.SetIDFromDatabaseKey(key)
entry.SetIDFromDatabaseKey(key)
entries = append(entries, entry)
return true
})
entries = append(entries, entry)
return true
})
return nil
})
return nil
})
sort.Slice(entries, func(i, j int) bool { return entries[i].Begin.Before(entries[j].Begin) })
return entries, dberr
sort.Slice(entries, func(i, j int) bool { return entries[i].Begin.Before(entries[j].Begin) })
return entries, dberr
}
func (database *Database) GetImportsSHA1List(user string) (map[string]string, error) {
var sha1List = make(map[string]string)
sha1List := make(map[string]string)
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":imports:sha1", false)
if err != nil {
return nil
}
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user+":imports:sha1", false)
if err != nil {
return nil
}
sha1Entries := strings.Split(value, ",")
sha1Entries := strings.Split(value, ",")
for _, sha1Entry := range sha1Entries {
sha1EntrySplit := strings.Split(sha1Entry, ":")
sha1 := sha1EntrySplit[0]
id := sha1EntrySplit[1]
sha1List[sha1] = id
}
for _, sha1Entry := range sha1Entries {
sha1EntrySplit := strings.Split(sha1Entry, ":")
sha1 := sha1EntrySplit[0]
id := sha1EntrySplit[1]
sha1List[sha1] = id
}
return nil
})
return nil
})
return sha1List, dberr
return sha1List, dberr
}
func (database *Database) UpdateImportsSHA1List(user string, sha1List map[string]string) (error) {
var sha1Entries []string
func (database *Database) UpdateImportsSHA1List(user string, sha1List map[string]string) error {
var sha1Entries []string
for sha1, id := range sha1List {
sha1Entries = append(sha1Entries, sha1 + ":" + id)
}
for sha1, id := range sha1List {
sha1Entries = append(sha1Entries, sha1+":"+id)
}
value := strings.Join(sha1Entries, ",")
value := strings.Join(sha1Entries, ",")
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, seterr := tx.Set(user + ":imports:sha1", value, nil)
if seterr != nil {
return seterr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, seterr := tx.Set(user+":imports:sha1", value, nil)
if seterr != nil {
return seterr
}
return nil
})
return nil
})
return dberr
return dberr
}
func (database *Database) UpdateProject(user string, projectName string, project Project) (error) {
projectJson, jsonerr := json.Marshal(project)
if jsonerr != nil {
return jsonerr
}
func (database *Database) UpdateProject(user string, projectName string, project Project) error {
projectJson, jsonerr := json.Marshal(project)
if jsonerr != nil {
return jsonerr
}
projectId := GetIdFromName(projectName)
projectId := GetIdFromName(projectName)
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, sperr := tx.Set(user + ":project:" + projectId, string(projectJson), nil)
if sperr != nil {
return sperr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, sperr := tx.Set(user+":project:"+projectId, string(projectJson), nil)
if sperr != nil {
return sperr
}
return nil
})
return nil
})
return dberr
return dberr
}
func (database *Database) GetProject(user string, projectName string) (Project, error) {
var project Project
projectId := GetIdFromName(projectName)
var project Project
projectId := GetIdFromName(projectName)
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":project:" + projectId, false)
if err != nil {
return nil
}
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user+":project:"+projectId, false)
if err != nil {
return nil
}
json.Unmarshal([]byte(value), &project)
json.Unmarshal([]byte(value), &project)
return nil
})
return nil
})
return project, dberr
return project, dberr
}
func (database *Database) UpdateTask(user string, taskName string, task Task) (error) {
taskJson, jsonerr := json.Marshal(task)
if jsonerr != nil {
return jsonerr
}
func (database *Database) UpdateTask(user string, taskName string, task Task) error {
taskJson, jsonerr := json.Marshal(task)
if jsonerr != nil {
return jsonerr
}
taskId := GetIdFromName(taskName)
taskId := GetIdFromName(taskName)
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, sperr := tx.Set(user + ":task:" + taskId, string(taskJson), nil)
if sperr != nil {
return sperr
}
dberr := database.DB.Update(func(tx *buntdb.Tx) error {
_, _, sperr := tx.Set(user+":task:"+taskId, string(taskJson), nil)
if sperr != nil {
return sperr
}
return nil
})
return nil
})
return dberr
return dberr
}
func (database *Database) GetTask(user string, taskName string) (Task, error) {
var task Task
taskId := GetIdFromName(taskName)
var task Task
taskId := GetIdFromName(taskName)
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user + ":task:" + taskId, false)
if err != nil {
return nil
}
dberr := database.DB.View(func(tx *buntdb.Tx) error {
value, err := tx.Get(user+":task:"+taskId, false)
if err != nil {
return nil
}
json.Unmarshal([]byte(value), &task)
json.Unmarshal([]byte(value), &task)
return nil
})
return nil
})
return task, dberr
return task, dberr
}
+173 -173
View File
@@ -1,240 +1,240 @@
package z
import (
"errors"
"strings"
"time"
"fmt"
"github.com/gookit/color"
"github.com/shopspring/decimal"
"github.com/spf13/viper"
"errors"
"fmt"
"strings"
"time"
"github.com/gookit/color"
"github.com/shopspring/decimal"
"github.com/spf13/viper"
)
type Entry struct {
ID string `json:"-"`
Begin time.Time `json:"begin,omitempty"`
Finish time.Time `json:"finish,omitempty"`
Project string `json:"project,omitempty"`
Task string `json:"task,omitempty"`
Notes string `json:"notes,omitempty"`
User string `json:"user,omitempty"`
ID string `json:"-"`
Begin time.Time `json:"begin,omitempty"`
Finish time.Time `json:"finish,omitempty"`
Project string `json:"project,omitempty"`
Task string `json:"task,omitempty"`
Notes string `json:"notes,omitempty"`
User string `json:"user,omitempty"`
SHA1 string `json:"-"`
SHA1 string `json:"-"`
}
func NewEntry(
id string,
begin string,
finish string,
project string,
task string,
user string) (Entry, error) {
var err error
id string,
begin string,
finish string,
project string,
task string,
user string,
) (Entry, error) {
var err error
newEntry := Entry{}
newEntry := Entry{}
newEntry.ID = id
newEntry.Project = project
newEntry.Task = task
newEntry.User = user
newEntry.ID = id
newEntry.Project = project
newEntry.Task = task
newEntry.User = user
_, err = newEntry.SetBeginFromString(begin, time.Time{})
if err != nil {
return Entry{}, err
}
_, err = newEntry.SetBeginFromString(begin, time.Time{})
if err != nil {
return Entry{}, err
}
_, err = newEntry.SetFinishFromString(finish, time.Time{})
if err != nil {
return Entry{}, err
}
_, err = newEntry.SetFinishFromString(finish, time.Time{})
if err != nil {
return Entry{}, err
}
if id == "" && newEntry.IsFinishedAfterBegan() == false {
return Entry{}, errors.New("beginning time of tracking cannot be after finish time")
}
if id == "" && newEntry.IsFinishedAfterBegan() == false {
return Entry{}, errors.New("beginning time of tracking cannot be after finish time")
}
return newEntry, nil
return newEntry, nil
}
func (entry *Entry) SetIDFromDatabaseKey(key string) (error) {
splitKey := strings.Split(key, ":")
func (entry *Entry) SetIDFromDatabaseKey(key string) error {
splitKey := strings.Split(key, ":")
if len(splitKey) < 3 || len(splitKey) > 3 {
return errors.New("not a valid database key")
}
if len(splitKey) < 3 || len(splitKey) > 3 {
return errors.New("not a valid database key")
}
entry.ID = splitKey[2]
return nil
entry.ID = splitKey[2]
return nil
}
func (entry *Entry) SetBeginFromString(begin string, contextTime time.Time) (time.Time, error) {
var beginTime time.Time
var err error
var beginTime time.Time
var err error
if begin == "" {
beginTime = time.Now()
} else {
beginTime, err = ParseTime(begin, contextTime)
if err != nil {
return beginTime, err
}
}
if begin == "" {
beginTime = time.Now()
} else {
beginTime, err = ParseTime(begin, contextTime)
if err != nil {
return beginTime, err
}
}
entry.Begin = beginTime
entry.secondsBegin()
return entry.Begin, nil
entry.Begin = beginTime
entry.secondsBegin()
return entry.Begin, nil
}
func (entry *Entry) SetFinishFromString(finish string, contextTime time.Time) (time.Time, error) {
var finishTime time.Time
var err error
var finishTime time.Time
var err error
if finish != "" {
finishTime, err = ParseTime(finish, contextTime)
if err != nil {
return finishTime, err
}
}
if finish != "" {
finishTime, err = ParseTime(finish, contextTime)
if err != nil {
return finishTime, err
}
}
entry.Finish = finishTime
entry.secondsFinish()
return entry.Finish, nil
entry.Finish = finishTime
entry.secondsFinish()
return entry.Finish, nil
}
func (entry *Entry) IsFinishedAfterBegan() (bool) {
return (entry.Finish.IsZero() || entry.Begin.Before(entry.Finish) || entry.Begin.Equal(entry.Finish))
func (entry *Entry) IsFinishedAfterBegan() bool {
return (entry.Finish.IsZero() || entry.Begin.Before(entry.Finish) || entry.Begin.Equal(entry.Finish))
}
func (entry *Entry) GetOutputForTrack(isRunning bool, wasRunning bool) (string) {
var outputPrefix string = ""
var outputSuffix string = ""
func (entry *Entry) GetOutputForTrack(isRunning bool, wasRunning bool) string {
var outputPrefix string = ""
var outputSuffix string = ""
now := time.Now()
trackDiffNow := now.Sub(entry.Begin)
durationString := fmtDuration(trackDiffNow)
now := time.Now()
trackDiffNow := now.Sub(entry.Begin)
durationString := fmtDuration(trackDiffNow)
if isRunning == true && wasRunning == false {
outputPrefix = "began tracking"
} else if isRunning == true && wasRunning == true {
outputPrefix = "tracking"
outputSuffix = fmt.Sprintf(" for %sh", color.FgLightWhite.Render(durationString))
} else if isRunning == false && wasRunning == false {
outputPrefix = "tracked"
}
if isRunning == true && wasRunning == false {
outputPrefix = "began tracking"
} else if isRunning == true && wasRunning == true {
outputPrefix = "tracking"
outputSuffix = fmt.Sprintf(" for %sh", color.FgLightWhite.Render(durationString))
} else if isRunning == false && wasRunning == false {
outputPrefix = "tracked"
}
if entry.Task != "" && entry.Project != "" {
return fmt.Sprintf("%s %s %s on %s%s\n", CharTrack, outputPrefix, color.FgLightWhite.Render(entry.Task), color.FgLightWhite.Render(entry.Project), outputSuffix)
} else if entry.Task != "" && entry.Project == "" {
return fmt.Sprintf("%s %s %s%s\n", CharTrack, outputPrefix, color.FgLightWhite.Render(entry.Task), outputSuffix)
} else if entry.Task == "" && entry.Project != "" {
return fmt.Sprintf("%s %s task on %s%s\n", CharTrack, outputPrefix, color.FgLightWhite.Render(entry.Project), outputSuffix)
}
if entry.Task != "" && entry.Project != "" {
return fmt.Sprintf("%s %s %s on %s%s\n", CharTrack, outputPrefix, color.FgLightWhite.Render(entry.Task), color.FgLightWhite.Render(entry.Project), outputSuffix)
} else if entry.Task != "" && entry.Project == "" {
return fmt.Sprintf("%s %s %s%s\n", CharTrack, outputPrefix, color.FgLightWhite.Render(entry.Task), outputSuffix)
} else if entry.Task == "" && entry.Project != "" {
return fmt.Sprintf("%s %s task on %s%s\n", CharTrack, outputPrefix, color.FgLightWhite.Render(entry.Project), outputSuffix)
}
return fmt.Sprintf("%s %s task%s\n", CharTrack, outputPrefix, outputSuffix)
return fmt.Sprintf("%s %s task%s\n", CharTrack, outputPrefix, outputSuffix)
}
func (entry *Entry) GetDuration() (decimal.Decimal) {
duration := entry.Finish.Sub(entry.Begin)
if (duration < 0) {
duration = time.Now().Sub(entry.Begin)
}
return decimal.NewFromFloat(duration.Hours())
func (entry *Entry) GetDuration() decimal.Decimal {
duration := entry.Finish.Sub(entry.Begin)
if duration < 0 {
duration = time.Now().Sub(entry.Begin)
}
return decimal.NewFromFloat(duration.Hours())
}
func (entry *Entry) GetOutputForFinish() (string) {
var outputSuffix string = ""
func (entry *Entry) GetOutputForFinish() string {
var outputSuffix string = ""
trackDiff := entry.Finish.Sub(entry.Begin)
taskDuration := fmtDuration(trackDiff)
trackDiff := entry.Finish.Sub(entry.Begin)
taskDuration := fmtDuration(trackDiff)
outputSuffix = fmt.Sprintf(" for %sh", color.FgLightWhite.Render(taskDuration))
outputSuffix = fmt.Sprintf(" for %sh", color.FgLightWhite.Render(taskDuration))
if entry.Task != "" && entry.Project != "" {
return fmt.Sprintf("%s finished tracking %s on %s%s\n", CharFinish, color.FgLightWhite.Render(entry.Task), color.FgLightWhite.Render(entry.Project), outputSuffix)
} else if entry.Task != "" && entry.Project == "" {
return fmt.Sprintf("%s finished tracking %s%s\n", CharFinish, color.FgLightWhite.Render(entry.Task), outputSuffix)
} else if entry.Task == "" && entry.Project != "" {
return fmt.Sprintf("%s finished tracking task on %s%s\n", CharFinish, color.FgLightWhite.Render(entry.Project), outputSuffix)
}
if entry.Task != "" && entry.Project != "" {
return fmt.Sprintf("%s finished tracking %s on %s%s\n", CharFinish, color.FgLightWhite.Render(entry.Task), color.FgLightWhite.Render(entry.Project), outputSuffix)
} else if entry.Task != "" && entry.Project == "" {
return fmt.Sprintf("%s finished tracking %s%s\n", CharFinish, color.FgLightWhite.Render(entry.Task), outputSuffix)
} else if entry.Task == "" && entry.Project != "" {
return fmt.Sprintf("%s finished tracking task on %s%s\n", CharFinish, color.FgLightWhite.Render(entry.Project), outputSuffix)
}
return fmt.Sprintf("%s finished tracking task%s\n", CharFinish, outputSuffix)
return fmt.Sprintf("%s finished tracking task%s\n", CharFinish, outputSuffix)
}
func (entry *Entry) GetOutput(full bool) (string) {
var output string = ""
var entryFinish time.Time
var isRunning string = ""
func (entry *Entry) GetOutput(full bool) string {
var output string = ""
var entryFinish time.Time
var isRunning string = ""
if entry.Finish.IsZero() {
entryFinish = time.Now()
isRunning = "[running]"
} else {
entryFinish = entry.Finish
}
if entry.Finish.IsZero() {
entryFinish = time.Now()
isRunning = "[running]"
} else {
entryFinish = entry.Finish
}
trackDiff := entryFinish.Sub(entry.Begin)
taskDuration := fmtDuration(trackDiff)
if full == false {
trackDiff := entryFinish.Sub(entry.Begin)
taskDuration := fmtDuration(trackDiff)
if full == false {
output = fmt.Sprintf("%s %s on %s from %s to %s (%sh) %s",
color.FgGray.Render(entry.ID),
color.FgLightWhite.Render(entry.Task),
color.FgLightWhite.Render(entry.Project),
color.FgLightWhite.Render(entry.Begin.Format("2006-01-02 15:04 -0700")),
color.FgLightWhite.Render(entryFinish.Format("2006-01-02 15:04 -0700")),
color.FgLightWhite.Render(taskDuration),
color.FgLightYellow.Render(isRunning),
)
} else {
output = fmt.Sprintf("%s\n %s on %s\n %sh from %s to %s %s\n\n Notes:\n %s\n",
color.FgGray.Render(entry.ID),
color.FgLightWhite.Render(entry.Task),
color.FgLightWhite.Render(entry.Project),
color.FgLightWhite.Render(taskDuration),
color.FgLightWhite.Render(entry.Begin.Format("2006-01-02 15:04 -0700")),
color.FgLightWhite.Render(entryFinish.Format("2006-01-02 15:04 -0700")),
color.FgLightYellow.Render(isRunning),
color.FgLightWhite.Render(strings.Replace(entry.Notes, "\n", "\n ", -1)),
)
}
output = fmt.Sprintf("%s %s on %s from %s to %s (%sh) %s",
color.FgGray.Render(entry.ID),
color.FgLightWhite.Render(entry.Task),
color.FgLightWhite.Render(entry.Project),
color.FgLightWhite.Render(entry.Begin.Format("2006-01-02 15:04 -0700")),
color.FgLightWhite.Render(entryFinish.Format("2006-01-02 15:04 -0700")),
color.FgLightWhite.Render(taskDuration) ,
color.FgLightYellow.Render(isRunning),
)
} else {
output = fmt.Sprintf("%s\n %s on %s\n %sh from %s to %s %s\n\n Notes:\n %s\n",
color.FgGray.Render(entry.ID),
color.FgLightWhite.Render(entry.Task),
color.FgLightWhite.Render(entry.Project),
color.FgLightWhite.Render(taskDuration),
color.FgLightWhite.Render(entry.Begin.Format("2006-01-02 15:04 -0700")),
color.FgLightWhite.Render(entryFinish.Format("2006-01-02 15:04 -0700")),
color.FgLightYellow.Render(isRunning),
color.FgLightWhite.Render(strings.Replace(entry.Notes, "\n", "\n ", -1)),
)
}
return output
return output
}
func GetFilteredEntries(entries []Entry, project string, task string, since time.Time, until time.Time) ([]Entry, error) {
var filteredEntries []Entry
var filteredEntries []Entry
for _, entry := range entries {
if project != "" && GetIdFromName(entry.Project) != GetIdFromName(project) {
continue
}
for _, entry := range entries {
if project != "" && GetIdFromName(entry.Project) != GetIdFromName(project) {
continue
}
if task != "" && GetIdFromName(entry.Task) != GetIdFromName(task) {
continue
}
if task != "" && GetIdFromName(entry.Task) != GetIdFromName(task) {
continue
}
if since.IsZero() == false && since.Before(entry.Begin) == false && since.Equal(entry.Begin) == false {
continue
}
if since.IsZero() == false && since.Before(entry.Begin) == false && since.Equal(entry.Begin) == false {
continue
}
if until.IsZero() == false && until.After(entry.Finish) == false && until.Equal(entry.Finish) == false {
continue
}
if until.IsZero() == false && until.After(entry.Finish) == false && until.Equal(entry.Finish) == false {
continue
}
filteredEntries = append(filteredEntries, entry)
}
filteredEntries = append(filteredEntries, entry)
}
return filteredEntries, nil
return filteredEntries, nil
}
func (entry *Entry) secondsBegin() {
if viper.GetBool("time.no-seconds") {
entry.Begin = entry.Begin.Truncate(time.Duration(time.Minute))
}
if viper.GetBool("time.no-seconds") {
entry.Begin = entry.Begin.Truncate(time.Duration(time.Minute))
}
}
func (entry *Entry) secondsFinish() {
if viper.GetBool("time.no-seconds") {
entry.Finish = entry.Finish.Truncate(time.Duration(time.Minute))
}
if viper.GetBool("time.no-seconds") {
entry.Finish = entry.Finish.Truncate(time.Duration(time.Minute))
}
}
+63 -63
View File
@@ -1,83 +1,83 @@
package z
import (
"fmt"
"os"
"strings"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/cobra"
)
var entryCmd = &cobra.Command{
Use: "entry ([flags]) [id]",
Short: "Display or update activity",
Long: "Display or update tracked activity.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
id := args[0]
Use: "entry ([flags]) [id]",
Short: "Display or update activity",
Long: "Display or update tracked activity.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
id := args[0]
entry, err := database.GetEntry(user, id)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
entry, err := database.GetEntry(user, id)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if begin != "" || finish != "" || project != "" || notes != "" || task != "" {
if begin != "" {
entry.Begin, err = entry.SetBeginFromString(begin, entry.Begin)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if begin != "" || finish != "" || project != "" || notes != "" || task != "" {
if begin != "" {
entry.Begin, err = entry.SetBeginFromString(begin, entry.Begin)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if finish != "" {
entry.Finish, err = entry.SetFinishFromString(finish, entry.Finish)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if finish != "" {
entry.Finish, err = entry.SetFinishFromString(finish, entry.Finish)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if project != "" {
entry.Project = project
}
if project != "" {
entry.Project = project
}
if task != "" {
entry.Task = task
}
if task != "" {
entry.Task = task
}
if notes != "" {
entry.Notes = strings.Replace(notes, "\\n", "\n", -1)
}
if notes != "" {
entry.Notes = strings.Replace(notes, "\\n", "\n", -1)
}
_, err = database.UpdateEntry(user, entry)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
_, err = database.UpdateEntry(user, entry)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
fmt.Printf("%s %s\n", CharInfo, entry.GetOutput(true))
return
},
fmt.Printf("%s %s\n", CharInfo, entry.GetOutput(true))
return
},
}
func init() {
rootCmd.AddCommand(entryCmd)
entryCmd.Flags().StringVarP(&begin, "begin", "b", "", "Update date/time the activity began at")
entryCmd.Flags().StringVarP(&finish, "finish", "s", "", "Update date/time the activity finished at")
entryCmd.Flags().StringVarP(&project, "project", "p", "", "Update activity project")
entryCmd.Flags().StringVarP(&notes, "notes", "n", "", "Update activity notes")
entryCmd.Flags().StringVarP(&task, "task", "t", "", "Update activity task")
entryCmd.Flags().BoolVar(&fractional, "decimal", false, "Show fractional hours in decimal format instead of minutes")
rootCmd.AddCommand(entryCmd)
entryCmd.Flags().StringVarP(&begin, "begin", "b", "", "Update date/time the activity began at")
entryCmd.Flags().StringVarP(&finish, "finish", "s", "", "Update date/time the activity finished at")
entryCmd.Flags().StringVarP(&project, "project", "p", "", "Update activity project")
entryCmd.Flags().StringVarP(&notes, "notes", "n", "", "Update activity notes")
entryCmd.Flags().StringVarP(&task, "task", "t", "", "Update activity task")
entryCmd.Flags().BoolVar(&fractional, "decimal", false, "Show fractional hours in decimal format instead of minutes")
flagName := "task"
entryCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
flagName := "task"
entryCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
}
+21 -20
View File
@@ -1,32 +1,33 @@
package z
import (
"os"
"fmt"
"github.com/spf13/cobra"
"github.com/gookit/color"
"fmt"
"os"
"github.com/gookit/color"
"github.com/spf13/cobra"
)
var eraseCmd = &cobra.Command{
Use: "erase ([flags]) [id]",
Short: "Erase activity",
Long: "Erase tracked activity.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
id := args[0]
Use: "erase ([flags]) [id]",
Short: "Erase activity",
Long: "Erase tracked activity.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
id := args[0]
err := database.EraseEntry(user, id)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
err := database.EraseEntry(user, id)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Printf("%s erased %s\n", CharInfo, color.FgLightWhite.Render(id))
return
},
fmt.Printf("%s erased %s\n", CharInfo, color.FgLightWhite.Render(id))
return
},
}
func init() {
rootCmd.AddCommand(eraseCmd)
rootCmd.AddCommand(eraseCmd)
}
+72 -71
View File
@@ -1,95 +1,96 @@
package z
import (
"os"
"fmt"
"strings"
"encoding/json"
"github.com/spf13/cobra"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
func exportZeitJson(user string, entries []Entry) (string, error) {
stringified, err := json.Marshal(entries)
if err != nil {
return "", err
}
stringified, err := json.Marshal(entries)
if err != nil {
return "", err
}
return string(stringified), nil
return string(stringified), nil
}
func exportTymeJson(user string, entries []Entry) (string, error) {
tyme := Tyme{}
err := tyme.FromEntries(entries)
if err != nil {
return "", err
}
tyme := Tyme{}
err := tyme.FromEntries(entries)
if err != nil {
return "", err
}
return tyme.Stringify(), nil
return tyme.Stringify(), nil
}
var exportCmd = &cobra.Command{
Use: "export ([flags])",
Short: "Export tracked activities",
Long: "Export tracked activities to various formats.",
// Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var entries []Entry
var err error
Use: "export ([flags])",
Short: "Export tracked activities",
Long: "Export tracked activities to various formats.",
// Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var entries []Entry
var err error
user := GetCurrentUser()
user := GetCurrentUser()
entries, err = database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
sinceTime, untilTime := ParseSinceUntil(since, until, listRange)
entries, err = database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
sinceTime, untilTime := ParseSinceUntil(since, until, listRange)
var filteredEntries []Entry
filteredEntries, err = GetFilteredEntries(entries, project, task, sinceTime, untilTime)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
var filteredEntries []Entry
filteredEntries, err = GetFilteredEntries(entries, project, task, sinceTime, untilTime)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
var output string = ""
switch(format) {
case "zeit":
output, err = exportZeitJson(user, filteredEntries)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
case "tyme":
output, err = exportTymeJson(user, filteredEntries)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
default:
fmt.Printf("%s specify an export format; see `zeit export --help` for more info\n", CharError)
os.Exit(1)
}
var output string = ""
switch format {
case "zeit":
output, err = exportZeitJson(user, filteredEntries)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
case "tyme":
output, err = exportTymeJson(user, filteredEntries)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
default:
fmt.Printf("%s specify an export format; see `zeit export --help` for more info\n", CharError)
os.Exit(1)
}
fmt.Printf("%s\n", output)
return
},
fmt.Printf("%s\n", output)
return
},
}
func init() {
rootCmd.AddCommand(exportCmd)
exportCmd.Flags().StringVar(&format, "format", "zeit", "Format to export, possible values: zeit, tyme")
exportCmd.Flags().StringVar(&since, "since", "", "Date/time to start the export from")
exportCmd.Flags().StringVar(&until, "until", "", "Date/time to export until")
exportCmd.Flags().StringVar(&listRange, "range", "", "Shortcut for --since and --until that accepts: " + strings.Join(Ranges(), ", "))
exportCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be exported")
exportCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be exported")
rootCmd.AddCommand(exportCmd)
exportCmd.Flags().StringVar(&format, "format", "zeit", "Format to export, possible values: zeit, tyme")
exportCmd.Flags().StringVar(&since, "since", "", "Date/time to start the export from")
exportCmd.Flags().StringVar(&until, "until", "", "Date/time to export until")
exportCmd.Flags().StringVar(&listRange, "range", "", "Shortcut for --since and --until that accepts: "+strings.Join(Ranges(), ", "))
exportCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be exported")
exportCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be exported")
flagName := "task"
exportCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
flagName := "task"
exportCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
}
+20 -20
View File
@@ -1,31 +1,31 @@
package z
import (
"github.com/spf13/cobra"
"github.com/spf13/cobra"
)
var finishCmd = &cobra.Command{
Use: "finish",
Short: "Finish currently running activity",
Long: "Finishing tracking of currently running activity.",
Run: func(cmd *cobra.Command, args []string) {
finishTask(FinishWithMetadata)
},
Use: "finish",
Short: "Finish currently running activity",
Long: "Finishing tracking of currently running activity.",
Run: func(cmd *cobra.Command, args []string) {
finishTask(FinishWithMetadata)
},
}
func init() {
rootCmd.AddCommand(finishCmd)
finishCmd.Flags().StringVarP(&begin, "begin", "b", "", "Time the activity should begin at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
finishCmd.Flags().StringVarP(&finish, "finish", "s", "", "Time the activity should finish at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).\nMust be after --begin time.")
finishCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be assigned")
finishCmd.Flags().StringVarP(&notes, "notes", "n", "", "Activity notes")
finishCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be assigned")
rootCmd.AddCommand(finishCmd)
finishCmd.Flags().StringVarP(&begin, "begin", "b", "", "Time the activity should begin at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
finishCmd.Flags().StringVarP(&finish, "finish", "s", "", "Time the activity should finish at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).\nMust be after --begin time.")
finishCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be assigned")
finishCmd.Flags().StringVarP(&notes, "notes", "n", "", "Activity notes")
finishCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be assigned")
flagName := "task"
finishCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
flagName := "task"
finishCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
}
+196 -198
View File
@@ -1,264 +1,262 @@
package z
import (
"os/user"
"os/exec"
"bytes"
"regexp"
"strconv"
"strings"
"time"
"math"
"errors"
"fmt"
"os"
"bytes"
"errors"
"fmt"
"math"
"os"
"os/exec"
"os/user"
"regexp"
"strconv"
"strings"
"time"
"github.com/araddon/dateparse"
"github.com/spf13/viper"
"github.com/jinzhu/now"
"github.com/araddon/dateparse"
"github.com/jinzhu/now"
"github.com/spf13/viper"
)
func TimeFormats() []string {
return []string{
`^\d{1,2}:\d{1,2}(am|pm)$`, // Absolute twelve hour format
`^\d{1,2}:\d{1,2}$`, // Absolute twenty four hour format
`^([+-])(\d{1,2}):(\d{1,2})$`, // Relative hour:minute format
`^([+-])(\d{1,2})\.(\d{1,2})$`, // Relative hour.fraction format
}
return []string{
`^\d{1,2}:\d{1,2}(am|pm)$`, // Absolute twelve hour format
`^\d{1,2}:\d{1,2}$`, // Absolute twenty four hour format
`^([+-])(\d{1,2}):(\d{1,2})$`, // Relative hour:minute format
`^([+-])(\d{1,2})\.(\d{1,2})$`, // Relative hour.fraction format
}
}
func GetCurrentUser() (string) {
user, err := user.Current()
if err != nil {
return "unknown"
}
func GetCurrentUser() string {
user, err := user.Current()
if err != nil {
return "unknown"
}
return user.Username
return user.Username
}
func GetTimeFormat(timeStr string) (int) {
var matched bool
var regerr error
func GetTimeFormat(timeStr string) int {
var matched bool
var regerr error
for timeFormatId, timeFormat := range TimeFormats() {
matched, regerr = regexp.MatchString(timeFormat, timeStr)
if regerr != nil {
return -1
}
for timeFormatId, timeFormat := range TimeFormats() {
matched, regerr = regexp.MatchString(timeFormat, timeStr)
if regerr != nil {
return -1
}
if matched == true {
return timeFormatId
}
}
if matched == true {
return timeFormatId
}
}
return -1
return -1
}
// TODO: Use https://golang.org/pkg/time/#ParseDuration
func RelToTime(timeStr string, ftId int, contextTime time.Time) (time.Time, error) {
var re = regexp.MustCompile(TimeFormats()[ftId])
gm := re.FindStringSubmatch(timeStr)
re := regexp.MustCompile(TimeFormats()[ftId])
gm := re.FindStringSubmatch(timeStr)
if len(gm) < 4 {
return time.Now(), errors.New("No match")
}
if len(gm) < 4 {
return time.Now(), errors.New("No match")
}
var hours int = 0
var minutes int = 0
var hours int = 0
var minutes int = 0
if ftId == TFRelHourFraction {
f, _ := strconv.ParseFloat(gm[2] + "." + gm[3], 32)
minutes = int(f * 60.0)
} else {
hours, _ = strconv.Atoi(gm[2])
minutes, _ = strconv.Atoi(gm[3])
}
if ftId == TFRelHourFraction {
f, _ := strconv.ParseFloat(gm[2]+"."+gm[3], 32)
minutes = int(f * 60.0)
} else {
hours, _ = strconv.Atoi(gm[2])
minutes, _ = strconv.Atoi(gm[3])
}
var t time.Time
var t time.Time
if viper.IsSet("time.relative") && viper.GetString("time.relative") == "context" && !contextTime.IsZero() {
switch gm[1] {
case "+":
t = contextTime.Add(time.Hour * time.Duration(hours) + time.Minute * time.Duration(minutes))
case "-":
t = contextTime.Add((time.Hour * time.Duration(hours) + time.Minute * time.Duration(minutes)) * -1)
}
if viper.IsSet("time.relative") && viper.GetString("time.relative") == "context" && !contextTime.IsZero() {
switch gm[1] {
case "+":
t = contextTime.Add(time.Hour*time.Duration(hours) + time.Minute*time.Duration(minutes))
case "-":
t = contextTime.Add((time.Hour*time.Duration(hours) + time.Minute*time.Duration(minutes)) * -1)
}
return t, nil
}
return t, nil
}
switch gm[1] {
case "+":
t = time.Now().Local().Add(time.Hour * time.Duration(hours) + time.Minute * time.Duration(minutes))
case "-":
t = time.Now().Local().Add((time.Hour * time.Duration(hours) + time.Minute * time.Duration(minutes)) * -1)
}
switch gm[1] {
case "+":
t = time.Now().Local().Add(time.Hour*time.Duration(hours) + time.Minute*time.Duration(minutes))
case "-":
t = time.Now().Local().Add((time.Hour*time.Duration(hours) + time.Minute*time.Duration(minutes)) * -1)
}
return t, nil
return t, nil
}
func ParseTime(timeStr string, contextTime time.Time) (time.Time, error) {
tfId := GetTimeFormat(timeStr)
tfId := GetTimeFormat(timeStr)
t:= time.Now()
t := time.Now()
switch tfId {
case TFAbsTwelveHour:
tadj, err := time.Parse("3:04pm", timeStr)
tnew := time.Date(t.Year(), t.Month(), t.Day(), tadj.Hour(), tadj.Minute(), t.Second(), t.Nanosecond(), t.Location())
return tnew, err
case TFAbsTwentyfourHour:
tadj, err := time.Parse("15:04", timeStr)
tnew := time.Date(t.Year(), t.Month(), t.Day(), tadj.Hour(), tadj.Minute(), t.Second(), t.Nanosecond(), t.Location())
return tnew, err
case TFRelHourMinute, TFRelHourFraction:
return RelToTime(timeStr, tfId, contextTime)
default:
loc, err := time.LoadLocation("Local")
switch tfId {
case TFAbsTwelveHour:
tadj, err := time.Parse("3:04pm", timeStr)
tnew := time.Date(t.Year(), t.Month(), t.Day(), tadj.Hour(), tadj.Minute(), t.Second(), t.Nanosecond(), t.Location())
return tnew, err
case TFAbsTwentyfourHour:
tadj, err := time.Parse("15:04", timeStr)
tnew := time.Date(t.Year(), t.Month(), t.Day(), tadj.Hour(), tadj.Minute(), t.Second(), t.Nanosecond(), t.Location())
return tnew, err
case TFRelHourMinute, TFRelHourFraction:
return RelToTime(timeStr, tfId, contextTime)
default:
loc, err := time.LoadLocation("Local")
if err != nil {
return time.Now(), errors.New("could not load location")
}
if err != nil {
return time.Now(), errors.New("could not load location")
}
time.Local = loc
time.Local = loc
tnew, err := dateparse.ParseIn(timeStr, loc)
if err != nil {
return time.Now(), errors.New("could not match passed time")
}
tnew, err := dateparse.ParseIn(timeStr, loc)
if err != nil {
return time.Now(), errors.New("could not match passed time")
}
return tnew, nil
}
return tnew, nil
}
}
func GetIdFromName(name string) string {
reg, regerr := regexp.Compile("[^a-zA-Z0-9]+")
if regerr != nil {
return ""
}
reg, regerr := regexp.Compile("[^a-zA-Z0-9]+")
if regerr != nil {
return ""
}
id := strings.ToLower(reg.ReplaceAllString(name, ""))
id := strings.ToLower(reg.ReplaceAllString(name, ""))
return id
return id
}
func GetISOCalendarWeek(date time.Time) (int) {
var _, cw = date.ISOWeek()
return cw
func GetISOCalendarWeek(date time.Time) int {
_, cw := date.ISOWeek()
return cw
}
func GetISOWeekInMonth(date time.Time) (month int, weeknumber int) {
if date.IsZero() {
return -1, -1
}
if date.IsZero() {
return -1, -1
}
newDay := (date.Day() - int(date.Weekday()) + 1)
addDay := (date.Day() - newDay) * -1
changedDate := date.AddDate(0, 0, addDay)
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));
return int(changedDate.Month()), int(math.Ceil(float64(changedDate.Day()) / 7.0))
}
func GetGitLog(repo string, since time.Time, until time.Time) (string, string, error) {
var stdout, stderr bytes.Buffer
cmd := exec.Command("git", "-C", repo, "config", "user.name")
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", "", err
}
gitUserStr, gitUserErrStr := string(stdout.Bytes()), string(stderr.Bytes())
if gitUserStr == "" && gitUserErrStr != "" {
return gitUserStr, gitUserErrStr, errors.New(gitUserErrStr)
}
var stdout, stderr bytes.Buffer
cmd := exec.Command("git", "-C", repo, "config", "user.name")
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", "", err
}
gitUserStr, gitUserErrStr := string(stdout.Bytes()), string(stderr.Bytes())
if gitUserStr == "" && gitUserErrStr != "" {
return gitUserStr, gitUserErrStr, errors.New(gitUserErrStr)
}
stdout.Reset()
stderr.Reset()
stdout.Reset()
stderr.Reset()
cmd = exec.Command("git", "-C", repo, "log", "--author", gitUserStr, "--since", since.Format("2006-01-02T15:04:05-0700"), "--until", until.Format("2006-01-02T15:04:05-0700"), "--pretty=oneline")
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
return "", "", err
}
cmd = exec.Command("git", "-C", repo, "log", "--author", gitUserStr, "--since", since.Format("2006-01-02T15:04:05-0700"), "--until", until.Format("2006-01-02T15:04:05-0700"), "--pretty=oneline")
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
return "", "", err
}
stdoutStr, stderrStr := string(stdout.Bytes()), string(stderr.Bytes())
return stdoutStr, stderrStr, nil
stdoutStr, stderrStr := string(stdout.Bytes()), string(stderr.Bytes())
return stdoutStr, stderrStr, nil
}
func Ranges() []string {
return []string{
"today",
"yesterday",
"thisWeek",
"lastWeek",
"thisMonth",
"lastMonth",
}
return []string{
"today",
"yesterday",
"thisWeek",
"lastWeek",
"thisMonth",
"lastMonth",
}
}
func ParseSinceUntil(since string, until string, listRange string) (time.Time, time.Time) {
var sinceTime time.Time
var untilTime time.Time
var err error
var sinceTime time.Time
var untilTime time.Time
var err error
if since != "" {
sinceTime, err = now.Parse(since)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if since != "" {
sinceTime, err = now.Parse(since)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if until != "" {
untilTime, err = now.Parse(until)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if until != "" {
untilTime, err = now.Parse(until)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
if listRange != "" {
if since != "" || until != "" {
fmt.Println("Range and since/until can't be used together, select one of them")
os.Exit(1)
}
if listRange != "" {
if since != "" || until != "" {
fmt.Println("Range and since/until can't be used together, select one of them")
os.Exit(1)
}
if viper.GetBool("firstWeekDayMonday") {
now.WeekStartDay = time.Monday
}
if viper.GetBool("firstWeekDayMonday") {
now.WeekStartDay = time.Monday
}
loc, _ := time.LoadLocation("Local")
time.Local = loc
switch strings.ToLower(listRange) {
case "today":
sinceTime = now.BeginningOfDay()
untilTime = now.EndOfDay()
case "yesterday":
sinceTime = now.BeginningOfDay().AddDate(0, 0, -1)
untilTime = now.EndOfDay().AddDate(0, 0, -1)
case "thisweek":
sinceTime = now.BeginningOfWeek()
untilTime = now.EndOfWeek()
case "lastweek":
lastWeekDay := time.Now().AddDate(0, 0, -7)
sinceTime = now.With(lastWeekDay).BeginningOfWeek()
untilTime = now.With(lastWeekDay).EndOfWeek()
case "thismonth":
sinceTime = now.BeginningOfMonth()
untilTime = now.EndOfMonth()
case "lastmonth":
lastMonthDay := time.Now().AddDate(0, -1, 0)
sinceTime = now.With(lastMonthDay).BeginningOfMonth()
untilTime = now.With(lastMonthDay).EndOfMonth()
default:
fmt.Println("Unknown range selection, possible options: ", strings.Join(Ranges(), " "))
os.Exit(1)
}
}
loc, _ := time.LoadLocation("Local")
time.Local = loc
switch strings.ToLower(listRange) {
case "today":
sinceTime = now.BeginningOfDay()
untilTime = now.EndOfDay()
case "yesterday":
sinceTime = now.BeginningOfDay().AddDate(0, 0, -1)
untilTime = now.EndOfDay().AddDate(0, 0, -1)
case "thisweek":
sinceTime = now.BeginningOfWeek()
untilTime = now.EndOfWeek()
case "lastweek":
lastWeekDay := time.Now().AddDate(0, 0, -7)
sinceTime = now.With(lastWeekDay).BeginningOfWeek()
untilTime = now.With(lastWeekDay).EndOfWeek()
case "thismonth":
sinceTime = now.BeginningOfMonth()
untilTime = now.EndOfMonth()
case "lastmonth":
lastMonthDay := time.Now().AddDate(0, -1, 0)
sinceTime = now.With(lastMonthDay).BeginningOfMonth()
untilTime = now.With(lastMonthDay).EndOfMonth()
default:
fmt.Println("Unknown range selection, possible options: ", strings.Join(Ranges(), " "))
os.Exit(1)
}
}
return sinceTime, untilTime
return sinceTime, untilTime
}
+83 -82
View File
@@ -1,111 +1,112 @@
package z
import (
"os"
"fmt"
"time"
"github.com/spf13/cobra"
"github.com/gookit/color"
"github.com/cnf/structhash"
"fmt"
"os"
"time"
"github.com/cnf/structhash"
"github.com/gookit/color"
"github.com/spf13/cobra"
)
func importTymeJson(user string, file string) ([]Entry, error) {
var entries []Entry
var entries []Entry
tyme := Tyme{}
tyme.Load(file)
tyme := Tyme{}
tyme.Load(file)
for _, tymeEntry := range tyme.Data {
tymeEntrySHA1 := structhash.Sha1(tymeEntry, 1)
tymeStart, err := time.Parse("2006-01-02T15:04:05-07:00", tymeEntry.Start)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
continue
}
for _, tymeEntry := range tyme.Data {
tymeEntrySHA1 := structhash.Sha1(tymeEntry, 1)
tymeStart, err := time.Parse("2006-01-02T15:04:05-07:00", tymeEntry.Start)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
continue
}
tymeEnd, err := time.Parse("2006-01-02T15:04:05-07:00", tymeEntry.End)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
continue
}
tymeEnd, err := time.Parse("2006-01-02T15:04:05-07:00", tymeEntry.End)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
continue
}
entry, err := NewEntry("", "", "", tymeEntry.Project, tymeEntry.Task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
continue
}
entry, err := NewEntry("", "", "", tymeEntry.Project, tymeEntry.Task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
continue
}
entry.Begin = tymeStart
entry.Finish = tymeEnd
entry.Begin = tymeStart
entry.Finish = tymeEnd
entry.SHA1 = fmt.Sprintf("%x", tymeEntrySHA1)
entry.SHA1 = fmt.Sprintf("%x", tymeEntrySHA1)
entries = append(entries, entry)
}
entries = append(entries, entry)
}
return entries, nil
return entries, nil
}
var importCmd = &cobra.Command{
Use: "import ([flags]) [file]",
Short: "Import tracked activities",
Long: "Import tracked activities from various formats.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var entries []Entry
var err error
Use: "import ([flags]) [file]",
Short: "Import tracked activities",
Long: "Import tracked activities from various formats.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var entries []Entry
var err error
user := GetCurrentUser()
user := GetCurrentUser()
switch(format) {
case "zeit":
// TODO:
fmt.Printf("%s not yet implemented\n", CharError)
os.Exit(1)
case "tyme":
entries, err = importTymeJson(user, args[0])
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
default:
fmt.Printf("%s specify an import format; see `zeit import --help` for more info\n", CharError)
os.Exit(1)
}
switch format {
case "zeit":
// TODO:
fmt.Printf("%s not yet implemented\n", CharError)
os.Exit(1)
case "tyme":
entries, err = importTymeJson(user, args[0])
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
default:
fmt.Printf("%s specify an import format; see `zeit import --help` for more info\n", CharError)
os.Exit(1)
}
sha1List, sha1Err := database.GetImportsSHA1List(user)
if sha1Err != nil {
fmt.Printf("%s %+v\n", CharError, sha1Err)
os.Exit(1)
}
sha1List, sha1Err := database.GetImportsSHA1List(user)
if sha1Err != nil {
fmt.Printf("%s %+v\n", CharError, sha1Err)
os.Exit(1)
}
for _, entry := range entries {
if id, ok := sha1List[entry.SHA1]; ok {
fmt.Printf("%s %s was previously imported as %s; not importing again\n", CharInfo, color.FgLightWhite.Render(entry.SHA1), color.FgLightWhite.Render(id))
continue
}
for _, entry := range entries {
if id, ok := sha1List[entry.SHA1]; ok {
fmt.Printf("%s %s was previously imported as %s; not importing again\n", CharInfo, color.FgLightWhite.Render(entry.SHA1), color.FgLightWhite.Render(id))
continue
}
importedId, err := database.AddEntry(user, entry, false)
if err != nil {
fmt.Printf("%s %s could not be imported: %+v\n", CharError, color.FgLightWhite.Render(entry.SHA1), color.FgRed.Render(err))
continue
}
importedId, err := database.AddEntry(user, entry, false)
if err != nil {
fmt.Printf("%s %s could not be imported: %+v\n", CharError, color.FgLightWhite.Render(entry.SHA1), color.FgRed.Render(err))
continue
}
fmt.Printf("%s %s was imported as %s\n", CharInfo, color.FgLightWhite.Render(entry.SHA1), color.FgLightWhite.Render(importedId))
sha1List[entry.SHA1] = importedId
}
fmt.Printf("%s %s was imported as %s\n", CharInfo, color.FgLightWhite.Render(entry.SHA1), color.FgLightWhite.Render(importedId))
sha1List[entry.SHA1] = importedId
}
err = database.UpdateImportsSHA1List(user, sha1List)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
err = database.UpdateImportsSHA1List(user, sha1List)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
return
},
return
},
}
func init() {
rootCmd.AddCommand(importCmd)
importCmd.Flags().StringVar(&format, "format", "zeit", "Format to import, possible values: zeit, tyme")
rootCmd.AddCommand(importCmd)
importCmd.Flags().StringVar(&format, "format", "zeit", "Format to import, possible values: zeit, tyme")
}
+43 -43
View File
@@ -1,58 +1,58 @@
package z
import (
"fmt"
"strings"
"fmt"
"strings"
"github.com/shopspring/decimal"
"github.com/spf13/cobra"
"github.com/shopspring/decimal"
"github.com/spf13/cobra"
)
var listTotalTime bool
var listOnlyProjectsAndTasks bool
var listOnlyTasks bool
var appendProjectIDToTask bool
var (
listTotalTime bool
listOnlyProjectsAndTasks bool
listOnlyTasks bool
appendProjectIDToTask bool
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List activities",
Long: "List all tracked activities.",
Run: func(cmd *cobra.Command, args []string) {
Use: "list",
Short: "List activities",
Long: "List all tracked activities.",
Run: func(cmd *cobra.Command, args []string) {
filteredEntries := listEntries()
filteredEntries := listEntries()
totalHours := decimal.NewFromInt(0)
for _, entry := range filteredEntries {
totalHours = totalHours.Add(entry.GetDuration())
fmt.Printf("%s\n", entry.GetOutput(false))
}
totalHours := decimal.NewFromInt(0)
for _, entry := range filteredEntries {
totalHours = totalHours.Add(entry.GetDuration())
fmt.Printf("%s\n", entry.GetOutput(false))
}
if listTotalTime == true {
fmt.Printf("\nTOTAL: %s H\n\n", fmtHours(totalHours));
}
return
},
if listTotalTime == true {
fmt.Printf("\nTOTAL: %s H\n\n", fmtHours(totalHours))
}
return
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().StringVar(&since, "since", "", "Date/time to start the list from")
listCmd.Flags().StringVar(&until, "until", "", "Date/time to list until")
listCmd.Flags().StringVar(&listRange, "range", "", "Shortcut for --since and --until that accepts: " + strings.Join(Ranges(), ", "))
listCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be listed")
listCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be listed")
listCmd.Flags().BoolVar(&fractional, "decimal", false, "Show fractional hours in decimal format instead of minutes")
listCmd.Flags().BoolVar(&listTotalTime, "total", false, "Show total time of hours for listed activities")
listCmd.Flags().BoolVar(&listOnlyProjectsAndTasks, "only-projects-and-tasks", false, "Only list projects and their tasks, no entries")
listCmd.Flags().BoolVar(&listOnlyTasks, "only-tasks", false, "Only list tasks, no projects nor entries")
listCmd.Flags().BoolVar(&appendProjectIDToTask, "append-project-id-to-task", false, "Append project ID to tasks in the list")
rootCmd.AddCommand(listCmd)
listCmd.Flags().StringVar(&since, "since", "", "Date/time to start the list from")
listCmd.Flags().StringVar(&until, "until", "", "Date/time to list until")
listCmd.Flags().StringVar(&listRange, "range", "", "Shortcut for --since and --until that accepts: "+strings.Join(Ranges(), ", "))
listCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be listed")
listCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be listed")
listCmd.Flags().BoolVar(&fractional, "decimal", false, "Show fractional hours in decimal format instead of minutes")
listCmd.Flags().BoolVar(&listTotalTime, "total", false, "Show total time of hours for listed activities")
listCmd.Flags().BoolVar(&listOnlyProjectsAndTasks, "only-projects-and-tasks", false, "Only list projects and their tasks, no entries")
listCmd.Flags().BoolVar(&listOnlyTasks, "only-tasks", false, "Only list tasks, no projects nor entries")
listCmd.Flags().BoolVar(&appendProjectIDToTask, "append-project-id-to-task", false, "Append project ID to tasks in the list")
flagName := "task"
listCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
flagName := "task"
listCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
}
+2 -5
View File
@@ -1,9 +1,6 @@
package z
import (
)
type Project struct {
Name string `json:"name,omitempty"`
Color string `json:"color,omitempty"`
Name string `json:"name,omitempty"`
Color string `json:"color,omitempty"`
}
+31 -31
View File
@@ -1,48 +1,48 @@
package z
import (
"os"
"fmt"
// "time"
"github.com/spf13/cobra"
// "github.com/gookit/color"
"fmt"
"os"
// "time"
"github.com/spf13/cobra"
// "github.com/gookit/color"
)
var projectColor string
var projectCmd = &cobra.Command{
Use: "project ([flags]) [project]",
Short: "Project settings",
Long: "Configure project settings.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
projectName := args[0]
Use: "project ([flags]) [project]",
Short: "Project settings",
Long: "Configure project settings.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
projectName := args[0]
project, err := database.GetProject(user, projectName)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
project, err := database.GetProject(user, projectName)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
project.Name = projectName
project.Name = projectName
if projectColor != "" {
project.Color = projectColor
}
if projectColor != "" {
project.Color = projectColor
}
err = database.UpdateProject(user, projectName, project)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
err = database.UpdateProject(user, projectName, project)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Printf("%s project updated\n", CharInfo)
return
},
fmt.Printf("%s project updated\n", CharInfo)
return
},
}
func init() {
rootCmd.AddCommand(projectCmd)
projectCmd.Flags().StringVarP(&projectColor, "color", "c", "", "Set the color of the project (hex code, e.g. #121212)")
rootCmd.AddCommand(projectCmd)
projectCmd.Flags().StringVarP(&projectColor, "color", "c", "", "Set the color of the project (hex code, e.g. #121212)")
}
+10 -10
View File
@@ -1,21 +1,21 @@
package z
import (
"github.com/spf13/cobra"
"github.com/spf13/cobra"
)
var resumeCmd = &cobra.Command{
Use: "resume",
Short: "Resume last task",
Long: "Track new activity with all parameters of the last task (based on begin time)",
Run: func(cmd *cobra.Command, args []string) {
resumeTask(1)
},
Use: "resume",
Short: "Resume last task",
Long: "Track new activity with all parameters of the last task (based on begin time)",
Run: func(cmd *cobra.Command, args []string) {
resumeTask(1)
},
}
func init() {
rootCmd.AddCommand(resumeCmd)
rootCmd.AddCommand(resumeCmd)
resumeCmd.Flags().StringVarP(&begin, "begin", "b", "", "Time the activity should begin at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
resumeCmd.Flags().StringVarP(&finish, "finish", "s", "", "Time the activity should finish at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).\nMust be after --begin time.")
resumeCmd.Flags().StringVarP(&begin, "begin", "b", "", "Time the activity should begin at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
resumeCmd.Flags().StringVarP(&finish, "finish", "s", "", "Time the activity should finish at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).\nMust be after --begin time.")
}
+80 -71
View File
@@ -1,104 +1,113 @@
package z
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/gookit/color"
"os"
"fmt"
"os"
"github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var database *Database
var begin string
var finish string
var switchString string
var project string
var task string
var notes string
var (
begin string
finish string
switchString string
project string
task string
notes string
)
var since string
var until string
var listRange string
var (
since string
until string
listRange string
)
var format string
var force bool
var (
format string
force bool
)
var noColors bool
var debug bool
var cfgFile string
var (
noColors bool
debug bool
cfgFile string
)
const(
CharTrack = " ▶"
CharFinish = " ■"
CharErase = " ◀"
CharError = " ▲"
CharInfo = " ●"
CharMore = " ◆"
const (
CharTrack = " ▶"
CharFinish = " ■"
CharErase = " ◀"
CharError = " ▲"
CharInfo = " ●"
CharMore = " ◆"
)
var rootCmd = &cobra.Command{
Use: "zeit",
Short: "Command line Zeiterfassung",
Long: `A command line time tracker.`,
Use: "zeit",
Short: "Command line Zeiterfassung",
Long: `A command line time tracker.`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(-1)
}
if err := rootCmd.Execute(); err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(-1)
}
}
func init() {
cobra.OnInitialize(initConfig)
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $XDG_CONFIG_HOME/zeit.[yaml|toml")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $XDG_CONFIG_HOME/zeit.[yaml|toml")
rootCmd.PersistentFlags().BoolVar(&noColors, FlagNoColors, false, "Do not use colors in output")
viper.BindPFlag(FlagNoColors, rootCmd.PersistentFlags().Lookup(FlagNoColors))
rootCmd.PersistentFlags().BoolVar(&noColors, FlagNoColors, false, "Do not use colors in output")
viper.BindPFlag(FlagNoColors, rootCmd.PersistentFlags().Lookup(FlagNoColors))
rootCmd.PersistentFlags().BoolVarP(&debug, FlagDebug, "d", false, "Display debugging output in the console. (default: false)")
viper.BindPFlag(FlagDebug, rootCmd.PersistentFlags().Lookup(FlagDebug))
rootCmd.PersistentFlags().BoolVarP(&debug, FlagDebug, "d", false, "Display debugging output in the console. (default: false)")
viper.BindPFlag(FlagDebug, rootCmd.PersistentFlags().Lookup(FlagDebug))
}
func initConfig() {
if noColors == true {
color.Disable()
}
if noColors == true {
color.Disable()
}
viper.SetEnvPrefix("zeit")
viper.BindEnv("db")
viper.SetEnvPrefix("zeit")
viper.BindEnv("db")
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath("$XDG_CONFIG_HOME")
viper.AddConfigPath("$XDG_CONFIG_HOME/zeit")
viper.AddConfigPath(home + "/.config")
viper.AddConfigPath(home + "/.config/zeit")
viper.SetConfigName("zeit")
}
viper.AddConfigPath("$XDG_CONFIG_HOME")
viper.AddConfigPath("$XDG_CONFIG_HOME/zeit")
viper.AddConfigPath(home + "/.config")
viper.AddConfigPath(home + "/.config/zeit")
viper.SetConfigName("zeit")
}
if err := viper.ReadInConfig(); err != nil {
// Set default values for parameters
viper.Set("debug", false)
}
if err := viper.ReadInConfig(); err != nil {
// Set default values for parameters
viper.Set("debug", false)
}
if viper.GetBool("debug") {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
fmt.Fprintln(os.Stderr, "Using Database file:", viper.GetString("db"))
}
if viper.GetBool("debug") {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
fmt.Fprintln(os.Stderr, "Using Database file:", viper.GetString("db"))
}
var err error
database, err = InitDatabase()
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
var err error
database, err = InitDatabase()
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
}
+40 -39
View File
@@ -1,55 +1,56 @@
package z
import (
"os"
"fmt"
"time"
"strings"
"github.com/spf13/cobra"
// "github.com/shopspring/decimal"
// "github.com/gookit/color"
"fmt"
"os"
"strings"
"time"
"github.com/spf13/cobra"
// "github.com/shopspring/decimal"
// "github.com/gookit/color"
)
var statsCmd = &cobra.Command{
Use: "stats",
Short: "Display activity statistics",
Long: "Display statistics on all tracked activities.",
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
Use: "stats",
Short: "Display activity statistics",
Long: "Display statistics on all tracked activities.",
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
entries, err := database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
entries, err := database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
cal, _ := NewCalendar(entries)
cal, _ := NewCalendar(entries)
weekMinus0 := time.Now()
monthMinus0, weeknumberMinus0 := GetISOWeekInMonth(weekMinus0)
monthMinus00 := monthMinus0 - 1
weeknumberMinus00 := weeknumberMinus0 - 1
thisWeek := cal.GetOutputForWeekCalendar(weekMinus0, monthMinus00, weeknumberMinus00)
weekMinus0 := time.Now()
monthMinus0, weeknumberMinus0 := GetISOWeekInMonth(weekMinus0)
monthMinus00 := monthMinus0 - 1
weeknumberMinus00 := weeknumberMinus0 - 1
thisWeek := cal.GetOutputForWeekCalendar(weekMinus0, monthMinus00, weeknumberMinus00)
weekMinus1 := weekMinus0.AddDate(0, 0, -7)
monthMinus1, weeknumberMinus1 := GetISOWeekInMonth(weekMinus1)
monthMinus10 := monthMinus1 - 1
weeknumberMinus10 := weeknumberMinus1 - 1
previousWeek := cal.GetOutputForWeekCalendar(weekMinus1, monthMinus10, weeknumberMinus10)
weekMinus1 := weekMinus0.AddDate(0, 0, -7)
monthMinus1, weeknumberMinus1 := GetISOWeekInMonth(weekMinus1)
monthMinus10 := monthMinus1 - 1
weeknumberMinus10 := weeknumberMinus1 - 1
previousWeek := cal.GetOutputForWeekCalendar(weekMinus1, monthMinus10, weeknumberMinus10)
if monthMinus00 == monthMinus10 {
fmt.Printf("\n%s\n\n", strings.ToUpper(weekMinus0.Month().String()))
} else {
fmt.Printf("\n%s / %s\n\n", strings.ToUpper(weekMinus0.Month().String()), strings.ToUpper(weekMinus1.Month().String()))
}
fmt.Printf("%s\n\n\n", OutputAppendRight(thisWeek, previousWeek, 16))
fmt.Printf("%s\n", cal.GetOutputForDistribution())
if monthMinus00 == monthMinus10 {
fmt.Printf("\n%s\n\n", strings.ToUpper(weekMinus0.Month().String()))
} else {
fmt.Printf("\n%s / %s\n\n", strings.ToUpper(weekMinus0.Month().String()), strings.ToUpper(weekMinus1.Month().String()))
}
fmt.Printf("%s\n\n\n", OutputAppendRight(thisWeek, previousWeek, 16))
fmt.Printf("%s\n", cal.GetOutputForDistribution())
return
},
return
},
}
func init() {
rootCmd.AddCommand(statsCmd)
statsCmd.Flags().BoolVar(&fractional, "decimal", false, "Show fractional hours in decimal format instead of minutes")
rootCmd.AddCommand(statsCmd)
statsCmd.Flags().BoolVar(&fractional, "decimal", false, "Show fractional hours in decimal format instead of minutes")
}
+13 -14
View File
@@ -1,26 +1,25 @@
package z
import (
"github.com/spf13/cobra"
"github.com/spf13/cobra"
)
var switchBackCmd = &cobra.Command{
Use: "switchback",
Short: "switchback to the task before the last one",
Long: "End running activity and resume the task which was before, which can either be kept running until 'finish' is being called or parameterized to be a finished activity.",
Run: func(cmd *cobra.Command, args []string) {
Use: "switchback",
Short: "switchback to the task before the last one",
Long: "End running activity and resume the task which was before, which can either be kept running until 'finish' is being called or parameterized to be a finished activity.",
Run: func(cmd *cobra.Command, args []string) {
finish = switchString
finishTask(FinishOnlyTime)
finish = switchString
finishTask(FinishOnlyTime)
finish = ""
begin = switchString
resumeTask(2)
},
finish = ""
begin = switchString
resumeTask(2)
},
}
func init() {
rootCmd.AddCommand(switchBackCmd)
rootCmd.AddCommand(switchBackCmd)
switchBackCmd.Flags().StringVarP(&switchString, "begin", "b", "", "Time the new activity should begin at and the old one ends\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
switchBackCmd.Flags().StringVarP(&switchString, "begin", "b", "", "Time the new activity should begin at and the old one ends\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
}
+23 -25
View File
@@ -1,38 +1,36 @@
package z
import (
"github.com/spf13/cobra"
"github.com/spf13/cobra"
)
var switchCmd = &cobra.Command{
Use: "switch",
Short: "switch to another task",
Long: "End running activity and track new activity, which can either be kept running until 'finish' is being called or parameterized to be a finished activity.",
Run: func(cmd *cobra.Command, args []string) {
Use: "switch",
Short: "switch to another task",
Long: "End running activity and track new activity, which can either be kept running until 'finish' is being called or parameterized to be a finished activity.",
Run: func(cmd *cobra.Command, args []string) {
finish = switchString
finishTask(FinishOnlyTime)
finish = switchString
finishTask(FinishOnlyTime)
finish = ""
begin = switchString
trackTask()
},
finish = ""
begin = switchString
trackTask()
},
}
func init() {
rootCmd.AddCommand(switchCmd)
rootCmd.AddCommand(switchCmd)
switchCmd.Flags().StringVarP(&switchString, "begin", "b", "", "Time the new activity should begin at and the old one ends\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
switchCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be assigned")
switchCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be assigned")
switchCmd.Flags().StringVarP(&notes, "notes", "n", "", "Activity notes")
switchCmd.Flags().StringVarP(&switchString, "begin", "b", "", "Time the new activity should begin at and the old one ends\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
switchCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be assigned")
switchCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be assigned")
switchCmd.Flags().StringVarP(&notes, "notes", "n", "", "Activity notes")
flagName := "task"
switchCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
flagName := "task"
switchCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
}
+192 -196
View File
@@ -1,272 +1,268 @@
package z
import (
"fmt"
"os"
"time"
"fmt"
"os"
"time"
"github.com/spf13/viper"
"github.com/spf13/viper"
)
type Task struct {
Name string `json:"name,omitempty"`
GitRepository string `json:"gitRepository,omitempty"`
Name string `json:"name,omitempty"`
GitRepository string `json:"gitRepository,omitempty"`
}
func listEntries() []Entry {
user := GetCurrentUser()
user := GetCurrentUser()
entries, err := database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
entries, err := database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
sinceTime, untilTime := ParseSinceUntil(since, until, listRange)
sinceTime, untilTime := ParseSinceUntil(since, until, listRange)
var filteredEntries []Entry
filteredEntries, err = GetFilteredEntries(entries, project, task, sinceTime, untilTime)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
var filteredEntries []Entry
filteredEntries, err = GetFilteredEntries(entries, project, task, sinceTime, untilTime)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if listOnlyProjectsAndTasks || listOnlyTasks {
printProjects(filteredEntries)
return nil
}
return filteredEntries
if listOnlyProjectsAndTasks || listOnlyTasks {
printProjects(filteredEntries)
return nil
}
return filteredEntries
}
func printProjects(entries []Entry) {
projectsAndTasks, _ := listProjectsAndTasks(entries)
for project := range projectsAndTasks {
if listOnlyProjectsAndTasks && !listOnlyTasks {
fmt.Printf("%s %s\n", CharMore, project)
}
projectsAndTasks, _ := listProjectsAndTasks(entries)
for project := range projectsAndTasks {
if listOnlyProjectsAndTasks && !listOnlyTasks {
fmt.Printf("%s %s\n", CharMore, project)
}
for task := range projectsAndTasks[project] {
if listOnlyProjectsAndTasks && !listOnlyTasks {
fmt.Printf("%*s└── ", 1, " ")
}
for task := range projectsAndTasks[project] {
if listOnlyProjectsAndTasks && !listOnlyTasks {
fmt.Printf("%*s└── ", 1, " ")
}
if appendProjectIDToTask {
fmt.Printf("%s [%s]\n", task, project)
} else {
fmt.Printf("%s\n", task)
}
}
}
if appendProjectIDToTask {
fmt.Printf("%s [%s]\n", task, project)
} else {
fmt.Printf("%s\n", task)
}
}
}
}
func listProjectsAndTasks(entries []Entry) (map[string]map[string]bool, []string) {
var projectsAndTasks = make(map[string]map[string]bool)
var allTasks []string
projectsAndTasks := make(map[string]map[string]bool)
var allTasks []string
for _, filteredEntry := range entries {
taskMap, ok := projectsAndTasks[filteredEntry.Project]
for _, filteredEntry := range entries {
taskMap, ok := projectsAndTasks[filteredEntry.Project]
if !ok {
taskMap = make(map[string]bool)
projectsAndTasks[filteredEntry.Project] = taskMap
}
if !ok {
taskMap = make(map[string]bool)
projectsAndTasks[filteredEntry.Project] = taskMap
}
taskMap[filteredEntry.Task] = true
projectsAndTasks[filteredEntry.Project] = taskMap
allTasks = append(allTasks, filteredEntry.Task)
}
taskMap[filteredEntry.Task] = true
projectsAndTasks[filteredEntry.Project] = taskMap
allTasks = append(allTasks, filteredEntry.Task)
}
return projectsAndTasks, allTasks
return projectsAndTasks, allTasks
}
func trackTask() {
user := GetCurrentUser()
user := GetCurrentUser()
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if runningEntryId != "" {
fmt.Printf("%s a task is already running\n", CharTrack)
os.Exit(1)
}
if runningEntryId != "" {
fmt.Printf("%s a task is already running\n", CharTrack)
os.Exit(1)
}
if project == "" && viper.GetString("project.default") != "" {
project = viper.GetString("project.default")
}
if project == "" && viper.GetString("project.default") != "" {
project = viper.GetString("project.default")
}
if project == "" && viper.GetBool("project.mandatory") {
fmt.Println("project is mandatory but missing")
os.Exit(1)
}
if project == "" && viper.GetBool("project.mandatory") {
fmt.Println("project is mandatory but missing")
os.Exit(1)
}
if task == "" && viper.GetBool("task.mandatory") {
fmt.Println("task is mandatory but missing")
os.Exit(1)
}
if task == "" && viper.GetBool("task.mandatory") {
fmt.Println("task is mandatory but missing")
os.Exit(1)
}
newEntry, err := NewEntry("", begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
newEntry, err := NewEntry("", begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if notes != "" {
newEntry.Notes = notes
}
if notes != "" {
newEntry.Notes = notes
}
isRunning := newEntry.Finish.IsZero()
isRunning := newEntry.Finish.IsZero()
_, err = database.AddEntry(user, newEntry, isRunning)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
_, err = database.AddEntry(user, newEntry, isRunning)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Print(newEntry.GetOutputForTrack(isRunning, false))
fmt.Print(newEntry.GetOutputForTrack(isRunning, false))
}
func finishTask(mode int) {
user := GetCurrentUser()
user := GetCurrentUser()
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if runningEntryId == "" {
fmt.Printf("%s not running\n", CharFinish)
os.Exit(1)
}
if runningEntryId == "" {
fmt.Printf("%s not running\n", CharFinish)
os.Exit(1)
}
runningEntry, err := database.GetEntry(user, runningEntryId)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntry, err := database.GetEntry(user, runningEntryId)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
tmpEntry, err := NewEntry(runningEntry.ID, begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
tmpEntry, err := NewEntry(runningEntry.ID, begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if begin != "" {
runningEntry.Begin = tmpEntry.Begin
}
if begin != "" {
runningEntry.Begin = tmpEntry.Begin
}
if finish != "" {
runningEntry.Finish = tmpEntry.Finish
} else {
runningEntry.Finish = time.Now()
}
if finish != "" {
runningEntry.Finish = tmpEntry.Finish
} else {
runningEntry.Finish = time.Now()
}
if mode == FinishWithMetadata {
finishTaskMetadata(user, &runningEntry, &tmpEntry)
}
if mode == FinishWithMetadata {
finishTaskMetadata(user, &runningEntry, &tmpEntry)
}
if !runningEntry.IsFinishedAfterBegan() {
fmt.Printf("%s %+v\n", CharError, "beginning time of tracking cannot be after finish time")
os.Exit(1)
}
if !runningEntry.IsFinishedAfterBegan() {
fmt.Printf("%s %+v\n", CharError, "beginning time of tracking cannot be after finish time")
os.Exit(1)
}
_, err = database.FinishEntry(user, runningEntry)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
_, err = database.FinishEntry(user, runningEntry)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Print(runningEntry.GetOutputForFinish())
fmt.Print(runningEntry.GetOutputForFinish())
}
func finishTaskMetadata(user string, runningEntry *Entry, tmpEntry *Entry) {
if project != "" {
runningEntry.Project = tmpEntry.Project
}
if project != "" {
runningEntry.Project = tmpEntry.Project
}
if task != "" {
runningEntry.Task = tmpEntry.Task
}
if task != "" {
runningEntry.Task = tmpEntry.Task
}
if notes != "" {
runningEntry.Notes = fmt.Sprintf("%s\n%s", runningEntry.Notes, notes)
}
if notes != "" {
runningEntry.Notes = fmt.Sprintf("%s\n%s", runningEntry.Notes, notes)
}
if runningEntry.Task != "" {
task, err := database.GetTask(user, runningEntry.Task)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
taskGit(&task, runningEntry)
}
if runningEntry.Task != "" {
task, err := database.GetTask(user, runningEntry.Task)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
taskGit(&task, runningEntry)
}
}
func taskGit(task *Task, runningEntry *Entry) {
if task.GitRepository != "" && task.GitRepository != "-" {
stdout, stderr, err := GetGitLog(task.GitRepository, runningEntry.Begin, runningEntry.Finish)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if task.GitRepository != "" && task.GitRepository != "-" {
stdout, stderr, err := GetGitLog(task.GitRepository, runningEntry.Begin, runningEntry.Finish)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if stderr == "" {
runningEntry.Notes = fmt.Sprintf("%s\n%s", runningEntry.Notes, stdout)
} else {
fmt.Printf("%s notes were not imported: %+v\n", CharError, stderr)
}
}
if stderr == "" {
runningEntry.Notes = fmt.Sprintf("%s\n%s", runningEntry.Notes, stdout)
} else {
fmt.Printf("%s notes were not imported: %+v\n", CharError, stderr)
}
}
}
func resumeTask(index int) {
user := GetCurrentUser()
user := GetCurrentUser()
entries, err := database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
lastEntry := entries[len(entries)-index]
entries, err := database.ListEntries(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
lastEntry := entries[len(entries)-index]
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if runningEntryId != "" {
fmt.Printf("%s a task is already running\n", CharTrack)
os.Exit(1)
}
if runningEntryId != "" {
fmt.Printf("%s a task is already running\n", CharTrack)
os.Exit(1)
}
project = lastEntry.Project
task = lastEntry.Task
project = lastEntry.Project
task = lastEntry.Task
newEntry, err := NewEntry("", begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
newEntry, err := NewEntry("", begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if lastEntry.Notes != "" {
newEntry.Notes = lastEntry.Notes
}
if lastEntry.Notes != "" {
newEntry.Notes = lastEntry.Notes
}
isRunning := newEntry.Finish.IsZero()
isRunning := newEntry.Finish.IsZero()
_, err = database.AddEntry(user, newEntry, isRunning)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
_, err = database.AddEntry(user, newEntry, isRunning)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Print(newEntry.GetOutputForTrack(isRunning, false))
fmt.Print(newEntry.GetOutputForTrack(isRunning, false))
}
+31 -31
View File
@@ -1,48 +1,48 @@
package z
import (
"os"
"fmt"
// "time"
"github.com/spf13/cobra"
// "github.com/gookit/color"
"fmt"
"os"
// "time"
"github.com/spf13/cobra"
// "github.com/gookit/color"
)
var taskGitRepository string
var taskCmd = &cobra.Command{
Use: "task ([flags]) [task]",
Short: "Task settings",
Long: "Configure task settings.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
taskName := args[0]
Use: "task ([flags]) [task]",
Short: "Task settings",
Long: "Configure task settings.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
taskName := args[0]
task, err := database.GetTask(user, taskName)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
task, err := database.GetTask(user, taskName)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
task.Name = taskName
task.Name = taskName
if taskGitRepository != "-" {
task.GitRepository = taskGitRepository
}
if taskGitRepository != "-" {
task.GitRepository = taskGitRepository
}
err = database.UpdateTask(user, taskName, task)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
err = database.UpdateTask(user, taskName, task)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Printf("%s task updated\n", CharInfo)
return
},
fmt.Printf("%s task updated\n", CharInfo)
return
},
}
func init() {
rootCmd.AddCommand(taskCmd)
taskCmd.Flags().StringVarP(&taskGitRepository, "git", "g", "-", "Set the task's Git repository to enable commit message importing into activity notes.\nSet to an empty string '' to remove a previously set repository and disable git log imports.")
rootCmd.AddCommand(taskCmd)
taskCmd.Flags().StringVarP(&taskGitRepository, "git", "g", "-", "Set the task's Git repository to enable commit message importing into activity notes.\nSet to an empty string '' to remove a previously set repository and disable git log imports.")
}
+61 -60
View File
@@ -1,81 +1,82 @@
package z
import (
"os"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var trackCmd = &cobra.Command{
Use: "track",
Short: "Tracking time",
Long: "Track new activity, which can either be kept running until 'finish' is being called or parameterized to be a finished activity.",
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
Use: "track",
Short: "Tracking time",
Long: "Track new activity, which can either be kept running until 'finish' is being called or parameterized to be a finished activity.",
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if runningEntryId != "" {
fmt.Printf("%s a task is already running\n", CharTrack)
os.Exit(1)
}
if runningEntryId != "" {
fmt.Printf("%s a task is already running\n", CharTrack)
os.Exit(1)
}
if project == "" && viper.GetString("project.default") != "" {
project = viper.GetString("project.default")
}
if project == "" && viper.GetString("project.default") != "" {
project = viper.GetString("project.default")
}
if project == "" && viper.GetBool("project.mandatory") {
fmt.Println("project is mandatory but missing")
os.Exit(1)
}
if project == "" && viper.GetBool("project.mandatory") {
fmt.Println("project is mandatory but missing")
os.Exit(1)
}
if task == "" && viper.GetBool("task.mandatory") {
fmt.Println("task is mandatory but missing")
os.Exit(1)
}
if task == "" && viper.GetBool("task.mandatory") {
fmt.Println("task is mandatory but missing")
os.Exit(1)
}
newEntry, err := NewEntry("", begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
newEntry, err := NewEntry("", begin, finish, project, task, user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if notes != "" {
newEntry.Notes = notes
}
if notes != "" {
newEntry.Notes = notes
}
isRunning := newEntry.Finish.IsZero()
isRunning := newEntry.Finish.IsZero()
_, err = database.AddEntry(user, newEntry, isRunning)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
_, err = database.AddEntry(user, newEntry, isRunning)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Printf(newEntry.GetOutputForTrack(isRunning, false))
return
},
fmt.Printf(newEntry.GetOutputForTrack(isRunning, false))
return
},
}
func init() {
rootCmd.AddCommand(trackCmd)
trackCmd.Flags().StringVarP(&begin, "begin", "b", "", "Time the activity should begin at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
trackCmd.Flags().StringVarP(&finish, "finish", "s", "", "Time the activity should finish at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).\nMust be after --begin time.")
trackCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be assigned")
trackCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be assigned")
trackCmd.Flags().StringVarP(&notes, "notes", "n", "", "Activity notes")
trackCmd.Flags().BoolVarP(&force, "force", "f", false, "Force begin tracking of a new task \neven though another one is still running \n(ONLY IF YOU KNOW WHAT YOU'RE DOING!)")
rootCmd.AddCommand(trackCmd)
trackCmd.Flags().StringVarP(&begin, "begin", "b", "", "Time the activity should begin at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).")
trackCmd.Flags().StringVarP(&finish, "finish", "s", "", "Time the activity should finish at\n\nEither in the formats 16:00 / 4:00PM \nor relative to the current time, \ne.g. -0:15 (now minus 15 minutes), +1.50 (now plus 1:30h).\nMust be after --begin time.")
trackCmd.Flags().StringVarP(&project, "project", "p", "", "Project to be assigned")
trackCmd.Flags().StringVarP(&task, "task", "t", "", "Task to be assigned")
trackCmd.Flags().StringVarP(&notes, "notes", "n", "", "Activity notes")
trackCmd.Flags().BoolVarP(&force, "force", "f", false, "Force begin tracking of a new task \neven though another one is still running \n(ONLY IF YOU KNOW WHAT YOU'RE DOING!)")
flagName := "task"
trackCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
flagName := "task"
trackCmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
user := GetCurrentUser()
entries, _ := database.ListEntries(user)
_, tasks := listProjectsAndTasks(entries)
return tasks, cobra.ShellCompDirectiveDefault
})
}
+27 -26
View File
@@ -1,40 +1,41 @@
package z
import (
"os"
"fmt"
"github.com/spf13/cobra"
"fmt"
"os"
"github.com/spf13/cobra"
)
var trackingCmd = &cobra.Command{
Use: "tracking",
Short: "Currently tracking activity",
Long: "Show currently tracking activity.",
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
Use: "tracking",
Short: "Currently tracking activity",
Long: "Show currently tracking activity.",
Run: func(cmd *cobra.Command, args []string) {
user := GetCurrentUser()
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntryId, err := database.GetRunningEntryId(user)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
if runningEntryId == "" {
fmt.Printf("%s not running\n", CharFinish)
os.Exit(1)
}
if runningEntryId == "" {
fmt.Printf("%s not running\n", CharFinish)
os.Exit(1)
}
runningEntry, err := database.GetEntry(user, runningEntryId)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
runningEntry, err := database.GetEntry(user, runningEntryId)
if err != nil {
fmt.Printf("%s %+v\n", CharError, err)
os.Exit(1)
}
fmt.Printf(runningEntry.GetOutputForTrack(true, true))
return
},
fmt.Printf(runningEntry.GetOutputForTrack(true, true))
return
},
}
func init() {
rootCmd.AddCommand(trackingCmd)
rootCmd.AddCommand(trackingCmd)
}
+95 -89
View File
@@ -1,117 +1,123 @@
package z
import (
"fmt"
"math"
"github.com/gookit/color"
"github.com/shopspring/decimal"
"fmt"
"math"
"github.com/gookit/color"
"github.com/shopspring/decimal"
)
func GetOutputBoxForNumber(number int, clr (func(...interface {}) string) ) (string) {
switch(number) {
case 0: return clr(" ")
case 1: return clr(" ")
case 2: return clr("▄▄")
case 3: return clr("▄")
case 4: return clr("██")
}
func GetOutputBoxForNumber(number int, clr func(...interface{}) string) string {
switch number {
case 0:
return clr(" ")
case 1:
return clr(" ▄")
case 2:
return clr("▄▄")
case 3:
return clr("▄█")
case 4:
return clr("██")
}
return clr(" ")
return clr(" ")
}
func GetOutputBarForHours(hours decimal.Decimal, stats []Statistic) ([]string) {
var bar = []string{
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
}
func GetOutputBarForHours(hours decimal.Decimal, stats []Statistic) []string {
bar := []string{
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
color.FgGray.Render("····"),
}
hoursInt := int((hours.Round(0)).IntPart())
rest := ((hours.Round(0)).Mod(decimal.NewFromInt(4))).Round(0)
restInt := int(rest.IntPart())
hoursInt := int((hours.Round(0)).IntPart())
rest := ((hours.Round(0)).Mod(decimal.NewFromInt(4))).Round(0)
restInt := int(rest.IntPart())
divisible := hoursInt - restInt
fullparts := divisible / 4
divisible := hoursInt - restInt
fullparts := divisible / 4
colorsFull := make(map[int](func(...interface {}) string))
colorsFullIdx := 0
colorsFull := make(map[int](func(...interface{}) string))
colorsFullIdx := 0
colorFraction := color.FgWhite.Render
colorFractionPrevAmount := 0.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()
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
}
if statRestFloat > colorFractionPrevAmount {
colorFractionPrevAmount = statRestFloat
colorFraction = stat.Color
}
fullColoredParts := int(math.Round(statHoursInt) / 4)
fullColoredParts := int(math.Round(statHoursInt) / 4)
if fullColoredParts == 0 && statHoursInt > colorFractionPrevAmount {
colorFractionPrevAmount = statHoursInt
colorFraction = stat.Color
}
if fullColoredParts == 0 && statHoursInt > colorFractionPrevAmount {
colorFractionPrevAmount = statHoursInt
colorFraction = stat.Color
}
for i := 0; i < fullColoredParts; i++ {
colorsFull[colorsFullIdx] = stat.Color
colorsFullIdx++
}
}
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 {
bar[i] = " " + GetOutputBoxForNumber(4, colorFraction) + " "
}
}
iColor := 0
for i := (len(bar) - 1); i > (len(bar) - 1 - fullparts); i-- {
if iColor < colorsFullIdx {
bar[i] = " " + GetOutputBoxForNumber(4, colorsFull[iColor]) + " "
iColor++
} else {
bar[i] = " " + GetOutputBoxForNumber(4, colorFraction) + " "
}
}
if(restInt > 0) {
bar[(len(bar) - 1 - fullparts)] = " " + GetOutputBoxForNumber(restInt, colorFraction) + " "
}
if restInt > 0 {
bar[(len(bar) - 1 - fullparts)] = " " + GetOutputBoxForNumber(restInt, colorFraction) + " "
}
return bar
return bar
}
func OutputAppendRight(leftStr string, rightStr string, pad int) (string) {
var output string = ""
var rpos int = 0
func OutputAppendRight(leftStr string, rightStr string, pad int) string {
var output string = ""
var rpos int = 0
left := []rune(leftStr)
leftLen := len(left)
right := []rune(rightStr)
rightLen := len(right)
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])
}
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
return output
}
func GetColorFnFromHex(colorHex string) (func(...interface {}) string) {
if colorHex == "" {
colorHex = "#dddddd"
}
return color.NewRGBStyle(color.HEX(colorHex)).Sprint
func GetColorFnFromHex(colorHex string) func(...interface{}) string {
if colorHex == "" {
colorHex = "#dddddd"
}
return color.NewRGBStyle(color.HEX(colorHex)).Sprint
}
+63 -62
View File
@@ -1,88 +1,89 @@
package z
import (
"encoding/json"
// "fmt"
"os"
"github.com/shopspring/decimal"
"time"
"encoding/json"
"os"
"time"
// "fmt"
"github.com/shopspring/decimal"
)
type TymeEntry struct {
Billing string `json:"billing"` // "UNBILLED",
Category string `json:"category"` // "Client",
Distance string `json:"distance"` // "0",
Duration string `json:"duration"` // "15",
Start string `json:"start"` // "2020-09-01T08:45:00+01:00",
End string `json:"end"` // "2020-09-01T08:57:00+01:00",
Note string `json:"note"` // "",
Project string `json:"project"` // "Project",
Quantity string `json:"quantity"` // "0",
Rate string `json:"rate"` // "140",
RoundingMethod string `json:"rounding_method"` // "NEAREST",
RoundingMinutes int `json:"rounding_minutes"` // 15,
Subtask string `json:"subtask"` // "",
Sum string `json:"sum"` // "35",
Task string `json:"task"` // "Development",
Type string `json:"type"` // "timed",
User string `json:"user"` // ""
Billing string `json:"billing"` // "UNBILLED",
Category string `json:"category"` // "Client",
Distance string `json:"distance"` // "0",
Duration string `json:"duration"` // "15",
Start string `json:"start"` // "2020-09-01T08:45:00+01:00",
End string `json:"end"` // "2020-09-01T08:57:00+01:00",
Note string `json:"note"` // "",
Project string `json:"project"` // "Project",
Quantity string `json:"quantity"` // "0",
Rate string `json:"rate"` // "140",
RoundingMethod string `json:"rounding_method"` // "NEAREST",
RoundingMinutes int `json:"rounding_minutes"` // 15,
Subtask string `json:"subtask"` // "",
Sum string `json:"sum"` // "35",
Task string `json:"task"` // "Development",
Type string `json:"type"` // "timed",
User string `json:"user"` // ""
}
type Tyme struct {
Data []TymeEntry `json:"data"`
Data []TymeEntry `json:"data"`
}
func (tyme *Tyme) Load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
decoder := json.NewDecoder(file)
decoder := json.NewDecoder(file)
if err = decoder.Decode(&tyme); err != nil {
return err
}
if err = decoder.Decode(&tyme); err != nil {
return err
}
return nil
return nil
}
func (tyme *Tyme) FromEntries(entries []Entry) error {
for _, entry := range entries {
duration := decimal.NewFromFloat(entry.Finish.Sub(entry.Begin).Minutes())
for _, entry := range entries {
duration := decimal.NewFromFloat(entry.Finish.Sub(entry.Begin).Minutes())
tymeEntry := TymeEntry{
Billing: "UNBILLED",
Category: "",
Distance: "0",
Duration: duration.StringFixed(0),
Start: entry.Begin.Format(time.RFC3339),
End: entry.Finish.Format(time.RFC3339),
Note: entry.Notes,
Project: entry.Project,
Quantity: "0",
Rate: "0",
RoundingMethod: "NEAREST",
RoundingMinutes: 15,
Subtask: "",
Sum: "0",
Task: entry.Task,
Type: "timed",
User: "",
}
tymeEntry := TymeEntry{
Billing: "UNBILLED",
Category: "",
Distance: "0",
Duration: duration.StringFixed(0),
Start: entry.Begin.Format(time.RFC3339),
End: entry.Finish.Format(time.RFC3339),
Note: entry.Notes,
Project: entry.Project,
Quantity: "0",
Rate: "0",
RoundingMethod: "NEAREST",
RoundingMinutes: 15,
Subtask: "",
Sum: "0",
Task: entry.Task,
Type: "timed",
User: "",
}
tyme.Data = append(tyme.Data, tymeEntry)
}
tyme.Data = append(tyme.Data, tymeEntry)
}
return nil
return nil
}
func (tyme *Tyme) Stringify() string {
stringified, err := json.Marshal(tyme)
if err != nil {
return ""
}
stringified, err := json.Marshal(tyme)
if err != nil {
return ""
}
return string(stringified)
return string(stringified)
}
+17 -17
View File
@@ -1,28 +1,28 @@
package z
import (
"fmt"
"time"
"fmt"
"time"
"github.com/shopspring/decimal"
"github.com/shopspring/decimal"
)
var fractional bool
func fmtDuration(dur time.Duration) (string) {
return fmtHours(decimal.NewFromFloat(dur.Hours()))
func fmtDuration(dur time.Duration) string {
return fmtHours(decimal.NewFromFloat(dur.Hours()))
}
func fmtHours(hours decimal.Decimal) (string) {
if fractional {
return hours.StringFixed(2)
} else {
return fmt.Sprintf(
"%s:%02s",
hours.Floor(), // hours
hours.Sub(hours.Floor()).
Mul(decimal.NewFromFloat(.6)).
Mul(decimal.NewFromInt(100)).
Floor())
}
func fmtHours(hours decimal.Decimal) string {
if fractional {
return hours.StringFixed(2)
} else {
return fmt.Sprintf(
"%s:%02s",
hours.Floor(), // hours
hours.Sub(hours.Floor()).
Mul(decimal.NewFromFloat(.6)).
Mul(decimal.NewFromInt(100)).
Floor())
}
}
+9 -9
View File
@@ -1,22 +1,22 @@
package z
import (
"fmt"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/cobra"
)
var VERSION string
func init() {
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Display what Zeit it is",
Long: `The version of Zeit.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("zeit", VERSION)
},
Use: "version",
Short: "Display what Zeit it is",
Long: `The version of Zeit.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("zeit", VERSION)
},
}
+2 -2
View File
@@ -1,9 +1,9 @@
package main
import (
"github.com/mrusme/zeit/z"
"github.com/mrusme/zeit/z"
)
func main() {
z.Execute()
z.Execute()
}