commit 43b7ada48fb26971cb5e0cb256d82157fb47a736
parent e715a580b779311dc42bf90a0d7802891f80c546
Author: Tomas Nemec <nemi@skaut.cz>
Date: Fri, 10 Feb 2023 20:34:47 +0100
feat: report
Diffstat:
10 files changed, 199 insertions(+), 114 deletions(-)
diff --git a/command.go b/command.go
@@ -37,7 +37,7 @@ func (c *Command) nextArg() (string, error) {
func (c Command) add() {
if len(c.args) != 3 {
- fmt.Fprintln(os.Stderr, "add <path> <start> <stop>")
+ fmt.Fprintln(os.Stderr, "add <group> <start> <stop>")
os.Exit(1)
}
@@ -61,7 +61,7 @@ func (c Command) add() {
// TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo)
group.Create()
- entry, err := tme.NewFinalEntry(start, stop)
+ entry, err := tme.NewCompletedEntry(start, stop)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
@@ -77,7 +77,7 @@ func (c Command) add() {
func (c Command) start() {
if len(c.args) != 2 {
- fmt.Fprintln(os.Stderr, "start <path> <start>")
+ fmt.Fprintln(os.Stderr, "start <group> <start>")
os.Exit(1)
}
@@ -106,7 +106,7 @@ func (c Command) start() {
func (c Command) stop() {
if len(c.args) != 2 {
- fmt.Fprintln(os.Stderr, "stop <path> <stop>")
+ fmt.Fprintln(os.Stderr, "stop <group> <stop>")
os.Exit(1)
}
@@ -153,7 +153,7 @@ func (c Command) stop() {
func (c Command) ls() {
if len(c.args) > 1 {
- fmt.Fprintln(os.Stderr, "ls [<path>]")
+ fmt.Fprintln(os.Stderr, "ls [<group>]")
os.Exit(1)
}
@@ -168,7 +168,7 @@ func (c Command) ls() {
func (c Command) lsr() {
if len(c.args) > 1 {
- fmt.Fprintln(os.Stderr, "ls [<path>]")
+ fmt.Fprintln(os.Stderr, "lsr [<group>]")
os.Exit(1)
}
@@ -177,7 +177,7 @@ func (c Command) lsr() {
groupPath, _ = c.nextArg()
}
- groups, err := groupTree(c.rootPath, groupPath)
+ groups, err := groupsRecursive(c.rootPath, groupPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
@@ -188,6 +188,51 @@ func (c Command) lsr() {
}
}
+func (c Command) report() {
+ if len(c.args) < 1 || len(c.args) > 2 {
+ fmt.Fprintln(os.Stderr, "report <since> [<group>]")
+ os.Exit(1)
+ }
+
+ sinceArg, _ := c.nextArg()
+ sinceTime, err := c.entryTime.ParseArg(sinceArg)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "[since] time (%s) could not be parsed\n", sinceArg)
+ os.Exit(1)
+ }
+
+ groupPath := ""
+ if len(c.args) == 1 {
+ groupPath, _ = c.nextArg()
+ }
+
+ groups, err := groupsRecursive(c.rootPath, groupPath)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ var duration time.Duration
+ for _, group := range groups {
+ entries, err := group.ListCompleted(c.entryTime)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ for _, entry := range entries {
+ if entry.Start.After(sinceTime) {
+ duration += entry.Duration()
+ formatEntry(group, entry)
+ }
+ }
+ }
+
+ if duration > 0 {
+ fmt.Printf("%s\n", duration.Round(time.Second))
+ }
+}
+
func formatEntries(group tme.Group, entryTime *tme.Time) {
entries, err := group.List(entryTime)
if err != nil {
@@ -196,24 +241,27 @@ func formatEntries(group tme.Group, entryTime *tme.Time) {
}
for _, entry := range entries {
+ formatEntry(group, entry)
+ }
+}
- groupPath := group.Path
- duration := entry.Duration().Round(time.Second)
-
- switch e := entry.(type) {
- case tme.FinalEntry:
- var start string
- start = e.Start.Format(tme.DateTimeLayout)
- stop := e.Stop.Format(tme.DateTimeLayout)
- fmt.Printf("%v\t%v\t%v\t%v\n", groupPath, start, stop, duration)
- case tme.RunningEntry:
- start := e.Start.Format(tme.DateTimeLayout)
- fmt.Printf("%v\t%v\t%s\t%v\n", groupPath, start, "running", duration)
- }
+func formatEntry(group tme.Group, entry tme.Entry) {
+ groupPath := group.Path
+ duration := entry.Duration().Round(time.Second)
+
+ switch e := entry.(type) {
+ case tme.CompletedEntry:
+ var start string
+ start = e.Start.Format(tme.DateTimeLayout)
+ stop := e.Stop.Format(tme.DateTimeLayout)
+ fmt.Printf("%s\t%s\t%v\t%s\n", groupPath, start, stop, duration)
+ case tme.RunningEntry:
+ start := e.Start.Format(tme.DateTimeLayout)
+ fmt.Printf("%s\t%s\t%s\t%s\n", groupPath, start, "running", duration)
}
}
-func groupTree(rootPath string, groupPath string) ([]tme.Group, error) {
+func groupsRecursive(rootPath string, groupPath string) ([]tme.Group, error) {
var groups []tme.Group
err := filepath.WalkDir(path.Join(rootPath, groupPath), func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
diff --git a/main.go b/main.go
@@ -31,7 +31,7 @@ func main() {
cmd := args[0]
args = args[1:] // shift
- entryTime := tme.NewTime()
+ entryTime := tme.NewTimeToday()
command := Command{rootPath, args, entryTime}
if cmd == "add" {
command.add()
@@ -43,5 +43,7 @@ func main() {
command.ls()
} else if cmd == "lsr" {
command.lsr()
+ } else if cmd == "report" {
+ command.report()
}
}
diff --git a/tme/completed_entry.go b/tme/completed_entry.go
@@ -0,0 +1,71 @@
+package tme
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path"
+ "strings"
+ "time"
+)
+
+type CompletedEntry struct {
+ Start time.Time
+ Stop time.Time
+}
+
+func NewCompletedEntry(start time.Time, stop time.Time) (CompletedEntry, error) {
+ if start.After(stop) {
+ return CompletedEntry{}, errors.New("duration must be positive")
+ }
+
+ return CompletedEntry{
+ Start: start,
+ Stop: stop,
+ }, nil
+}
+
+func NewCompletedEntryFromPath(entryPath string, entryTime *Time) (CompletedEntry, error) {
+ if _, err := os.Stat(entryPath); os.IsNotExist(err) {
+ return CompletedEntry{}, err
+ }
+
+ data, err := os.ReadFile(entryPath)
+ if err != nil {
+ return CompletedEntry{}, err
+ }
+
+ // TODO(tms) 11.11.22: format check
+
+ lines := strings.Split(string(data), "\n")
+ start, _ := entryTime.ParseEntry(lines[0])
+ stop, _ := entryTime.ParseEntry(lines[1])
+
+ return CompletedEntry{
+ Start: start,
+ Stop: stop,
+ }, nil
+}
+
+func (e CompletedEntry) Data() string {
+ return fmt.Sprintf("%s\n%s\n", e.Start.Format(DataTimeLayout), e.Stop.Format(DataTimeLayout))
+}
+
+func (e CompletedEntry) FileName() string {
+ return strings.Join([]string{e.Start.Format(FileNameLayout), e.Stop.Format(FileNameLayout)}, "_")
+}
+
+func (e CompletedEntry) FullPath(group Group) string {
+ return path.Join(group.FullPath(), e.FileName())
+}
+
+func (e CompletedEntry) Exists(group Group) bool {
+ if _, err := os.Stat(e.FullPath(group)); !os.IsNotExist(err) {
+ return true
+ }
+ return false
+}
+
+func (e CompletedEntry) Duration() time.Duration {
+ return e.Stop.Sub(e.Start)
+}
diff --git a/tme/entry.go b/tme/entry.go
@@ -16,12 +16,12 @@ type Entry interface {
func NewEntryFromPath(entryPath string, entryTime *Time) (Entry, error) {
if _, err := os.Stat(entryPath); os.IsNotExist(err) {
- return FinalEntry{}, err
+ return CompletedEntry{}, err
}
data, err := os.ReadFile(entryPath)
if err != nil {
- return FinalEntry{}, err
+ return CompletedEntry{}, err
}
// TODO(tms) 11.11.22: format check
@@ -37,7 +37,7 @@ func NewEntryFromPath(entryPath string, entryTime *Time) (Entry, error) {
} else {
start, _ := entryTime.ParseEntry(lines[0])
stop, _ := entryTime.ParseEntry(lines[1])
- return FinalEntry{
+ return CompletedEntry{
Start: start,
Stop: stop,
}, nil
diff --git a/tme/entry_test.go b/tme/entry_test.go
@@ -6,12 +6,12 @@ import (
)
func TestEntry(t *testing.T) {
- entryTime := NewTime()
+ entryTime := NewTimeToday()
t.Run("positive duration is ok", func(b *testing.T) {
start, _ := entryTime.ParseArg("5:00")
stop, _ := entryTime.ParseArg("6:00")
- _, err := NewFinalEntry(start, stop)
+ _, err := NewCompletedEntry(start, stop)
if err != nil {
b.Error(err)
}
@@ -20,7 +20,7 @@ func TestEntry(t *testing.T) {
t.Run("same duration is kind of ok?", func(b *testing.T) {
start, _ := entryTime.ParseArg("6:00")
stop, _ := entryTime.ParseArg("6:00")
- _, err := NewFinalEntry(start, stop)
+ _, err := NewCompletedEntry(start, stop)
if err != nil {
b.Error(err)
}
@@ -29,7 +29,7 @@ func TestEntry(t *testing.T) {
t.Run("negative duration should fail", func(b *testing.T) {
start, _ := entryTime.ParseArg("6:00")
stop, _ := entryTime.ParseArg("5:00")
- _, err := NewFinalEntry(start, stop)
+ _, err := NewCompletedEntry(start, stop)
if err == nil {
b.Error(err)
}
@@ -38,7 +38,7 @@ func TestEntry(t *testing.T) {
func TestStartStopEntry(t *testing.T) {
basePath := setUp()
- entryTime := NewTime()
+ entryTime := NewTimeToday()
t.Cleanup(func() { tearDown(basePath) })
start, _ := entryTime.ParseArg("6:00")
@@ -53,7 +53,7 @@ func TestStartStopEntry(t *testing.T) {
}
stop, _ := entryTime.ParseArg("7:00")
- finalEntry, err := startEntry.Stop(stop)
+ completedEntry, err := startEntry.Stop(stop)
if err != nil {
t.Error(err)
}
@@ -63,9 +63,9 @@ func TestStartStopEntry(t *testing.T) {
t.Fatalf(`want %q to be deleted`, startEntry.FullPath(group))
}
- group.Add(finalEntry)
- if _, err := os.Stat(finalEntry.FullPath(group)); os.IsNotExist(err) {
- t.Fatalf(`want %q to exist`, finalEntry.FullPath(group))
+ group.Add(completedEntry)
+ if _, err := os.Stat(completedEntry.FullPath(group)); os.IsNotExist(err) {
+ t.Fatalf(`want %q to exist`, completedEntry.FullPath(group))
}
}
diff --git a/tme/final_entry.go b/tme/final_entry.go
@@ -1,71 +0,0 @@
-package tme
-
-import (
- "errors"
- "fmt"
- "os"
- "path"
- "strings"
- "time"
-)
-
-type FinalEntry struct {
- Start time.Time
- Stop time.Time
-}
-
-func NewFinalEntry(start time.Time, stop time.Time) (FinalEntry, error) {
- if start.After(stop) {
- return FinalEntry{}, errors.New("duration must be positive")
- }
-
- return FinalEntry{
- Start: start,
- Stop: stop,
- }, nil
-}
-
-func NewFinalEntryFromPath(entryPath string, entryTime *Time) (FinalEntry, error) {
- if _, err := os.Stat(entryPath); os.IsNotExist(err) {
- return FinalEntry{}, err
- }
-
- data, err := os.ReadFile(entryPath)
- if err != nil {
- return FinalEntry{}, err
- }
-
- // TODO(tms) 11.11.22: format check
-
- lines := strings.Split(string(data), "\n")
- start, _ := entryTime.ParseEntry(lines[0])
- stop, _ := entryTime.ParseEntry(lines[1])
-
- return FinalEntry{
- Start: start,
- Stop: stop,
- }, nil
-}
-
-func (e FinalEntry) Data() string {
- return fmt.Sprintf("%s\n%s\n", e.Start.Format(DataTimeLayout), e.Stop.Format(DataTimeLayout))
-}
-
-func (e FinalEntry) FileName() string {
- return strings.Join([]string{e.Start.Format(FileNameLayout), e.Stop.Format(FileNameLayout)}, "_")
-}
-
-func (e FinalEntry) FullPath(group Group) string {
- return path.Join(group.FullPath(), e.FileName())
-}
-
-func (e FinalEntry) Exists(group Group) bool {
- if _, err := os.Stat(e.FullPath(group)); !os.IsNotExist(err) {
- return true
- }
- return false
-}
-
-func (e FinalEntry) Duration() time.Duration {
- return e.Stop.Sub(e.Start)
-}
diff --git a/tme/group.go b/tme/group.go
@@ -91,3 +91,34 @@ func (g Group) List(entryTime *Time) ([]Entry, error) {
return entries, nil
}
+
+func (g Group) ListCompleted(entryTime *Time) ([]CompletedEntry, error) {
+ entries, err := g.List(entryTime)
+ if err != nil {
+ return []CompletedEntry{}, err
+ }
+
+ return listFilter[CompletedEntry](entries)
+}
+
+func (g Group) ListRunning(entryTime *Time) ([]RunningEntry, error) {
+ entries, err := g.List(entryTime)
+ if err != nil {
+ return []RunningEntry{}, err
+ }
+
+ return listFilter[RunningEntry](entries)
+}
+
+func listFilter[K CompletedEntry | RunningEntry](entries []Entry) ([]K, error) {
+ var completedEntries []K
+
+ for _, entry := range entries {
+ switch e := entry.(type) {
+ case K:
+ completedEntries = append(completedEntries, e)
+ }
+ }
+
+ return completedEntries, nil
+}
diff --git a/tme/running_entry.go b/tme/running_entry.go
@@ -56,8 +56,8 @@ func (e RunningEntry) Exists(group Group) bool {
return false
}
-func (e RunningEntry) Stop(stop time.Time) (FinalEntry, error) {
- return NewFinalEntry(e.Start, stop)
+func (e RunningEntry) Stop(stop time.Time) (CompletedEntry, error) {
+ return NewCompletedEntry(e.Start, stop)
}
func (e RunningEntry) Duration() time.Duration {
diff --git a/tme/time.go b/tme/time.go
@@ -13,7 +13,11 @@ type Time struct {
context time.Time
}
-func NewTime() *Time {
+func NewTime(t time.Time) *Time {
+ return &Time{t}
+}
+
+func NewTimeToday() *Time {
return &Time{time.Now()}
}
diff --git a/tme_test.go b/tme_test.go
@@ -13,12 +13,12 @@ import (
func TestAdd(t *testing.T) {
basePath := setUp()
t.Cleanup(func() { tearDown(basePath) })
- entryTime := tme.NewTime()
+ entryTime := tme.NewTimeToday()
group := createAndCheckGroup(t, basePath, "project")
start, _ := entryTime.ParseArg("10:00")
stop, _ := entryTime.ParseArg("11:00")
- tme, err := tme.NewFinalEntry(start, stop)
+ tme, err := tme.NewCompletedEntry(start, stop)
if err != nil {
t.Error(err)
}
@@ -37,7 +37,7 @@ func TestStart(t *testing.T) {
t.Cleanup(func() { tearDown(basePath) })
entryDir := "project"
fullEntryDir := strings.Join([]string{basePath, entryDir}, "/")
- entryTime := tme.NewTime()
+ entryTime := tme.NewTimeToday()
group := createAndCheckGroup(t, basePath, "project")
@@ -59,7 +59,7 @@ func TestStart(t *testing.T) {
func TestStop(t *testing.T) {
basePath := setUp()
t.Cleanup(func() { tearDown(basePath) })
- entryTime := tme.NewTime()
+ entryTime := tme.NewTimeToday()
group := tme.NewGroup(basePath, "project")
group.Create()
@@ -69,7 +69,7 @@ func TestStop(t *testing.T) {
group.Add(startEntry)
stop, _ := entryTime.ParseArg("11:00")
- entry, err := tme.NewFinalEntry(startEntry.Start, stop)
+ entry, err := tme.NewCompletedEntry(startEntry.Start, stop)
if err != nil {
t.Error(err)
}
@@ -91,7 +91,7 @@ func TestStop(t *testing.T) {
// start, _ := entryTime.ParseArg("10:00")
// stop, _ := entryTime.ParseArg("11:00")
-// entry, _ := tme.NewFinalEntry(start, stop)
+// entry, _ := tme.NewCompletedEntry(start, stop)
// group.Add(entry)
// lines, err := group.FormatList(entryTime)