From 0ddbeda2cbf2d7ec2686d186748fe27abfcf0d6e Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:01:18 -0700 Subject: [PATCH 1/2] move todos --- internal/daylog/daylog.go | 38 ++++++++++++++++++++--- internal/daylog/daylog_test.go | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/internal/daylog/daylog.go b/internal/daylog/daylog.go index b237472..e33bda7 100644 --- a/internal/daylog/daylog.go +++ b/internal/daylog/daylog.go @@ -11,6 +11,7 @@ import ( "github.com/adrg/xdg" "github.com/markusmobius/go-dateparser" "github.com/notnmeyer/daylog-cli/internal/editor" + "github.com/notnmeyer/daylog-cli/internal/file" "github.com/notnmeyer/daylog-cli/internal/git" "github.com/notnmeyer/daylog-cli/internal/output-formatter" ) @@ -215,23 +216,52 @@ func createIfMissing(d *DayLog) error { return nil } - var file *os.File + var f *os.File if os.IsNotExist(err) { - file, err = os.Create(d.Path) + f, err = os.Create(d.Path) if err != nil { return err } - defer file.Close() + defer f.Close() } else { return err } year, month, day := d.Date.Year(), int(d.Date.Month()), d.Date.Day() header := fmt.Sprintf("# %d/%02d/%02d\n\n", year, month, day) - _, err = file.WriteString(header) + _, err = f.WriteString(header) if err != nil { return err } + if todos := carryOverTodos(d.ProjectPath); len(todos) > 0 { + _, err = f.WriteString(strings.Join(todos, "\n") + "\n") + if err != nil { + return err + } + } + return nil } + +// carryOverTodos reads the previous log and returns any lines containing "TODO". +func carryOverTodos(projectPath string) []string { + prev, err := file.PreviousLog(projectPath, file.NewLogProvider()) + if err != nil { + return nil + } + + prevPath := filepath.Join(projectPath, prev, "log.md") + content, err := os.ReadFile(prevPath) + if err != nil { + return nil + } + + var todos []string + for _, line := range strings.Split(string(content), "\n") { + if strings.Contains(line, "TODO") { + todos = append(todos, line) + } + } + return todos +} diff --git a/internal/daylog/daylog_test.go b/internal/daylog/daylog_test.go index 47dfd06..82eed22 100644 --- a/internal/daylog/daylog_test.go +++ b/internal/daylog/daylog_test.go @@ -7,6 +7,19 @@ import ( "time" ) +// writePrevLog creates a log file for the given date under projectPath. +func writePrevLog(t *testing.T, projectPath, dateDir, content string) { + t.Helper() + dir := filepath.Join(projectPath, dateDir) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatalf("creating prev log dir: %v", err) + } + path := filepath.Join(dir, "log.md") + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatalf("writing prev log: %v", err) + } +} + func testDayLog(t *testing.T) *DayLog { t.Helper() dir := t.TempDir() @@ -88,4 +101,47 @@ func TestAppend(t *testing.T) { } } +func TestCarryOverTodos(t *testing.T) { + tests := []struct { + name string + prevContent string + expectedFile string + }{ + { + name: "no todos in previous log", + prevContent: "# 2025/12/01\n\ndid some work\n", + expectedFile: "# 2025/12/02\n\n", + }, + { + name: "todos are copied to new log", + prevContent: "# 2025/12/01\n\n- TODO: write tests\n- TODO: fix bug\n- done thing\n", + expectedFile: "# 2025/12/02\n\n- TODO: write tests\n- TODO: fix bug\n", + }, + { + name: "no previous log", + prevContent: "", + expectedFile: "# 2025/12/02\n\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dl := testDayLog(t) + + if tt.prevContent != "" { + writePrevLog(t, dl.ProjectPath, "2025/12/01", tt.prevContent) + } + + if err := createIfMissing(dl); err != nil { + t.Fatalf("createIfMissing() error = %v", err) + } + + got := readFile(t, dl.Path) + if got != tt.expectedFile { + t.Errorf("file contents =\n%q\nwant\n%q", got, tt.expectedFile) + } + }) + } +} + func strPtr(s string) *string { return &s } From cf2800683542b8ccf1fd1c2b41eb8b8776a790ca Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:33:28 -0700 Subject: [PATCH 2/2] create log for "show" command so TODOs are moved --- cmd/root.go | 3 ++- cmd/show.go | 3 ++- internal/daylog/daylog.go | 12 ++++++++---- internal/file/file.go | 21 ++++++++------------- internal/file/file_test.go | 23 +++++------------------ 5 files changed, 25 insertions(+), 37 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 51847d5..a9c325e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/charmbracelet/fang" "github.com/notnmeyer/daylog-cli/internal/daylog" @@ -88,7 +89,7 @@ func applyPrevFlag(cmd *cobra.Command, dl *daylog.DayLog) error { return err } if showPrevious { - prev, err := file.PreviousLog(dl.ProjectPath, file.LogProvider{}) + prev, err := file.PreviousLog(dl.ProjectPath, file.LogProvider{}, time.Now()) if err != nil { return err } diff --git a/cmd/show.go b/cmd/show.go index 7a9109c..ad1e420 100644 --- a/cmd/show.go +++ b/cmd/show.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "path/filepath" + "time" "github.com/notnmeyer/daylog-cli/internal/daylog" "github.com/notnmeyer/daylog-cli/internal/file" @@ -35,7 +36,7 @@ var showCmd = &cobra.Command{ } if showPrevious { - prev, err := file.PreviousLog(dl.ProjectPath, file.LogProvider{}) + prev, err := file.PreviousLog(dl.ProjectPath, file.LogProvider{}, time.Now()) if err != nil { log.Fatal(err) } diff --git a/internal/daylog/daylog.go b/internal/daylog/daylog.go index e33bda7..59fd897 100644 --- a/internal/daylog/daylog.go +++ b/internal/daylog/daylog.go @@ -105,6 +105,10 @@ func (d *DayLog) Edit() error { } func (d *DayLog) Show(format string) (string, error) { + if err := createIfMissing(d); err != nil { + return "", err + } + contents, err := editor.Read(d.Path) if err != nil { return "", err @@ -234,7 +238,7 @@ func createIfMissing(d *DayLog) error { return err } - if todos := carryOverTodos(d.ProjectPath); len(todos) > 0 { + if todos := carryOverTodos(d.ProjectPath, *d.Date); len(todos) > 0 { _, err = f.WriteString(strings.Join(todos, "\n") + "\n") if err != nil { return err @@ -244,9 +248,9 @@ func createIfMissing(d *DayLog) error { return nil } -// carryOverTodos reads the previous log and returns any lines containing "TODO". -func carryOverTodos(projectPath string) []string { - prev, err := file.PreviousLog(projectPath, file.NewLogProvider()) +// carryOverTodos reads the log before `before` and returns any lines containing "TODO". +func carryOverTodos(projectPath string, before time.Time) []string { + prev, err := file.PreviousLog(projectPath, file.NewLogProvider(), before) if err != nil { return nil } diff --git a/internal/file/file.go b/internal/file/file.go index 8ebff2b..4edb432 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -11,8 +11,6 @@ import ( "strconv" "strings" "time" - - "github.com/notnmeyer/daylog-cli/internal/dateutil" ) type FileLogProvider interface { @@ -59,15 +57,15 @@ func (LogProvider) GetLogs(projectPath string) ([]string, error) { return validFiles, nil } -func PreviousLog(path string, provider FileLogProvider) (string, error) { +func PreviousLog(path string, provider FileLogProvider, before time.Time) (string, error) { // get all the logs logs, err := provider.GetLogs(path) if err != nil { log.Fatal(err) } - // find the most recent existing log before today - prev, exists := findPreviousLog(logs) + // find the most recent existing log before the given date + prev, exists := findPreviousLog(logs, before) if !exists { return "", fmt.Errorf("no previous log found") } @@ -101,16 +99,13 @@ func convertLogToDisplayName(log string) string { return path.Join(split[0], split[1], split[2]) } -// logs is the list of keys of all logs. each log is YYYY/MM/DD, which the most recent date at index 0 -func findPreviousLog(logs []string) (string, bool) { - todayTime, err := time.Parse("2006/01/02", dateutil.GetCurrent().String()) - if err != nil { - return "", false - } - +// logs is the list of keys of all logs. each log is YYYY/MM/DD, with the most recent date at index 0 +func findPreviousLog(logs []string, before time.Time) (string, bool) { + // Compare dates only so time-of-day doesn't affect the result + beforeDate := time.Date(before.Year(), before.Month(), before.Day(), 0, 0, 0, 0, time.UTC) for _, log := range logs { l, err := time.Parse("2006/01/02", log) - if err == nil && l.Before(todayTime) { + if err == nil && l.Before(beforeDate) { return log, true } } diff --git a/internal/file/file_test.go b/internal/file/file_test.go index 30cf403..bca8dde 100644 --- a/internal/file/file_test.go +++ b/internal/file/file_test.go @@ -5,8 +5,7 @@ import ( "os" "path/filepath" "testing" - - "github.com/notnmeyer/daylog-cli/internal/dateutil" + "time" ) type mockLogProvider struct { @@ -110,13 +109,7 @@ func TestConvertLogToDisplayName(t *testing.T) { } func TestPreviousLog(t *testing.T) { - dateutil.GetCurrent = func() dateutil.Date { - return dateutil.Date{ - Year: 2025, - Month: 12, - Day: 2, - } - } + before := time.Date(2025, 12, 2, 0, 0, 0, 0, time.UTC) tests := []struct { name string @@ -149,7 +142,7 @@ func TestPreviousLog(t *testing.T) { logs: tt.logs, } t.Run(tt.name, func(t *testing.T) { - prev, err := PreviousLog("mock/path", provider) + prev, err := PreviousLog("mock/path", provider, before) if err != nil && err.Error() != tt.err.Error() { t.Errorf("findPreviousLog(%v) = found: %v, want: %v", tt.logs, err, tt.err) } @@ -161,13 +154,7 @@ func TestPreviousLog(t *testing.T) { } func TestFindPreviousLog(t *testing.T) { - dateutil.GetCurrent = func() dateutil.Date { - return dateutil.Date{ - Year: 2025, - Month: 12, - Day: 2, - } - } + before := time.Date(2025, 12, 2, 0, 0, 0, 0, time.UTC) tests := []struct { name string @@ -209,7 +196,7 @@ func TestFindPreviousLog(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - prev, found := findPreviousLog(tt.logs) + prev, found := findPreviousLog(tt.logs, before) if found != tt.found { t.Errorf("findPreviousLog(%v) = found: %v, want: %v", tt.logs, found, tt.found) }