commit 4e58099f16941ee6a6f5033774125da34088cd8e
parent 62d43b6978d914917b7be65c0fb0cabff12c8199
Author: Tomas Nemec <nemi@skaut.cz>
Date: Sat, 11 Feb 2023 20:56:55 +0100
refactor
Diffstat:
M | command.go | | | 34 | ++++++++++++++++------------------ |
A | completed_entry.go | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | entry.go | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
A | entry_test.go | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | go.mod | | | 2 | +- |
A | group.go | | | 124 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | main.go | | | 4 | +--- |
A | running_entry.go | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | time.go | | | 159 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | time_test.go | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
D | tme/completed_entry.go | | | 71 | ----------------------------------------------------------------------- |
D | tme/entry.go | | | 45 | --------------------------------------------- |
D | tme/entry_test.go | | | 84 | ------------------------------------------------------------------------------- |
D | tme/group.go | | | 124 | ------------------------------------------------------------------------------- |
D | tme/running_entry.go | | | 65 | ----------------------------------------------------------------- |
D | tme/time.go | | | 159 | ------------------------------------------------------------------------------- |
D | tme/time_test.go | | | 44 | -------------------------------------------- |
M | tme_test.go | | | 32 | +++++++++++++++----------------- |
18 files changed, 611 insertions(+), 631 deletions(-)
diff --git a/command.go b/command.go
@@ -9,14 +9,12 @@ import (
"path/filepath"
"strings"
"time"
-
- "gtms.dev/tme/tme"
)
type Command struct {
rootPath string
args []string
- entryTime *tme.Time
+ entryTime *Time
}
func (c *Command) nextArg() (string, error) {
@@ -57,11 +55,11 @@ func (c Command) add() {
os.Exit(1)
}
- group := tme.NewGroup(c.rootPath, groupArg)
+ group := NewGroup(c.rootPath, groupArg)
// TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo)
group.Create()
- entry, err := tme.NewCompletedEntry(start, stop)
+ entry, err := NewCompletedEntry(start, stop)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
@@ -82,7 +80,7 @@ func (c Command) start() {
}
groupArg, _ := c.nextArg()
- group := tme.NewGroup(c.rootPath, groupArg)
+ group := NewGroup(c.rootPath, groupArg)
// TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo)
group.Create()
@@ -96,7 +94,7 @@ func (c Command) start() {
}
}
- entry := tme.NewRunningEntry(start)
+ entry := NewRunningEntry(start)
if entry.Exists(group) {
fmt.Fprintln(os.Stderr, "entry in this group already started")
@@ -113,7 +111,7 @@ func (c Command) stop() {
}
groupArg, _ := c.nextArg()
- group := tme.NewGroup(c.rootPath, groupArg)
+ group := NewGroup(c.rootPath, groupArg)
stop := time.Now()
stopArg, err := c.nextArg()
@@ -125,7 +123,7 @@ func (c Command) stop() {
}
}
- startEntry, err := tme.NewRunningEntryFromPath(group.ActivePath(), c.entryTime)
+ startEntry, err := NewRunningEntryFromPath(group.ActivePath(), c.entryTime)
if err != nil {
fmt.Fprintf(os.Stderr, "no entry running in %q\n", groupArg)
os.Exit(1)
@@ -166,7 +164,7 @@ func (c Command) ls() {
rootPath, _ = c.nextArg()
}
- group := tme.NewGroup(c.rootPath, rootPath)
+ group := NewGroup(c.rootPath, rootPath)
formatEntries(group, c.entryTime)
}
@@ -251,7 +249,7 @@ func (c Command) report() {
}
}
-func formatEntries(group tme.Group, entryTime *tme.Time) {
+func formatEntries(group Group, entryTime *Time) {
entries, err := group.List(entryTime)
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -263,17 +261,17 @@ func formatEntries(group tme.Group, entryTime *tme.Time) {
}
}
-func formatEntry(group tme.Group, entry tme.Entry) {
+func formatEntry(group Group, entry Entry) {
groupPath := group.Path
duration := entry.Duration().Round(time.Second)
timeLayout := "15:04:05 02/01/2006"
var start, stop string
switch e := entry.(type) {
- case tme.CompletedEntry:
+ case CompletedEntry:
start = e.Start.Format(timeLayout)
stop = e.Stop.Format(timeLayout)
- case tme.RunningEntry:
+ case RunningEntry:
start = e.Start.Format(timeLayout)
stop = "running"
}
@@ -281,8 +279,8 @@ func formatEntry(group tme.Group, entry tme.Entry) {
fmt.Printf("%s\t%s\t%v\t%s\n", groupPath, start, stop, duration)
}
-func groupsRecursive(rootPath string, groupPath string) ([]tme.Group, error) {
- var groups []tme.Group
+func groupsRecursive(rootPath string, groupPath string) ([]Group, error) {
+ var groups []Group
err := filepath.WalkDir(path.Join(rootPath, groupPath), func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
if path == groupPath {
@@ -290,13 +288,13 @@ func groupsRecursive(rootPath string, groupPath string) ([]tme.Group, error) {
}
groupPath := strings.TrimPrefix(path, rootPath)
groupPath = strings.TrimPrefix(groupPath, string(os.PathSeparator))
- group := tme.NewGroup(rootPath, groupPath)
+ group := NewGroup(rootPath, groupPath)
groups = append(groups, group)
}
return nil
})
if err != nil {
- return []tme.Group{}, err
+ return []Group{}, err
}
return groups, nil
diff --git a/completed_entry.go b/completed_entry.go
@@ -0,0 +1,71 @@
+package main
+
+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/entry.go b/entry.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "os"
+ "path"
+ "strings"
+ "time"
+)
+
+type Entry interface {
+ Data() string
+ FileName() string
+ FullPath(group Group) string
+ Duration() time.Duration
+}
+
+func NewEntryFromPath(entryPath string, entryTime *Time) (Entry, 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
+
+ base := path.Base(entryPath)
+ lines := strings.Split(string(data), "\n")
+
+ if base == "active" {
+ start, _ := entryTime.ParseEntry(lines[0])
+ return RunningEntry{
+ Start: start,
+ }, nil
+ } else {
+ start, _ := entryTime.ParseEntry(lines[0])
+ stop, _ := entryTime.ParseEntry(lines[1])
+ return CompletedEntry{
+ Start: start,
+ Stop: stop,
+ }, nil
+ }
+}
diff --git a/entry_test.go b/entry_test.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "os"
+ "testing"
+)
+
+func TestEntry(t *testing.T) {
+ entryTime := NewTimeToday()
+
+ t.Run("positive duration is ok", func(b *testing.T) {
+ start, _ := entryTime.ParseArg("5:00")
+ stop, _ := entryTime.ParseArg("6:00")
+ _, err := NewCompletedEntry(start, stop)
+ if err != nil {
+ b.Error(err)
+ }
+ })
+
+ t.Run("same duration is kind of ok?", func(b *testing.T) {
+ start, _ := entryTime.ParseArg("6:00")
+ stop, _ := entryTime.ParseArg("6:00")
+ _, err := NewCompletedEntry(start, stop)
+ if err != nil {
+ b.Error(err)
+ }
+ })
+
+ t.Run("negative duration should fail", func(b *testing.T) {
+ start, _ := entryTime.ParseArg("6:00")
+ stop, _ := entryTime.ParseArg("5:00")
+ _, err := NewCompletedEntry(start, stop)
+ if err == nil {
+ b.Error(err)
+ }
+ })
+}
+
+func TestStartStopEntry(t *testing.T) {
+ basePath := setUp()
+ entryTime := NewTimeToday()
+ t.Cleanup(func() { tearDown(basePath) })
+
+ start, _ := entryTime.ParseArg("6:00")
+ startEntry := NewRunningEntry(start)
+
+ group := NewGroup(basePath, "project")
+ group.Create()
+
+ group.Add(startEntry)
+ if _, err := os.Stat(startEntry.FullPath(group)); os.IsNotExist(err) {
+ t.Fatalf(`want %q to exist`, startEntry.FullPath(group))
+ }
+
+ stop, _ := entryTime.ParseArg("7:00")
+ completedEntry, err := startEntry.Stop(stop)
+ if err != nil {
+ t.Error(err)
+ }
+
+ group.Remove(startEntry)
+ if _, err := os.Stat(startEntry.FullPath(group)); !os.IsNotExist(err) {
+ t.Fatalf(`want %q to be deleted`, startEntry.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/go.mod b/go.mod
@@ -1,3 +1,3 @@
module gtms.dev/tme
-go 1.19
+go 1.20
diff --git a/group.go b/group.go
@@ -0,0 +1,124 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path"
+ "path/filepath"
+)
+
+type Group struct {
+ rootPath string
+ Path string
+}
+
+func NewGroup(rootPath, path string) Group {
+ return Group{
+ rootPath: rootPath,
+ Path: path,
+ }
+}
+
+func (g Group) Create() error {
+ if err := os.MkdirAll(g.FullPath(), os.ModePerm); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (g Group) Name() string {
+ return path.Base(g.Path)
+}
+
+func (g Group) FullPath() string {
+ return path.Join(g.rootPath, g.Path)
+}
+
+func (g Group) EntryPath(entry Entry) string {
+ return path.Join(g.Path, entry.FileName())
+}
+
+func (g Group) ActivePath() string {
+ return path.Join(g.FullPath(), "active")
+}
+
+func (g Group) Exists() bool {
+ if _, err := os.Stat(g.FullPath()); !os.IsNotExist(err) {
+ return true
+ }
+ return false
+}
+
+func (g Group) Add(entry Entry) error {
+ err := os.WriteFile(entry.FullPath(g), []byte(entry.Data()), os.ModePerm)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (g Group) Remove(entry Entry) error {
+ return os.Remove(entry.FullPath(g))
+}
+
+func (g Group) List(entryTime *Time) ([]Entry, error) {
+ if !g.Exists() {
+ return []Entry{}, errors.New("Group '" + g.FullPath() + "' does not exist")
+ }
+
+ var entries []Entry
+ err := filepath.WalkDir(g.FullPath(), func(path string, d fs.DirEntry, err error) error {
+ if d.IsDir() {
+ if path == g.FullPath() {
+ return nil
+ }
+ return fs.SkipDir
+ }
+
+ entry, err := NewEntryFromPath(path, entryTime)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ entries = append(entries, entry)
+ return nil
+ })
+ if err != nil {
+ return []Entry{}, err
+ }
+
+ 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/main.go b/main.go
@@ -3,8 +3,6 @@ package main
import (
"fmt"
"os"
-
- "gtms.dev/tme/tme"
)
const (
@@ -31,7 +29,7 @@ func main() {
cmd := args[0]
args = args[1:] // shift
- entryTime := tme.NewTimeToday()
+ entryTime := NewTimeToday()
command := Command{rootPath, args, entryTime}
if cmd == "add" {
command.add()
diff --git a/running_entry.go b/running_entry.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "strings"
+ "time"
+)
+
+type RunningEntry struct {
+ Start time.Time
+}
+
+func NewRunningEntry(start time.Time) RunningEntry {
+ return RunningEntry{
+ Start: start,
+ }
+}
+
+func NewRunningEntryFromPath(entryPath string, entryTime *Time) (RunningEntry, error) {
+ if _, err := os.Stat(entryPath); os.IsNotExist(err) {
+ return RunningEntry{}, err
+ }
+
+ data, err := os.ReadFile(entryPath)
+ if err != nil {
+ return RunningEntry{}, err
+ }
+
+ firstLine := strings.Split(string(data), "\n")[0]
+ start, _ := entryTime.ParseEntry(firstLine)
+
+ return RunningEntry{
+ Start: start,
+ }, nil
+
+}
+
+func (e RunningEntry) Data() string {
+ return fmt.Sprintf("%s\n", e.Start.Format(DataTimeLayout))
+}
+
+func (e RunningEntry) FileName() string {
+ return "active"
+}
+
+func (e RunningEntry) FullPath(group Group) string {
+ return path.Join(group.FullPath(), e.FileName())
+}
+
+func (e RunningEntry) Exists(group Group) bool {
+ if _, err := os.Stat(e.FullPath(group)); !os.IsNotExist(err) {
+ return true
+ }
+ return false
+}
+
+func (e RunningEntry) Stop(stop time.Time) (CompletedEntry, error) {
+ return NewCompletedEntry(e.Start, stop)
+}
+
+func (e RunningEntry) Duration() time.Duration {
+ return time.Since(e.Start)
+}
diff --git a/time.go b/time.go
@@ -0,0 +1,159 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "time"
+)
+
+const (
+ FileNameLayout = "0601021504"
+ DataTimeLayout = time.RFC3339
+)
+
+type Time struct {
+ context time.Time
+}
+
+func NewTime(t time.Time) *Time {
+ return &Time{t}
+}
+
+func NewTimeToday() *Time {
+ return &Time{time.Now()}
+}
+
+func (et *Time) ParseArg(raw string) (time.Time, error) {
+ return et.ParseArgDir(raw, false)
+}
+
+func (et *Time) ParseArgRight(raw string) (time.Time, error) {
+ return et.ParseArgDir(raw, true)
+}
+
+// TODO(tms) 11.02.23: Simplify
+func (et *Time) ParseArgDir(raw string, shiftRight bool) (time.Time, error) {
+ parts := strings.Split(raw, " ")
+ hour := et.context.Hour()
+ min := et.context.Minute()
+ sec := et.context.Second()
+ year, mon, day := et.context.Date()
+
+ zeroTime := 0
+ if shiftRight {
+ zeroTime = 59
+ }
+
+ var parsed bool
+ part := parts[0]
+
+ t, err := time.Parse("15", part)
+ if err == nil {
+ parsed = true
+ hour = t.Hour()
+ min = zeroTime
+ sec = zeroTime
+ }
+
+ t, err = time.Parse("15:4", part)
+ if err == nil {
+ parsed = true
+ hour = t.Hour()
+ min = t.Minute()
+ sec = zeroTime
+ }
+
+ t, err = time.Parse("15:4:5", part)
+ if err == nil {
+ parsed = true
+ hour = t.Hour()
+ min = t.Minute()
+ sec = t.Second()
+ }
+
+ t, err = time.Parse("2/1", part)
+ if err == nil {
+ parsed = true
+ hour = zeroTime
+ min = zeroTime
+ sec = zeroTime
+ day = t.Day()
+ mon = t.Month()
+ year = et.context.Year()
+ }
+
+ t, err = time.Parse("1/2006", part)
+ if err == nil {
+ parsed = true
+ hour = zeroTime
+ min = zeroTime
+ sec = zeroTime
+
+ zeroDay := 1
+ firstOfMonth := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, et.context.Location())
+ lastOfMonth := firstOfMonth.AddDate(0, 1, -1)
+ if shiftRight {
+ zeroDay = lastOfMonth.Day()
+ }
+
+ day = zeroDay
+
+ mon = t.Month()
+ year = t.Year()
+ }
+
+ t, err = time.Parse("2/1/2006", part)
+ if err == nil {
+ parsed = true
+ hour = zeroTime
+ min = zeroTime
+ sec = zeroTime
+ day = t.Day()
+ mon = t.Month()
+ year = t.Year()
+ }
+
+ if !parsed && len(raw) > 0 {
+ return time.Now(), fmt.Errorf("Cannot parse %q", raw)
+ }
+
+ if len(parts) > 1 {
+ part = parts[1]
+ t, err := time.Parse("2", part)
+ if err == nil {
+ day = t.Day()
+ mon = et.context.Month()
+ year = et.context.Year()
+ }
+
+ t, err = time.Parse("2/1", part)
+ if err == nil {
+ day = t.Day()
+ mon = t.Month()
+ year = et.context.Year()
+ }
+
+ t, err = time.Parse("2/1/2006", part)
+ if err == nil {
+ day = t.Day()
+ mon = t.Month()
+ year = t.Year()
+ }
+ }
+
+ return time.Date(year, mon, day, hour, min, sec, 0, et.context.Location()), nil
+}
+
+func (et *Time) ParseEntry(raw string) (time.Time, error) {
+ t, err := time.Parse(DataTimeLayout, raw)
+ return time.Date(
+ t.Year(),
+ t.Month(),
+ t.Day(),
+ t.Hour(),
+ t.Minute(),
+ t.Second(),
+ 0,
+ et.context.Location(),
+ ), err
+}
diff --git a/time_test.go b/time_test.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "testing"
+ "time"
+)
+
+func TestParseArg(t *testing.T) {
+ layout := "15:4:5 2/1/2006"
+ context, _ := time.Parse(layout, "1:1:1 1/1/2001")
+ var check = func(raw string, expected string) {
+ t.Helper()
+ t.Run("", func(b *testing.T) {
+ b.Helper()
+ tmeTime := NewTime(context)
+ expected, _ := time.Parse(layout, expected)
+ parsed, err := tmeTime.ParseArg(raw)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ if !parsed.Equal(expected) {
+ b.Errorf("Parsed %v; Expected %v", parsed, expected)
+ }
+ })
+ }
+
+ check("5", "5:0:0 1/1/2001")
+ check("5:5", "5:5:0 1/1/2001")
+ check("5:5:5", "5:5:5 1/1/2001")
+ check("5:5 5", "5:5:0 5/1/2001")
+ check("5:5 5/5", "5:5:0 5/5/2001")
+ check("5:5 5/5/2005", "5:5:0 5/5/2005")
+
+ check("5/5", "0:0:0 5/5/2001")
+ check("5/5/2005", "0:0:0 5/5/2005")
+ check("5/2005", "0:0:0 1/5/2005")
+
+ check("5:05", "5:5:0 1/1/2001")
+ check("05:05", "5:5:0 1/1/2001")
+ check("05:5 05/5/2005", "5:5:0 5/5/2005")
+ check("5:5 5/05/2005", "5:5:0 5/5/2005")
+ check("5:05 5/05/2005", "5:5:0 5/5/2005")
+}
diff --git a/tme/completed_entry.go b/tme/completed_entry.go
@@ -1,71 +0,0 @@
-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
@@ -1,45 +0,0 @@
-package tme
-
-import (
- "os"
- "path"
- "strings"
- "time"
-)
-
-type Entry interface {
- Data() string
- FileName() string
- FullPath(group Group) string
- Duration() time.Duration
-}
-
-func NewEntryFromPath(entryPath string, entryTime *Time) (Entry, 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
-
- base := path.Base(entryPath)
- lines := strings.Split(string(data), "\n")
-
- if base == "active" {
- start, _ := entryTime.ParseEntry(lines[0])
- return RunningEntry{
- Start: start,
- }, nil
- } else {
- start, _ := entryTime.ParseEntry(lines[0])
- stop, _ := entryTime.ParseEntry(lines[1])
- return CompletedEntry{
- Start: start,
- Stop: stop,
- }, nil
- }
-}
diff --git a/tme/entry_test.go b/tme/entry_test.go
@@ -1,84 +0,0 @@
-package tme
-
-import (
- "os"
- "testing"
-)
-
-func TestEntry(t *testing.T) {
- entryTime := NewTimeToday()
-
- t.Run("positive duration is ok", func(b *testing.T) {
- start, _ := entryTime.ParseArg("5:00")
- stop, _ := entryTime.ParseArg("6:00")
- _, err := NewCompletedEntry(start, stop)
- if err != nil {
- b.Error(err)
- }
- })
-
- t.Run("same duration is kind of ok?", func(b *testing.T) {
- start, _ := entryTime.ParseArg("6:00")
- stop, _ := entryTime.ParseArg("6:00")
- _, err := NewCompletedEntry(start, stop)
- if err != nil {
- b.Error(err)
- }
- })
-
- t.Run("negative duration should fail", func(b *testing.T) {
- start, _ := entryTime.ParseArg("6:00")
- stop, _ := entryTime.ParseArg("5:00")
- _, err := NewCompletedEntry(start, stop)
- if err == nil {
- b.Error(err)
- }
- })
-}
-
-func TestStartStopEntry(t *testing.T) {
- basePath := setUp()
- entryTime := NewTimeToday()
- t.Cleanup(func() { tearDown(basePath) })
-
- start, _ := entryTime.ParseArg("6:00")
- startEntry := NewRunningEntry(start)
-
- group := NewGroup(basePath, "project")
- group.Create()
-
- group.Add(startEntry)
- if _, err := os.Stat(startEntry.FullPath(group)); os.IsNotExist(err) {
- t.Fatalf(`want %q to exist`, startEntry.FullPath(group))
- }
-
- stop, _ := entryTime.ParseArg("7:00")
- completedEntry, err := startEntry.Stop(stop)
- if err != nil {
- t.Error(err)
- }
-
- group.Remove(startEntry)
- if _, err := os.Stat(startEntry.FullPath(group)); !os.IsNotExist(err) {
- t.Fatalf(`want %q to be deleted`, startEntry.FullPath(group))
- }
-
- group.Add(completedEntry)
- if _, err := os.Stat(completedEntry.FullPath(group)); os.IsNotExist(err) {
- t.Fatalf(`want %q to exist`, completedEntry.FullPath(group))
- }
-}
-
-func setUp() string {
- tempDir, err := os.MkdirTemp("", "tme_*")
- if err != nil {
- panic(err)
- }
-
- os.Setenv("TME_DIR", tempDir)
- return tempDir
-}
-
-func tearDown(tempDir string) {
- os.RemoveAll(tempDir)
-}
diff --git a/tme/group.go b/tme/group.go
@@ -1,124 +0,0 @@
-package tme
-
-import (
- "errors"
- "fmt"
- "io/fs"
- "os"
- "path"
- "path/filepath"
-)
-
-type Group struct {
- rootPath string
- Path string
-}
-
-func NewGroup(rootPath, path string) Group {
- return Group{
- rootPath: rootPath,
- Path: path,
- }
-}
-
-func (g Group) Create() error {
- if err := os.MkdirAll(g.FullPath(), os.ModePerm); err != nil {
- return err
- }
- return nil
-}
-
-func (g Group) Name() string {
- return path.Base(g.Path)
-}
-
-func (g Group) FullPath() string {
- return path.Join(g.rootPath, g.Path)
-}
-
-func (g Group) EntryPath(entry Entry) string {
- return path.Join(g.Path, entry.FileName())
-}
-
-func (g Group) ActivePath() string {
- return path.Join(g.FullPath(), "active")
-}
-
-func (g Group) Exists() bool {
- if _, err := os.Stat(g.FullPath()); !os.IsNotExist(err) {
- return true
- }
- return false
-}
-
-func (g Group) Add(entry Entry) error {
- err := os.WriteFile(entry.FullPath(g), []byte(entry.Data()), os.ModePerm)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (g Group) Remove(entry Entry) error {
- return os.Remove(entry.FullPath(g))
-}
-
-func (g Group) List(entryTime *Time) ([]Entry, error) {
- if !g.Exists() {
- return []Entry{}, errors.New("Group '" + g.FullPath() + "' does not exist")
- }
-
- var entries []Entry
- err := filepath.WalkDir(g.FullPath(), func(path string, d fs.DirEntry, err error) error {
- if d.IsDir() {
- if path == g.FullPath() {
- return nil
- }
- return fs.SkipDir
- }
-
- entry, err := NewEntryFromPath(path, entryTime)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- entries = append(entries, entry)
- return nil
- })
- if err != nil {
- return []Entry{}, err
- }
-
- 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
@@ -1,65 +0,0 @@
-package tme
-
-import (
- "fmt"
- "os"
- "path"
- "strings"
- "time"
-)
-
-type RunningEntry struct {
- Start time.Time
-}
-
-func NewRunningEntry(start time.Time) RunningEntry {
- return RunningEntry{
- Start: start,
- }
-}
-
-func NewRunningEntryFromPath(entryPath string, entryTime *Time) (RunningEntry, error) {
- if _, err := os.Stat(entryPath); os.IsNotExist(err) {
- return RunningEntry{}, err
- }
-
- data, err := os.ReadFile(entryPath)
- if err != nil {
- return RunningEntry{}, err
- }
-
- firstLine := strings.Split(string(data), "\n")[0]
- start, _ := entryTime.ParseEntry(firstLine)
-
- return RunningEntry{
- Start: start,
- }, nil
-
-}
-
-func (e RunningEntry) Data() string {
- return fmt.Sprintf("%s\n", e.Start.Format(DataTimeLayout))
-}
-
-func (e RunningEntry) FileName() string {
- return "active"
-}
-
-func (e RunningEntry) FullPath(group Group) string {
- return path.Join(group.FullPath(), e.FileName())
-}
-
-func (e RunningEntry) Exists(group Group) bool {
- if _, err := os.Stat(e.FullPath(group)); !os.IsNotExist(err) {
- return true
- }
- return false
-}
-
-func (e RunningEntry) Stop(stop time.Time) (CompletedEntry, error) {
- return NewCompletedEntry(e.Start, stop)
-}
-
-func (e RunningEntry) Duration() time.Duration {
- return time.Since(e.Start)
-}
diff --git a/tme/time.go b/tme/time.go
@@ -1,159 +0,0 @@
-package tme
-
-import (
- "fmt"
- "strings"
- "time"
-)
-
-const (
- FileNameLayout = "0601021504"
- DataTimeLayout = time.RFC3339
-)
-
-type Time struct {
- context time.Time
-}
-
-func NewTime(t time.Time) *Time {
- return &Time{t}
-}
-
-func NewTimeToday() *Time {
- return &Time{time.Now()}
-}
-
-func (et *Time) ParseArg(raw string) (time.Time, error) {
- return et.ParseArgDir(raw, false)
-}
-
-func (et *Time) ParseArgRight(raw string) (time.Time, error) {
- return et.ParseArgDir(raw, true)
-}
-
-// TODO(tms) 11.02.23: Simplify
-func (et *Time) ParseArgDir(raw string, shiftRight bool) (time.Time, error) {
- parts := strings.Split(raw, " ")
- hour := et.context.Hour()
- min := et.context.Minute()
- sec := et.context.Second()
- year, mon, day := et.context.Date()
-
- zeroTime := 0
- if shiftRight {
- zeroTime = 59
- }
-
- var parsed bool
- part := parts[0]
-
- t, err := time.Parse("15", part)
- if err == nil {
- parsed = true
- hour = t.Hour()
- min = zeroTime
- sec = zeroTime
- }
-
- t, err = time.Parse("15:4", part)
- if err == nil {
- parsed = true
- hour = t.Hour()
- min = t.Minute()
- sec = zeroTime
- }
-
- t, err = time.Parse("15:4:5", part)
- if err == nil {
- parsed = true
- hour = t.Hour()
- min = t.Minute()
- sec = t.Second()
- }
-
- t, err = time.Parse("2/1", part)
- if err == nil {
- parsed = true
- hour = zeroTime
- min = zeroTime
- sec = zeroTime
- day = t.Day()
- mon = t.Month()
- year = et.context.Year()
- }
-
- t, err = time.Parse("1/2006", part)
- if err == nil {
- parsed = true
- hour = zeroTime
- min = zeroTime
- sec = zeroTime
-
- zeroDay := 1
- firstOfMonth := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, et.context.Location())
- lastOfMonth := firstOfMonth.AddDate(0, 1, -1)
- if shiftRight {
- zeroDay = lastOfMonth.Day()
- }
-
- day = zeroDay
-
- mon = t.Month()
- year = t.Year()
- }
-
- t, err = time.Parse("2/1/2006", part)
- if err == nil {
- parsed = true
- hour = zeroTime
- min = zeroTime
- sec = zeroTime
- day = t.Day()
- mon = t.Month()
- year = t.Year()
- }
-
- if !parsed && len(raw) > 0 {
- return time.Now(), fmt.Errorf("Cannot parse %q", raw)
- }
-
- if len(parts) > 1 {
- part = parts[1]
- t, err := time.Parse("2", part)
- if err == nil {
- day = t.Day()
- mon = et.context.Month()
- year = et.context.Year()
- }
-
- t, err = time.Parse("2/1", part)
- if err == nil {
- day = t.Day()
- mon = t.Month()
- year = et.context.Year()
- }
-
- t, err = time.Parse("2/1/2006", part)
- if err == nil {
- day = t.Day()
- mon = t.Month()
- year = t.Year()
- }
- }
-
- return time.Date(year, mon, day, hour, min, sec, 0, et.context.Location()), nil
-}
-
-func (et *Time) ParseEntry(raw string) (time.Time, error) {
- t, err := time.Parse(DataTimeLayout, raw)
- return time.Date(
- t.Year(),
- t.Month(),
- t.Day(),
- t.Hour(),
- t.Minute(),
- t.Second(),
- 0,
- et.context.Location(),
- ), err
-}
diff --git a/tme/time_test.go b/tme/time_test.go
@@ -1,44 +0,0 @@
-package tme
-
-import (
- "testing"
- "time"
-)
-
-func TestParseArg(t *testing.T) {
- layout := "15:4:5 2/1/2006"
- context, _ := time.Parse(layout, "1:1:1 1/1/2001")
- var check = func(raw string, expected string) {
- t.Helper()
- t.Run("", func(b *testing.T) {
- b.Helper()
- tmeTime := NewTime(context)
- expected, _ := time.Parse(layout, expected)
- parsed, err := tmeTime.ParseArg(raw)
- if err != nil {
- b.Fatal(err)
- }
-
- if !parsed.Equal(expected) {
- b.Errorf("Parsed %v; Expected %v", parsed, expected)
- }
- })
- }
-
- check("5", "5:0:0 1/1/2001")
- check("5:5", "5:5:0 1/1/2001")
- check("5:5:5", "5:5:5 1/1/2001")
- check("5:5 5", "5:5:0 5/1/2001")
- check("5:5 5/5", "5:5:0 5/5/2001")
- check("5:5 5/5/2005", "5:5:0 5/5/2005")
-
- check("5/5", "0:0:0 5/5/2001")
- check("5/5/2005", "0:0:0 5/5/2005")
- check("5/2005", "0:0:0 1/5/2005")
-
- check("5:05", "5:5:0 1/1/2001")
- check("05:05", "5:5:0 1/1/2001")
- check("05:5 05/5/2005", "5:5:0 5/5/2005")
- check("5:5 5/05/2005", "5:5:0 5/5/2005")
- check("5:05 5/05/2005", "5:5:0 5/5/2005")
-}
diff --git a/tme_test.go b/tme_test.go
@@ -6,19 +6,17 @@ import (
"strings"
"testing"
"time"
-
- "gtms.dev/tme/tme"
)
func TestAdd(t *testing.T) {
basePath := setUp()
t.Cleanup(func() { tearDown(basePath) })
- entryTime := tme.NewTimeToday()
+ entryTime := NewTimeToday()
group := createAndCheckGroup(t, basePath, "project")
start, _ := entryTime.ParseArg("10:00")
stop, _ := entryTime.ParseArg("11:00")
- tme, err := tme.NewCompletedEntry(start, stop)
+ tme, err := NewCompletedEntry(start, stop)
if err != nil {
t.Error(err)
}
@@ -37,12 +35,12 @@ func TestStart(t *testing.T) {
t.Cleanup(func() { tearDown(basePath) })
entryDir := "project"
fullEntryDir := strings.Join([]string{basePath, entryDir}, "/")
- entryTime := tme.NewTimeToday()
+ entryTime := NewTimeToday()
group := createAndCheckGroup(t, basePath, "project")
start, _ := entryTime.ParseArg("10:00")
- entry := tme.NewRunningEntry(start)
+ entry := NewRunningEntry(start)
group.Add(entry)
if _, err := os.Stat(fullEntryDir); os.IsNotExist(err) {
@@ -59,17 +57,17 @@ func TestStart(t *testing.T) {
func TestStop(t *testing.T) {
basePath := setUp()
t.Cleanup(func() { tearDown(basePath) })
- entryTime := tme.NewTimeToday()
+ entryTime := NewTimeToday()
- group := tme.NewGroup(basePath, "project")
+ group := NewGroup(basePath, "project")
group.Create()
start, _ := entryTime.ParseArg("10:00")
- startEntry := tme.NewRunningEntry(start)
+ startEntry := NewRunningEntry(start)
group.Add(startEntry)
stop, _ := entryTime.ParseArg("11:00")
- entry, err := tme.NewCompletedEntry(startEntry.Start, stop)
+ entry, err := NewCompletedEntry(startEntry.Start, stop)
if err != nil {
t.Error(err)
}
@@ -84,14 +82,14 @@ func TestStop(t *testing.T) {
// func ExampleList() {
// basePath := setUp()
-// entryTime := tme.NewTime()
+// entryTime := NewTime()
-// group := tme.NewGroup(basePath, "tme")
+// group := NewGroup(basePath, "tme")
// group.Create()
// start, _ := entryTime.ParseArg("10:00")
// stop, _ := entryTime.ParseArg("11:00")
-// entry, _ := tme.NewCompletedEntry(start, stop)
+// entry, _ := NewCompletedEntry(start, stop)
// group.Add(entry)
// lines, err := group.FormatList(entryTime)
@@ -109,7 +107,7 @@ func TestStop(t *testing.T) {
// tearDown(basePath)
// }
-func checkEntryFile(start time.Time, stop time.Time, data []byte, entryTime *tme.Time, t *testing.T) {
+func checkEntryFile(start time.Time, stop time.Time, data []byte, entryTime *Time, t *testing.T) {
lines := strings.Split(string(data), "\n")
expectLines := 3
if len(lines) != expectLines {
@@ -129,7 +127,7 @@ func checkEntryFile(start time.Time, stop time.Time, data []byte, entryTime *tme
}
}
-func checkStartEntryFile(start time.Time, data []byte, entryTime *tme.Time, t *testing.T) {
+func checkStartEntryFile(start time.Time, data []byte, entryTime *Time, t *testing.T) {
lines := strings.Split(string(data), "\n")
expectLines := 2
if len(lines) != expectLines {
@@ -142,8 +140,8 @@ func checkStartEntryFile(start time.Time, data []byte, entryTime *tme.Time, t *t
t.Fatalf("time mismatch! %v != %v", start, lineStart)
}
}
-func createAndCheckGroup(t *testing.T, basePath, groupName string) tme.Group {
- group := tme.NewGroup(basePath, groupName)
+func createAndCheckGroup(t *testing.T, basePath, groupName string) Group {
+ group := NewGroup(basePath, groupName)
group.Create()
if _, err := os.Stat(group.FullPath()); os.IsNotExist(err) {
t.Fatalf(`want %q to exist`, group.Path)