Skip to content

Commit

Permalink
feat: log based on user provided date range
Browse files Browse the repository at this point in the history
  • Loading branch information
dhth committed Jun 11, 2024
1 parent 2679a87 commit 4b01b45
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ jobs:

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
go-version: '1.22.0'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
- name: Install Cosign
uses: sigstore/cosign-installer@v3
with:
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ Besides a TUI, `hours` also offers reports and logs based on the time tracking
you do. These can be viewed using the subcommands `report` and `log`
respectively.

### Reports

Reports show time spent on tasks in the last `n` days. These can also be
aggregated (using `-a`) to consolidate all task entries and show the cumulative
time spent on each task per day.

This subcommand accepts a `-p` flag, which can be anything in the range [1-7]
(both inclusive) to see reports for the last "n" days (including today).

```
hours report -h
Expand All @@ -56,6 +65,31 @@ Flags:
-p, --plain whether to output report without any formatting
```

```bash
hours report
# or
hours report -n=7
```

### Logs

As the name suggests, logs are just that: list of task entries you've saved
using `hours`. This subcommand accepts an argument, which can be one of the following:

- `all`: all recent log entries (in reverse chronological order)
- `today`: for log entries from today
- `yest`: for log entries from yesterday
- `<date>`: for log entries from that day
- `<range>`: for log entries from in that range

```bash
hours log today
# or
hours log 2024/06/08
# or
hours log 2024/06/08...2024/06/12
```

📋 TUI Reference Manual
---

Expand Down
22 changes: 20 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,27 @@ var reportCmd = &cobra.Command{

var logCmd = &cobra.Command{
Use: "log",
Short: "Output task log entries (in reverse chronological order)",
Short: "Output task log entries",
Long: `Output task log entries
Accepts an argument, which can be one of the following:
all: all recent log entries (in reverse chronological order)
today: for log entries from today
yest: for log entries from yesterday
date: for log entries from that day (eg. "2024/06/08")
range: for log entries from that range (eg. "2024/06/08...2024/06/12")
`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ui.RenderTaskLog(db, os.Stdout, reportOrLogPlain)
if len(args) == 0 {
ui.RenderTaskLog(db, os.Stdout, reportOrLogPlain, "all")
} else {
if args[0] == "" {
die("Time period shouldn't be empty\n")
}
ui.RenderTaskLog(db, os.Stdout, reportOrLogPlain, args[0])
}
},
}

Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/dustin/go-humanize v1.0.1
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
modernc.org/sqlite v1.30.0
)

Expand All @@ -19,6 +20,7 @@ require (
github.com/charmbracelet/x/input v0.1.0 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
Expand All @@ -32,6 +34,7 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
Expand All @@ -40,6 +43,7 @@ require (
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.50.9 // indirect
modernc.org/mathutil v1.6.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKU
github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4=
github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
Expand Down Expand Up @@ -68,6 +70,8 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
Expand All @@ -84,7 +88,9 @@ golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
Expand Down
54 changes: 54 additions & 0 deletions internal/ui/date_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ui

import (
"errors"
"fmt"
"strings"
"time"
)

const (
timePeriodHoursUpperBound = 168
)

var (
timePeriodNotValidErr = errors.New("time period is not valid")
timePeriodTooLargeErr = fmt.Errorf("time period is too large; maximum allowed time period is %d hours", timePeriodHoursUpperBound)
)

type timePeriod struct {
start time.Time
end time.Time
}

func parseDateDuration(period string) (timePeriod, error) {
var tp timePeriod

elements := strings.Split(period, "...")
if len(elements) != 2 {
return tp, timePeriodNotValidErr
}

start, err := time.ParseInLocation(string(dateFormat), elements[0], time.Local)
if err != nil {
return tp, timePeriodNotValidErr
}

end, err := time.ParseInLocation(string(dateFormat), elements[1], time.Local)
if err != nil {
return tp, timePeriodNotValidErr
}

if end.Sub(start) <= 0 {
return tp, timePeriodNotValidErr
}

if end.Sub(start).Hours() >= timePeriodHoursUpperBound {
return tp, timePeriodTooLargeErr
}

tp.start = start
tp.end = end

return tp, nil
}
86 changes: 86 additions & 0 deletions internal/ui/date_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ui

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseDateDuration(t *testing.T) {
testCases := []struct {
name string
input string
expectedStartStr string
expectedEndStr string
err error
}{
// success
{
name: "a range of 1 day",
input: "2024/06/10...2024/06/11",
expectedStartStr: "2024/06/10 00:00",
expectedEndStr: "2024/06/11 00:00",
},
{
name: "a range of 3 days",
input: "2024/06/29...2024/07/01",
expectedStartStr: "2024/06/29 00:00",
expectedEndStr: "2024/07/01 00:00",
},
// failures
{
name: "empty string",
input: "",
err: timePeriodNotValidErr,
},
{
name: "only one date",
input: "2024/06/10",
err: timePeriodNotValidErr,
},
{
name: "badly formatted start date",
input: "2024/0610...2024/06/10",
err: timePeriodNotValidErr,
},
{
name: "badly formatted end date",
input: "2024/06/10...2024/0610",
err: timePeriodNotValidErr,
},
{
name: "a range of 0 days",
input: "2024/06/10...2024/06/10",
err: timePeriodNotValidErr,
},
{
name: "end date before start date",
input: "2024/06/10...2024/06/08",
err: timePeriodNotValidErr,
},
{
name: "a range of 8 days",
input: "2024/06/29...2024/07/06",
err: timePeriodTooLargeErr,
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
got, err := parseDateDuration(tt.input)

startStr := got.start.Format(timeFormat)
endStr := got.end.Format(timeFormat)

if tt.err == nil {
assert.Equal(t, tt.expectedStartStr, startStr)
assert.Equal(t, tt.expectedEndStr, endStr)
assert.Nil(t, err)
} else {
assert.Equal(t, tt.err, err)
}

})
}

}
Loading

0 comments on commit 4b01b45

Please # to comment.