diff --git a/cli/create.go b/cli/create.go index 2b823d2..1f418a7 100644 --- a/cli/create.go +++ b/cli/create.go @@ -106,7 +106,6 @@ func createRecordCommand(t *core.Timetrace) *cobra.Command { return } if collides { - out.Err("Record collides with other record!") return } diff --git a/core/timetrace.go b/core/timetrace.go index e41e4fb..7e0235d 100644 --- a/core/timetrace.go +++ b/core/timetrace.go @@ -5,9 +5,11 @@ import ( "io" "os" "path/filepath" + "strconv" "time" "github.com/dominikbraun/timetrace/config" + "github.com/dominikbraun/timetrace/out" ) const ( @@ -273,6 +275,37 @@ func (t *Timetrace) latestNonEmptyDir(dirs []string) (string, error) { return "", ErrAllDirectoriesEmpty } +func printCollisions(t *Timetrace, records []*Record) { + out.Err("collides with these records :") + + rows := make([][]string, len(records)) + + for i, record := range records { + end := "still running" + if record.End != nil { + end = t.Formatter().TimeString(*record.End) + } + + billable := "no" + + if record.IsBillable { + billable = "yes" + } + + rows[i] = make([]string, 6) + rows[i][0] = strconv.Itoa(i + 1) + rows[i][1] = t.Formatter().RecordKey(record) + rows[i][2] = record.Project.Key + rows[i][3] = t.Formatter().TimeString(record.Start) + rows[i][4] = end + rows[i][5] = billable + } + + out.Table([]string{"#", "Key", "Project", "Start", "End", "Billable"}, rows, []string{}) + + out.Warn(" start and end of the record should not overlap with others") +} + // RecordCollides checks if the time of a record collides // with other records of the same day and returns a bool func (t *Timetrace) RecordCollides(toCheck Record) (bool, error) { @@ -291,21 +324,31 @@ func (t *Timetrace) RecordCollides(toCheck Record) (bool, error) { } } - return collides(toCheck, allRecords), nil + collide, collidingRecords := collides(toCheck, allRecords) + if collide { + printCollisions(t, collidingRecords) + } + + return collide, nil } -func collides(toCheck Record, allRecords []*Record) bool { +func collides(toCheck Record, allRecords []*Record) (bool, []*Record) { + collide := false + collidingRecords := make([]*Record, 0) for _, rec := range allRecords { + if rec.End != nil && rec.Start.Before(*toCheck.End) && rec.End.After(toCheck.Start) { - return true + collidingRecords = append(collidingRecords, rec) + collide = true } if rec.End == nil && toCheck.End.After(rec.Start) { - return true + collidingRecords = append(collidingRecords, rec) + collide = true } } - return false + return collide, collidingRecords } // isBackFile checks if a given filename is a backup-file diff --git a/core/timetrace_test.go b/core/timetrace_test.go index 0ef651e..afb42b7 100644 --- a/core/timetrace_test.go +++ b/core/timetrace_test.go @@ -16,6 +16,31 @@ func newTestRecTracked(s int) Record { return Record{Start: start} } +func checkConsistent(t *testing.T, expect, result []*Record) { + sameLen := len(result) == len(expect) + sameContent := true + + if sameLen { + for i := range result { + if expect[i] != result[i] { + sameContent = false + } + } + } + + if !(sameLen && sameContent) { + t.Errorf("should collide with :\n") + for _, r := range expect { + t.Errorf("%v\n", r) + } + t.Errorf("while collides return :\n") + for _, r := range result { + t.Errorf("%v\n", r) + } + } + +} + func TestCollides(t *testing.T) { savedRec := newTestRecord(-60, -1) allRecs := []*Record{&savedRec} @@ -25,77 +50,93 @@ func TestCollides(t *testing.T) { // rec1 starts and end after savedRec rec1 := newTestRecord(-1, 0) - if collides(rec1, allRecs) { + if collide, collidingRecs := collides(rec1, allRecs); collide && len(collidingRecs) == 0 { t.Error("records should not collide") } // rec2 starts in savedRec, ends after rec2 := newTestRecord(-30, 1) - if !collides(rec2, allRecs) { + if collide, collidingRecs := collides(rec2, allRecs); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecs, collidingRecs) } // rec3 start before savedRec, ends inside rec3 := newTestRecord(-75, -30) - if !collides(rec3, allRecs) { + if collide, collidingRecs := collides(rec3, allRecs); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecs, collidingRecs) } // rec4 starts and ends before savedRec rec4 := newTestRecord(-75, -70) - if collides(rec4, allRecs) { + if collide, collidingRecs := collides(rec4, allRecs); collide && len(collidingRecs) == 0 { t.Error("records should not collide") } // rec5 starts and ends inside savedRec rec5 := newTestRecord(-40, -20) - if !collides(rec5, allRecs) { + if collide, collidingRecs := collides(rec5, allRecs); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecs, collidingRecs) } // rec6 starts before and ends after savedRec rec6 := newTestRecord(-70, 10) - if !collides(rec6, allRecs) { + if collide, collidingRecs := collides(rec6, allRecs); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecs, collidingRecs) } // rec7 starts and ends at the same time as savedRec rec7 := newTestRecord(-60, -1) - if !collides(rec7, allRecs) { + if collide, collidingRecs := collides(rec7, allRecs); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecs, collidingRecs) } // rec7 starts at the same time as savedRecTracked rec8 := newTestRecord(-60, -1) - if !collides(rec8, allRecsTracked) { + if collide, collidingRecs := collides(rec8, allRecsTracked); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecsTracked, collidingRecs) } // rec9 ends at the time savedRecTracked ends rec9 := newTestRecord(-80, -60) - if !collides(rec9, allRecsTracked) { + if collide, collidingRecs := collides(rec9, allRecsTracked); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecsTracked, collidingRecs) } // rec10 ends after savedRecTracked starts rec10 := newTestRecord(-80, -50) - if !collides(rec10, allRecsTracked) { + if collide, collidingRecs := collides(rec10, allRecsTracked); !collide { t.Error("records should collide") + } else { + checkConsistent(t, allRecsTracked, collidingRecs) } // rec11 ends before savedRecTracked starts rec11 := newTestRecord(-80, -70) - if collides(rec11, allRecsTracked) { + if collide, collidingRecs := collides(rec11, allRecsTracked); collide && len(collidingRecs) == 0 { t.Error("records should not collide") } }