tme

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

commit c89a7084641ea1de118d935b3ca4cc307c7ed3fb
parent 35432023977291eba0a1c6fb2a3864f83e2b75c8
Author: Tomas Nemec <nemi@skaut.cz>
Date:   Fri, 10 Feb 2023 19:18:54 +0100

feat: list entries and list entries recursively

Diffstat:
Mcommand.go | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mmain.go | 12+++++++-----
Mtme/entry.go | 69+++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mtme/entry_test.go | 8++++----
Mtme/group.go | 52++++++++++++++++++++++++----------------------------
Mtme_test.go | 48++++++++++++++++++++++++------------------------
6 files changed, 174 insertions(+), 92 deletions(-)

diff --git a/command.go b/command.go @@ -3,13 +3,17 @@ package main import ( "errors" "fmt" + "io/fs" "os" + "path" + "path/filepath" + "strings" "gtms.dev/tme/tme" ) type Command struct { - basePath string + rootPath string args []string entryTime *tme.Time } @@ -52,11 +56,11 @@ func (c Command) add() { os.Exit(1) } - group := tme.NewGroup(c.basePath, groupArg) + group := tme.NewGroup(c.rootPath, groupArg) // TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo) group.Create() - entry, err := tme.NewEntry(start, stop) + entry, err := tme.NewFinalEntry(start, stop) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -85,11 +89,11 @@ func (c Command) start() { os.Exit(1) } - group := tme.NewGroup(c.basePath, groupArg) + group := tme.NewGroup(c.rootPath, groupArg) // TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo) group.Create() - entry := tme.NewStartEntry(start) + entry := tme.NewRunningEntry(start) if entry.Exists(group) { fmt.Fprintln(os.Stderr, "entry in this group already started") @@ -114,9 +118,9 @@ func (c Command) stop() { os.Exit(1) } - group := tme.NewGroup(c.basePath, groupArg) + group := tme.NewGroup(c.rootPath, groupArg) - startEntry, err := tme.NewStartEntryFromGroup(group, c.entryTime) + startEntry, err := tme.NewRunningEntryFromPath(group.ActivePath(), c.entryTime) if err != nil { fmt.Fprintf(os.Stderr, "no entry running in %q\n", groupArg) os.Exit(1) @@ -157,14 +161,65 @@ func (c Command) ls() { rootPath, _ = c.nextArg() } - group := tme.NewGroup(c.basePath, rootPath) - files, err := group.List() + group := tme.NewGroup(c.rootPath, rootPath) + formatEntries(group, c.entryTime) +} + +func (c Command) lsr() { + if len(c.args) > 1 { + fmt.Fprintln(os.Stderr, "ls [<path>]") + os.Exit(1) + } + + groupPath := "" + if len(c.args) == 1 { + groupPath, _ = c.nextArg() + } + + groups, err := groupTree(c.rootPath, groupPath) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - for _, file := range files { - fmt.Println(file) + for _, group := range groups { + formatEntries(group, c.entryTime) } } + +func formatEntries(group tme.Group, entryTime *tme.Time) { + entries, err := group.List(entryTime) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + for _, entry := range entries { + switch v := entry.(type) { + case tme.FinalEntry: + fmt.Printf("%v\t%v\t%v\n", group.EntryPath(entry), v.Start.Format(tme.TimeLayout), v.Stop.Format(tme.TimeLayout)) + case tme.RunningEntry: + fmt.Printf("%v\t%v\t%s\n", group.EntryPath(entry), v.Start.Format(tme.TimeLayout), "running") + } + } +} + +func groupTree(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() { + if path == groupPath { + return nil + } + groupPath := strings.TrimPrefix(path, rootPath) + group := tme.NewGroup(rootPath, groupPath) + groups = append(groups, group) + } + return nil + }) + if err != nil { + return []tme.Group{}, err + } + + return groups, nil +} diff --git a/main.go b/main.go @@ -12,14 +12,14 @@ const ( ) func main() { - basePath := os.Getenv("TME_DIR") - if basePath == "" { + rootPath := os.Getenv("TME_DIR") + if rootPath == "" { fmt.Fprintln(os.Stderr, "TME_DIR environment variable is not set.") os.Exit(1) } - if _, err := os.Stat(basePath); os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "%s does not exist\n", basePath) + if _, err := os.Stat(rootPath); os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "%s does not exist\n", rootPath) os.Exit(1) } @@ -32,7 +32,7 @@ func main() { args = args[1:] // shift entryTime := tme.NewTime() - command := Command{basePath, args, entryTime} + command := Command{rootPath, args, entryTime} if cmd == "add" { command.add() } else if cmd == "start" { @@ -41,5 +41,7 @@ func main() { command.stop() } else if cmd == "ls" { command.ls() + } else if cmd == "lsr" { + command.lsr() } } diff --git a/tme/entry.go b/tme/entry.go @@ -15,12 +15,42 @@ type Entry interface { FullPath(group Group) string } +func NewEntryFromPath(entryPath string, entryTime *Time) (Entry, 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 + + 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 FinalEntry{ + Start: start, + Stop: stop, + }, nil + } +} + type FinalEntry struct { Start time.Time Stop time.Time } -func NewEntry(start time.Time, stop time.Time) (FinalEntry, error) { +func NewFinalEntry(start time.Time, stop time.Time) (FinalEntry, error) { if start.After(stop) { return FinalEntry{}, errors.New("duration must be positive") } @@ -31,12 +61,12 @@ func NewEntry(start time.Time, stop time.Time) (FinalEntry, error) { }, nil } -func NewEntryFromPath(path string, entryTime *Time) (FinalEntry, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { +func NewFinalEntryFromPath(entryPath string, entryTime *Time) (FinalEntry, error) { + if _, err := os.Stat(entryPath); os.IsNotExist(err) { return FinalEntry{}, err } - data, err := os.ReadFile(path) + data, err := os.ReadFile(entryPath) if err != nil { return FinalEntry{}, err } @@ -72,55 +102,54 @@ func (e FinalEntry) Exists(group Group) bool { return false } -type StartEntry struct { +type RunningEntry struct { Start time.Time } -func NewStartEntry(start time.Time) StartEntry { - return StartEntry{ +func NewRunningEntry(start time.Time) RunningEntry { + return RunningEntry{ Start: start, } } -func NewStartEntryFromGroup(group Group, entryTime *Time) (StartEntry, error) { - startEntryPath := group.ActivePath() - if _, err := os.Stat(startEntryPath); os.IsNotExist(err) { - return StartEntry{}, err +func NewRunningEntryFromPath(entryPath string, entryTime *Time) (RunningEntry, error) { + if _, err := os.Stat(entryPath); os.IsNotExist(err) { + return RunningEntry{}, err } - data, err := os.ReadFile(startEntryPath) + data, err := os.ReadFile(entryPath) if err != nil { - return StartEntry{}, err + return RunningEntry{}, err } firstLine := strings.Split(string(data), "\n")[0] start, _ := entryTime.ParseEntry(firstLine) - return StartEntry{ + return RunningEntry{ Start: start, }, nil } -func (e StartEntry) Data() string { +func (e RunningEntry) Data() string { return fmt.Sprintf("%s\n", e.Start.Format(DateTimeLayout)) } -func (e StartEntry) FileName() string { +func (e RunningEntry) FileName() string { return "active" } -func (e StartEntry) FullPath(group Group) string { +func (e RunningEntry) FullPath(group Group) string { return path.Join(group.FullPath(), e.FileName()) } -func (e StartEntry) Exists(group Group) bool { +func (e RunningEntry) Exists(group Group) bool { if _, err := os.Stat(e.FullPath(group)); !os.IsNotExist(err) { return true } return false } -func (e StartEntry) Stop(stop time.Time) (FinalEntry, error) { - return NewEntry(e.Start, stop) +func (e RunningEntry) Stop(stop time.Time) (FinalEntry, error) { + return NewFinalEntry(e.Start, stop) } diff --git a/tme/entry_test.go b/tme/entry_test.go @@ -11,7 +11,7 @@ func TestEntry(t *testing.T) { t.Run("positive duration is ok", func(b *testing.T) { start, _ := entryTime.ParseArg("5:00") stop, _ := entryTime.ParseArg("6:00") - _, err := NewEntry(start, stop) + _, err := NewFinalEntry(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 := NewEntry(start, stop) + _, err := NewFinalEntry(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 := NewEntry(start, stop) + _, err := NewFinalEntry(start, stop) if err == nil { b.Error(err) } @@ -42,7 +42,7 @@ func TestStartStopEntry(t *testing.T) { t.Cleanup(func() { tearDown(basePath) }) start, _ := entryTime.ParseArg("6:00") - startEntry := NewStartEntry(start) + startEntry := NewRunningEntry(start) group := NewGroup(basePath, "project") group.Create() diff --git a/tme/group.go b/tme/group.go @@ -10,13 +10,13 @@ import ( ) type Group struct { - basePath string + rootPath string Path string } -func NewGroup(basePath, path string) Group { +func NewGroup(rootPath, path string) Group { return Group{ - basePath: basePath, + rootPath: rootPath, Path: path, } } @@ -28,8 +28,12 @@ func (g Group) Create() error { return nil } +func (g Group) Name() string { + return path.Base(g.Path) +} + func (g Group) FullPath() string { - return path.Join(g.basePath, g.Path) + return path.Join(g.rootPath, g.Path) } func (g Group) EntryPath(entry Entry) string { @@ -59,39 +63,31 @@ func (g Group) Remove(entry Entry) error { return os.Remove(entry.FullPath(g)) } -func (g Group) List() ([]string, error) { +func (g Group) List(entryTime *Time) ([]Entry, error) { if !g.Exists() { - return []string{}, errors.New("Group '" + g.Path + "' does not exist") + return []Entry{}, errors.New("Group '" + g.FullPath() + "' does not exist") } - var files []string + var entries []Entry err := filepath.WalkDir(g.FullPath(), func(path string, d fs.DirEntry, err error) error { if d.IsDir() { - return nil + if path == g.FullPath() { + return nil + } + return fs.SkipDir } - files = append(files, path) - return nil - }) - if err != nil { - return []string{}, err - } - - return files, nil -} -func (g Group) FormatList(entryTime *Time) ([]string, error) { - filePaths, err := g.List() - if err != nil { - return []string{}, err - } - - var lines []string - for _, path := range filePaths { entry, err := NewEntryFromPath(path, entryTime) if err != nil { - return []string{}, err + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } - lines = append(lines, fmt.Sprintf("%s %s %s", g.Path, entry.Start.Format(TimeLayout), entry.Stop.Format(TimeLayout))) + entries = append(entries, entry) + return nil + }) + if err != nil { + return []Entry{}, err } - return lines, nil + + return entries, nil } diff --git a/tme_test.go b/tme_test.go @@ -18,7 +18,7 @@ func TestAdd(t *testing.T) { start, _ := entryTime.ParseArg("10:00") stop, _ := entryTime.ParseArg("11:00") - tme, err := tme.NewEntry(start, stop) + tme, err := tme.NewFinalEntry(start, stop) if err != nil { t.Error(err) } @@ -42,7 +42,7 @@ func TestStart(t *testing.T) { group := createAndCheckGroup(t, basePath, "project") start, _ := entryTime.ParseArg("10:00") - entry := tme.NewStartEntry(start) + entry := tme.NewRunningEntry(start) group.Add(entry) if _, err := os.Stat(fullEntryDir); os.IsNotExist(err) { @@ -65,11 +65,11 @@ func TestStop(t *testing.T) { group.Create() start, _ := entryTime.ParseArg("10:00") - startEntry := tme.NewStartEntry(start) + startEntry := tme.NewRunningEntry(start) group.Add(startEntry) stop, _ := entryTime.ParseArg("11:00") - entry, err := tme.NewEntry(startEntry.Start, stop) + entry, err := tme.NewFinalEntry(startEntry.Start, stop) if err != nil { t.Error(err) } @@ -82,32 +82,32 @@ func TestStop(t *testing.T) { } } -func ExampleList() { - basePath := setUp() - entryTime := tme.NewTime() +// func ExampleList() { +// basePath := setUp() +// entryTime := tme.NewTime() - group := tme.NewGroup(basePath, "tme") - group.Create() +// group := tme.NewGroup(basePath, "tme") +// group.Create() - start, _ := entryTime.ParseArg("10:00") - stop, _ := entryTime.ParseArg("11:00") - entry, _ := tme.NewEntry(start, stop) - group.Add(entry) +// start, _ := entryTime.ParseArg("10:00") +// stop, _ := entryTime.ParseArg("11:00") +// entry, _ := tme.NewFinalEntry(start, stop) +// group.Add(entry) - lines, err := group.FormatList(entryTime) - if err != nil { - fmt.Print(err) - } +// lines, err := group.FormatList(entryTime) +// if err != nil { +// fmt.Print(err) +// } - for _, line := range lines { - fmt.Println(line) - } +// for _, line := range lines { +// fmt.Println(line) +// } - // Output: - // tme 10:00 11:00 +// // Output: +// // tme 10:00 11:00 - tearDown(basePath) -} +// tearDown(basePath) +// } func checkEntryFile(start time.Time, stop time.Time, data []byte, entryTime *tme.Time, t *testing.T) { lines := strings.Split(string(data), "\n")