From 6cf6c1aacb1dbf9376e41c5a1c7eb61c67cec912 Mon Sep 17 00:00:00 2001 From: Dominik Braun Date: Sun, 16 May 2021 00:34:36 +0200 Subject: [PATCH] Implement `timetrace list records` command (#16) --- README.md | 22 +++++++++++++++++++ cli/list.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ core/record.go | 26 +++++++++++++++++++++- 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d257d64..e90229e 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,28 @@ timetrace list projects +---+-------------+ ``` +### List all records from a date + +**Syntax:** + +``` +timetrace list records +``` + +**Arguments:** + +|Argument|Description| +|-|-| +|`YYYY-MM-DD`|The date of the records to list.| + +**Example:** + +Display all records created on May 1st 2021: + +``` +timetrace list records 2021-05-01 +``` + ### Edit a project **Syntax:** diff --git a/cli/list.go b/cli/list.go index 752746d..2fa9531 100644 --- a/cli/list.go +++ b/cli/list.go @@ -2,6 +2,7 @@ package cli import ( "strconv" + "time" "github.com/dominikbraun/timetrace/core" "github.com/dominikbraun/timetrace/out" @@ -9,6 +10,10 @@ import ( "github.com/spf13/cobra" ) +const ( + defaultTimeLayout = "15:04" +) + func listCommand(t *core.Timetrace) *cobra.Command { list := &cobra.Command{ Use: "list", @@ -19,6 +24,7 @@ func listCommand(t *core.Timetrace) *cobra.Command { } list.AddCommand(listProjectsCommand(t)) + list.AddCommand(listRecordsCommand(t)) return list } @@ -48,3 +54,56 @@ func listProjectsCommand(t *core.Timetrace) *cobra.Command { return listProjects } + +func listRecordsCommand(t *core.Timetrace) *cobra.Command { + listRecords := &cobra.Command{ + Use: "records ", + Short: "List all records from a date", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + date, err := time.Parse("2006-01-02", args[0]) + if err != nil { + out.Err("failed to parse date: %s", err.Error()) + return + } + + records, err := t.ListRecords(date) + if err != nil { + out.Err("failed to list records: %s", err.Error()) + return + } + + dateLayout := defaultTimeLayout + + if t.Config().Use12Hours { + dateLayout = "03:04PM" + } + + rows := make([][]string, len(records)) + + for i, record := range records { + end := defaultString + + if record.End != nil { + end = record.End.Format(dateLayout) + } + + billable := defaultBool + + if record.IsBillable { + billable = "yes" + } + + rows[i] = make([]string, 4) + rows[i][0] = strconv.Itoa(i + 1) + rows[i][1] = record.Start.Format(dateLayout) + rows[i][2] = end + rows[i][3] = billable + } + + out.Table([]string{"#", "Start", "End", "Billable"}, rows) + }, + } + + return listRecords +} diff --git a/core/record.go b/core/record.go index 30ea291..2f6cbf5 100644 --- a/core/record.go +++ b/core/record.go @@ -31,6 +31,30 @@ func (t *Timetrace) LoadRecord(start time.Time) (*Record, error) { return t.loadRecord(path) } +// ListRecords loads and returns all records from the given date. If no records +// are found, an empty slice and no error will be returned. +func (t *Timetrace) ListRecords(date time.Time) ([]*Record, error) { + dir := t.fs.RecordDirFromDate(date) + paths, err := t.fs.RecordFilepaths(dir, func(_, _ string) bool { + return true + }) + if err != nil { + return nil, err + } + + records := make([]*Record, 0) + + for _, path := range paths { + record, err := t.loadRecord(path) + if err != nil { + return nil, err + } + records = append(records, record) + } + + return records, nil +} + // SaveRecord persists the given record. Returns ErrRecordAlreadyExists if the // record already exists and saving isn't forced. func (t *Timetrace) SaveRecord(record Record, force bool) error { @@ -74,7 +98,7 @@ func (t *Timetrace) DeleteRecord(record Record) error { func (t *Timetrace) loadAllRecords(date time.Time) ([]*Record, error) { dir := t.fs.RecordDirFromDate(date) - recordFilepaths, err := t.fs.RecordFilepaths(dir, func(a, b string) bool { + recordFilepaths, err := t.fs.RecordFilepaths(dir, func(_, _ string) bool { return true }) if err != nil {