tme

Toggl like Time Manager
git clone git://gtms.dev:tme
Log | Files | Refs

commit d30f0b07dd9a6863da2dbcefecfc28e949b0b140
parent e40e8434f1a48c9c5382fc8580e454bcd5dc3657
Author: Tomas Nemec <nemi@skaut.cz>
Date:   Sat, 11 Feb 2023 23:41:51 +0100

refctor

Diffstat:
Mcommand.go | 102++++++++++++++++++++++++++-----------------------------------------------------
Dcompleted_entry.go | 71-----------------------------------------------------------------------
Mentry.go | 65+++++++++++++++++++++++++++++++++--------------------------------
Mentry_test.go | 40+++-------------------------------------
Mgroup.go | 121++-----------------------------------------------------------------------------
Mmain.go | 3++-
Arange.go | 12++++++++++++
Arepository.go | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Drunning_entry.go | 65-----------------------------------------------------------------
Dtime.go | 159-------------------------------------------------------------------------------
Atime_context.go | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rtime_test.go -> time_context_test.go | 0
Mtme_test.go | 236+++++++++++++++++++++++++++++++++++++++----------------------------------------
13 files changed, 590 insertions(+), 673 deletions(-)

diff --git a/command.go b/command.go @@ -3,18 +3,14 @@ package main import ( "errors" "fmt" - "io/fs" "os" - "path" - "path/filepath" - "strings" "time" ) type Command struct { - rootPath string - args []string - entryTime *Time + repository FSRepository + args []string + entryTimeContext *TimeContext } func (c *Command) nextArg() (string, error) { @@ -43,21 +39,19 @@ func (c Command) add() { startArg, _ := c.nextArg() stopArg, _ := c.nextArg() - start, err := c.entryTime.ParseArg(startArg) + start, err := c.entryTimeContext.ParseArg(startArg) if err != nil { fmt.Fprintf(os.Stderr, "[start] time (%s) could not be parsed\n", startArg) os.Exit(1) } - stop, err := c.entryTime.ParseArg(stopArg) + stop, err := c.entryTimeContext.ParseArg(stopArg) if err != nil { fmt.Fprintf(os.Stderr, "[stop] time (%s) could not be parsed\n", stopArg) os.Exit(1) } - group := NewGroup(c.rootPath, groupArg) - // TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo) - group.Create() + group := NewGroup(groupArg) entry, err := NewCompletedEntry(start, stop) if err != nil { @@ -65,12 +59,12 @@ func (c Command) add() { os.Exit(1) } - if entry.Exists(group) { + if c.repository.ExistsEntry(group, entry) { fmt.Fprintln(os.Stderr, "entry already created") os.Exit(1) } - group.Add(entry) + c.repository.Save(group, entry) } func (c Command) start() { @@ -80,14 +74,12 @@ func (c Command) start() { } groupArg, _ := c.nextArg() - group := NewGroup(c.rootPath, groupArg) - // TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo) - group.Create() + group := NewGroup(groupArg) startArg, err := c.nextArg() start := time.Now() if err == nil { - start, err = c.entryTime.ParseArg(startArg) + start, err = c.entryTimeContext.ParseArg(startArg) if err != nil { fmt.Fprintf(os.Stderr, "[start] time (%s) could not be parsed\n", startArg) os.Exit(1) @@ -96,12 +88,12 @@ func (c Command) start() { entry := NewRunningEntry(start) - if entry.Exists(group) { + if c.repository.ExistsEntry(group, entry) { fmt.Fprintln(os.Stderr, "entry in this group already started") os.Exit(1) } - group.Add(entry) + c.repository.Save(group, entry) } func (c Command) stop() { @@ -111,46 +103,37 @@ func (c Command) stop() { } groupArg, _ := c.nextArg() - group := NewGroup(c.rootPath, groupArg) + group := NewGroup(groupArg) stop := time.Now() stopArg, err := c.nextArg() if err == nil { - stop, err = c.entryTime.ParseArg(stopArg) + stop, err = c.entryTimeContext.ParseArg(stopArg) if err != nil { fmt.Fprintf(os.Stderr, "[start] time (%s) could not be parsed\n", stopArg) os.Exit(1) } } - startEntry, err := NewRunningEntryFromPath(group.ActivePath(), c.entryTime) + runningEntry, err := c.repository.RunningEntry(group, c.entryTimeContext) if err != nil { fmt.Fprintf(os.Stderr, "no entry running in %q\n", groupArg) os.Exit(1) } - entry, err := startEntry.Stop(stop) + entry, err := runningEntry.Complete(stop) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if entry.Exists(group) { + if c.repository.ExistsEntry(group, entry) { fmt.Fprintln(os.Stderr, "entry already created") os.Exit(1) } - err = group.Add(entry) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - err = group.Remove(startEntry) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } + c.repository.Remove(group, runningEntry) + c.repository.Save(group, entry) } func (c Command) ls() { @@ -164,8 +147,8 @@ func (c Command) ls() { rootPath, _ = c.nextArg() } - group := NewGroup(c.rootPath, rootPath) - formatEntries(group, c.entryTime) + group := NewGroup(rootPath) + formatEntries(c.repository, group, c.entryTimeContext) } func (c Command) lsr() { @@ -179,14 +162,14 @@ func (c Command) lsr() { groupPath, _ = c.nextArg() } - groups, err := groupsRecursive(c.rootPath, groupPath) + groups, err := c.repository.ListGroupsDeep(groupPath) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for _, group := range groups { - formatEntries(group, c.entryTime) + formatEntries(c.repository, group, c.entryTimeContext) } } @@ -203,7 +186,7 @@ func (c Command) report() { // } sinceArg, _ := c.nextArg() - sinceTime, err := c.entryTime.ParseArg(sinceArg) + sinceTime, err := c.entryTimeContext.ParseArg(sinceArg) if err != nil { fmt.Fprintf(os.Stderr, "[since] time (%s) could not be parsed\n", sinceArg) os.Exit(1) @@ -212,14 +195,14 @@ func (c Command) report() { untilTime := time.Now() untilArgs, err := c.nextArg() if err == nil { - untilTime, err = c.entryTime.ParseArgRight(untilArgs) + untilTime, err = c.entryTimeContext.ParseArgRight(untilArgs) if err != nil { fmt.Fprintf(os.Stderr, "[until] time (%s) could not be parsed\n", sinceArg) os.Exit(1) } } - groups, err := groupsRecursive(c.rootPath, groupPath) + groups, err := c.repository.ListGroupsDeep(groupPath) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -228,7 +211,7 @@ func (c Command) report() { var atLeastOneEntry bool var totalDuration time.Duration for _, group := range groups { - entries, err := group.ListCompleted(c.entryTime) + entries, err := c.repository.ListCompleted(group, c.entryTimeContext) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -249,8 +232,8 @@ func (c Command) report() { } } -func formatEntries(group Group, entryTime *Time) { - entries, err := group.List(entryTime) +func formatEntries(repository FSRepository, group Group, entryTime *TimeContext) { + entries, err := repository.List(group, entryTime) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -262,40 +245,21 @@ func formatEntries(group Group, entryTime *Time) { } func formatEntry(group Group, entry Entry) { - groupPath := group.Path - duration := entry.Duration().Round(time.Second) + groupPath := group.Name timeLayout := "15:04:05 02/01/2006" var start, stop string + var duration time.Duration switch e := entry.(type) { case CompletedEntry: start = e.Start.Format(timeLayout) stop = e.Stop.Format(timeLayout) + duration = e.Duration().Round(time.Second) case RunningEntry: start = e.Start.Format(timeLayout) + duration = time.Since(e.Start).Round(time.Second) stop = "running" } fmt.Printf("%s\t%s\t%v\t%s\n", groupPath, start, stop, duration) } - -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 { - return nil - } - groupPath := strings.TrimPrefix(path, rootPath) - groupPath = strings.TrimPrefix(groupPath, string(os.PathSeparator)) - group := NewGroup(rootPath, groupPath) - groups = append(groups, group) - } - return nil - }) - if err != nil { - return []Group{}, err - } - - return groups, nil -} diff --git a/completed_entry.go b/completed_entry.go @@ -1,71 +0,0 @@ -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 @@ -1,45 +1,46 @@ package main import ( - "os" - "path" - "strings" + "errors" "time" ) -type Entry interface { - Data() string - FileName() string - FullPath(group Group) string - Duration() time.Duration +type Entry interface{} + +type CompletedEntry struct { + Start time.Time + Stop time.Time } -func NewEntryFromPath(entryPath string, entryTime *Time) (Entry, error) { - if _, err := os.Stat(entryPath); os.IsNotExist(err) { - return CompletedEntry{}, err +func NewCompletedEntry(start time.Time, stop time.Time) (CompletedEntry, error) { + if start.After(stop) || start.Equal(stop) { + return CompletedEntry{}, errors.New("duration must be positive") } - data, err := os.ReadFile(entryPath) - if err != nil { - return CompletedEntry{}, err - } + return CompletedEntry{ + Start: start, + Stop: stop, + }, nil +} - // TODO(tms) 11.11.22: format check - - base := path.Base(entryPath) - lines := strings.Split(string(data), "\n") - - if base == activeFile { - 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 +func (e CompletedEntry) Duration() time.Duration { + return e.Stop.Sub(e.Start) +} + +type RunningEntry struct { + Start time.Time +} + +func NewRunningEntry(start time.Time) RunningEntry { + return RunningEntry{ + Start: start, } } + +func (e RunningEntry) Duration() time.Duration { + return time.Since(e.Start) +} + +func (e RunningEntry) Complete(stop time.Time) (CompletedEntry, error) { + return NewCompletedEntry(e.Start, stop) +} diff --git a/entry_test.go b/entry_test.go @@ -1,14 +1,13 @@ package main import ( - "os" "testing" ) func TestEntry(t *testing.T) { entryTime := NewTimeToday() - t.Run("positive duration is ok", func(b *testing.T) { + t.Run("positive duration should pass", func(b *testing.T) { start, _ := entryTime.ParseArg("5:00") stop, _ := entryTime.ParseArg("6:00") _, err := NewCompletedEntry(start, stop) @@ -17,11 +16,11 @@ func TestEntry(t *testing.T) { } }) - t.Run("same duration is kind of ok?", func(b *testing.T) { + t.Run("zero duration should fail", func(b *testing.T) { start, _ := entryTime.ParseArg("6:00") stop, _ := entryTime.ParseArg("6:00") _, err := NewCompletedEntry(start, stop) - if err != nil { + if err == nil { b.Error(err) } }) @@ -35,36 +34,3 @@ func TestEntry(t *testing.T) { } }) } - -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/group.go b/group.go @@ -1,124 +1,9 @@ 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(), activeFile) -} - -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 + Name string } -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 +func NewGroup(name string) Group { + return Group{name} } diff --git a/main.go b/main.go @@ -30,7 +30,8 @@ func main() { args = args[1:] // shift entryTime := NewTimeToday() - command := Command{rootPath, args, entryTime} + repository := NewFSRepository(rootPath) + command := Command{repository, args, entryTime} if cmd == "add" { command.add() } else if cmd == "start" { diff --git a/range.go b/range.go @@ -0,0 +1,12 @@ +package main + +import "time" + +type Range interface { + Entries() []Entry +} + +type TimeRange struct { + since time.Time + until time.Time +} diff --git a/repository.go b/repository.go @@ -0,0 +1,230 @@ +package main + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "strings" +) + +type FSRepository struct { + rootPath string +} + +func NewFSRepository(rootPath string) FSRepository { + return FSRepository{rootPath} +} + +func (repo FSRepository) NewEntry(entryPath string, entryTime *TimeContext) (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 == activeFile { + 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 + } +} + +func (repo FSRepository) CompletedEntry(entryPath string, entryTime *TimeContext) (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 (repo FSRepository) RunningEntry(group Group, entryTime *TimeContext) (RunningEntry, error) { + entryPath := path.Join(repo.rootPath, group.Name, activeFile) + 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 (r FSRepository) Save(group Group, entry Entry) error { + // TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo) + groupPath := path.Join(r.rootPath, group.Name) + if err := os.MkdirAll(groupPath, os.ModePerm); err != nil { + return err + } + + entryPath := path.Join(r.rootPath, group.Name, r.EntryName(entry)) + if err := os.WriteFile(entryPath, []byte(r.EntryData(entry)), os.ModePerm); err != nil { + return err + } + + return nil +} + +func (repo FSRepository) EntryData(entry Entry) string { + switch e := entry.(type) { + case CompletedEntry: + return fmt.Sprintf("%s\n%s\n", e.Start.Format(DataTimeLayout), e.Stop.Format(DataTimeLayout)) + case RunningEntry: + return fmt.Sprintf("%s\n", e.Start.Format(DataTimeLayout)) + } + + return "_UNKNOWNTYPE_" +} + +func (r FSRepository) EntryName(entry Entry) string { + switch e := entry.(type) { + case CompletedEntry: + return strings.Join([]string{e.Start.Format(FileNameLayout), e.Stop.Format(FileNameLayout)}, "_") + case RunningEntry: + return activeFile + } + + return "_UNKNOWNTYPE_" +} + +func (repo FSRepository) Remove(group Group, entry Entry) error { + entryPath := path.Join(repo.rootPath, group.Name, repo.EntryName(entry)) + return os.Remove(entryPath) +} + +func (repo FSRepository) ExistsEntry(group Group, entry Entry) bool { + fullPath := path.Join(repo.rootPath, group.Name, repo.EntryName(entry)) + if _, err := os.Stat(fullPath); !os.IsNotExist(err) { + return true + } + return false +} +func (repo FSRepository) Exists(group Group) bool { + groupPath := path.Join(repo.rootPath, group.Name) + if _, err := os.Stat(groupPath); !os.IsNotExist(err) { + return true + } + return false +} + +func (repo FSRepository) ListGroupsDeep(groupPath string) ([]Group, error) { + var groups []Group + err := filepath.WalkDir(path.Join(repo.rootPath, groupPath), func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + if path == groupPath { + return nil + } + groupPath := strings.TrimPrefix(path, repo.rootPath) + groupPath = strings.TrimPrefix(groupPath, string(os.PathSeparator)) + group := NewGroup(groupPath) + groups = append(groups, group) + } + return nil + }) + if err != nil { + return []Group{}, err + } + + return groups, nil +} + +func (repo FSRepository) List(group Group, entryTime *TimeContext) ([]Entry, error) { + if !repo.Exists(group) { + return []Entry{}, errors.New("Group '" + group.Name + "' does not exist") + } + + var entries []Entry + groupPath := path.Join(repo.rootPath, group.Name) + err := filepath.WalkDir(groupPath, func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + if path == groupPath { + return nil + } + return fs.SkipDir + } + + entry, err := repo.NewEntry(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 (repo FSRepository) ListCompleted(group Group, entryTime *TimeContext) ([]CompletedEntry, error) { + entries, err := repo.List(group, entryTime) + if err != nil { + return []CompletedEntry{}, err + } + + return listFilter[CompletedEntry](entries) +} + +func (repo FSRepository) ListRunning(group Group, entryTime *TimeContext) ([]RunningEntry, error) { + entries, err := repo.List(group, 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/running_entry.go b/running_entry.go @@ -1,65 +0,0 @@ -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 activeFile -} - -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 @@ -1,159 +0,0 @@ -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_context.go b/time_context.go @@ -0,0 +1,159 @@ +package main + +import ( + "fmt" + "strings" + "time" +) + +const ( + FileNameLayout = "0601021504" + DataTimeLayout = time.RFC3339 +) + +type TimeContext struct { + context time.Time +} + +func NewTime(t time.Time) *TimeContext { + return &TimeContext{t} +} + +func NewTimeToday() *TimeContext { + return &TimeContext{time.Now()} +} + +func (et *TimeContext) ParseArg(raw string) (time.Time, error) { + return et.ParseArgDir(raw, false) +} + +func (et *TimeContext) ParseArgRight(raw string) (time.Time, error) { + return et.ParseArgDir(raw, true) +} + +// TODO(tms) 11.02.23: Simplify +func (et *TimeContext) 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 *TimeContext) 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_context_test.go diff --git a/tme_test.go b/tme_test.go @@ -1,153 +1,147 @@ package main -import ( - "fmt" - "os" - "strings" - "testing" - "time" -) - -func TestAdd(t *testing.T) { - basePath := setUp() - t.Cleanup(func() { tearDown(basePath) }) - entryTime := NewTimeToday() - group := createAndCheckGroup(t, basePath, "project") - - start, _ := entryTime.ParseArg("10:00") - stop, _ := entryTime.ParseArg("11:00") - tme, err := NewCompletedEntry(start, stop) - if err != nil { - t.Error(err) - } - - group.Add(tme) - if data, err := os.ReadFile(tme.FullPath(group)); err != nil { - fmt.Printf("%v", err) - } else { - checkEntryFile(start, stop, data, entryTime, t) - } - -} - -func TestStart(t *testing.T) { - basePath := setUp() - t.Cleanup(func() { tearDown(basePath) }) - entryDir := "project" - fullEntryDir := strings.Join([]string{basePath, entryDir}, "/") - entryTime := NewTimeToday() +import "os" - group := createAndCheckGroup(t, basePath, "project") +// func TestAdd(t *testing.T) { +// rootPath := setUp() +// t.Cleanup(func() { tearDown(rootPath) }) +// entryTime := NewTimeToday() +// group := createAndCheckGroup(t, rootPath, "project") - start, _ := entryTime.ParseArg("10:00") - entry := NewRunningEntry(start) +// start, _ := entryTime.ParseArg("10:00") +// stop, _ := entryTime.ParseArg("11:00") +// tme, err := NewCompletedEntry(start, stop) +// if err != nil { +// t.Error(err) +// } - group.Add(entry) - if _, err := os.Stat(fullEntryDir); os.IsNotExist(err) { - t.Fatalf(`want "%v" to exist`, entryDir) - } +// group.Add(tme) +// if data, err := os.ReadFile(tme.FullPath(group)); err != nil { +// fmt.Printf("%v", err) +// } else { +// checkEntryFile(start, stop, data, entryTime, t) +// } - if data, err := os.ReadFile(entry.FullPath(group)); err != nil { - fmt.Printf("%v", err) - } else { - checkStartEntryFile(start, data, entryTime, t) - } -} +// } -func TestStop(t *testing.T) { - basePath := setUp() - t.Cleanup(func() { tearDown(basePath) }) - entryTime := NewTimeToday() +// func TestStart(t *testing.T) { +// basePath := setUp() +// t.Cleanup(func() { tearDown(basePath) }) +// entryDir := "project" +// fullEntryDir := strings.Join([]string{basePath, entryDir}, "/") +// entryTime := NewTimeToday() - group := NewGroup(basePath, "project") - group.Create() +// group := createAndCheckGroup(t, basePath, "project") - start, _ := entryTime.ParseArg("10:00") - startEntry := NewRunningEntry(start) - group.Add(startEntry) +// start, _ := entryTime.ParseArg("10:00") +// entry := NewRunningEntry(start) - stop, _ := entryTime.ParseArg("11:00") - entry, err := NewCompletedEntry(startEntry.Start, stop) - if err != nil { - t.Error(err) - } +// group.Add(entry) +// if _, err := os.Stat(fullEntryDir); os.IsNotExist(err) { +// t.Fatalf(`want "%v" to exist`, entryDir) +// } - group.Add(entry) - if data, err := os.ReadFile(entry.FullPath(group)); err != nil { - t.Errorf("%v", err) - } else { - checkEntryFile(start, stop, data, entryTime, t) - } -} +// if data, err := os.ReadFile(entry.FullPath(group)); err != nil { +// fmt.Printf("%v", err) +// } else { +// checkStartEntryFile(start, data, entryTime, t) +// } +// } -// func ExampleList() { +// func TestStop(t *testing.T) { // basePath := setUp() -// entryTime := NewTime() +// t.Cleanup(func() { tearDown(basePath) }) +// entryTime := NewTimeToday() -// group := NewGroup(basePath, "tme") +// group := NewGroup(basePath, "project") // group.Create() // start, _ := entryTime.ParseArg("10:00") -// stop, _ := entryTime.ParseArg("11:00") -// entry, _ := NewCompletedEntry(start, stop) -// group.Add(entry) +// startEntry := NewRunningEntry(start) +// group.Add(startEntry) -// lines, err := group.FormatList(entryTime) +// stop, _ := entryTime.ParseArg("11:00") +// entry, err := NewCompletedEntry(startEntry.Start, stop) // if err != nil { -// fmt.Print(err) +// t.Error(err) // } -// for _, line := range lines { -// fmt.Println(line) +// group.Add(entry) +// if data, err := os.ReadFile(entry.FullPath(group)); err != nil { +// t.Errorf("%v", err) +// } else { +// checkEntryFile(start, stop, data, entryTime, t) // } +// } -// // Output: -// // tme 10:00 11:00 +// // func ExampleList() { +// // basePath := setUp() +// // entryTime := NewTime() -// tearDown(basePath) -// } +// // group := NewGroup(basePath, "tme") +// // group.Create() -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 { - t.Fatalf("want %d lines, got %d lines", expectLines, len(lines)) - } +// // start, _ := entryTime.ParseArg("10:00") +// // stop, _ := entryTime.ParseArg("11:00") +// // entry, _ := NewCompletedEntry(start, stop) +// // group.Add(entry) - startLine := lines[0] - lineStart, _ := entryTime.ParseEntry(startLine) - if start != lineStart { - t.Fatalf("time mismatch! %v != %v", start, lineStart) - } +// // lines, err := group.FormatList(entryTime) +// // if err != nil { +// // fmt.Print(err) +// // } - stopLine := lines[1] - lineStop, _ := entryTime.ParseEntry(stopLine) - if stop != lineStop { - t.Fatalf("time mismatch! %v != %v", stop, lineStop) - } -} +// // for _, line := range lines { +// // fmt.Println(line) +// // } -func checkStartEntryFile(start time.Time, data []byte, entryTime *Time, t *testing.T) { - lines := strings.Split(string(data), "\n") - expectLines := 2 - if len(lines) != expectLines { - t.Fatalf("want %d lines, got %d lines", expectLines, len(lines)) - } +// // // Output: +// // // tme 10:00 11:00 - startLine := lines[0] - lineStart, _ := entryTime.ParseEntry(startLine) - if start != lineStart { - t.Fatalf("time mismatch! %v != %v", start, lineStart) - } -} -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) - } - return group -} +// // tearDown(basePath) +// // } + +// func checkEntryFile(start time.Time, stop time.Time, data []byte, entryTime *TimeContext, t *testing.T) { +// lines := strings.Split(string(data), "\n") +// expectLines := 3 +// if len(lines) != expectLines { +// t.Fatalf("want %d lines, got %d lines", expectLines, len(lines)) +// } + +// startLine := lines[0] +// lineStart, _ := entryTime.ParseEntry(startLine) +// if start != lineStart { +// t.Fatalf("time mismatch! %v != %v", start, lineStart) +// } + +// stopLine := lines[1] +// lineStop, _ := entryTime.ParseEntry(stopLine) +// if stop != lineStop { +// t.Fatalf("time mismatch! %v != %v", stop, lineStop) +// } +// } + +// func checkStartEntryFile(start time.Time, data []byte, entryTime *TimeContext, t *testing.T) { +// lines := strings.Split(string(data), "\n") +// expectLines := 2 +// if len(lines) != expectLines { +// t.Fatalf("want %d lines, got %d lines", expectLines, len(lines)) +// } + +// startLine := lines[0] +// lineStart, _ := entryTime.ParseEntry(startLine) +// if start != lineStart { +// t.Fatalf("time mismatch! %v != %v", start, lineStart) +// } +// } +// 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) +// } +// return group +// } func setUp() string { tempDir, err := os.MkdirTemp("", "tme_*")