diff --git a/internal/daylog/daylog.go b/internal/daylog/daylog.go index 6ec9540..313afbf 100644 --- a/internal/daylog/daylog.go +++ b/internal/daylog/daylog.go @@ -104,6 +104,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 @@ -141,17 +145,46 @@ func createIfMissing(d *DayLog) error { return err } - file, err := os.Create(d.Path) + f, err := os.Create(d.Path) if err != nil { return err } - defer file.Close() + defer f.Close() year, month, day := d.Date.Year(), int(d.Date.Month()), d.Date.Day() header := fmt.Sprintf("# %d/%02d/%02d\n\n", year, month, day) - if _, err := file.WriteString(header); err != nil { + if _, err := f.WriteString(header); err != nil { return err } + if todos := carryOverTodos(d.ProjectPath, *d.Date); len(todos) > 0 { + _, err = f.WriteString(strings.Join(todos, "\n") + "\n") + if err != nil { + return err + } + } + return nil } + +// carryOverTodos reads the log before `before` and returns any lines starting with "- TODO:". +func carryOverTodos(projectPath string, before time.Time) []string { + prev, err := file.PreviousLog(projectPath, file.NewLogProvider(), before) + 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.HasPrefix(line, "- TODO:") { + todos = append(todos, line) + } + } + return todos +} diff --git a/internal/daylog/daylog_test.go b/internal/daylog/daylog_test.go index e8f421a..722b410 100644 --- a/internal/daylog/daylog_test.go +++ b/internal/daylog/daylog_test.go @@ -9,6 +9,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() @@ -90,6 +103,54 @@ 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: "only lines starting with - TODO: are copied", + prevContent: "# 2025/12/01\n\n- TODO: write tests\nthinking about TODOs\n - TODO: indented note\n- did a TODO\n", + expectedFile: "# 2025/12/02\n\n- TODO: write tests\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 } func TestProjectPathAt(t *testing.T) { diff --git a/internal/file/file.go b/internal/file/file.go index a73c8e1..3d4ce12 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -68,15 +68,15 @@ func (LogProvider) GetLogs(projectPath string) ([]string, error) { return validFiles, nil } -func PreviousLog(path string, provider FileLogProvider, now time.Time) (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 { return "", err } - // find the most recent existing log before today - prev, exists := findPreviousLog(logs, now) + // find the most recent existing log before the given date + prev, exists := findPreviousLog(logs, before) if !exists { return "", fmt.Errorf("no previous log found") } @@ -102,15 +102,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, now time.Time) (string, bool) { - today := now.Format("2006/01/02") - +// 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 { - if _, err := time.Parse("2006/01/02", log); err != nil { - continue - } - if log < today { + l, err := time.Parse("2006/01/02", log) + if err == nil && l.Before(beforeDate) { return log, true } } diff --git a/internal/file/file_test.go b/internal/file/file_test.go index b48a7c4..2fc4cef 100644 --- a/internal/file/file_test.go +++ b/internal/file/file_test.go @@ -114,7 +114,7 @@ func TestConvertLogToDisplayName(t *testing.T) { } func TestPreviousLog(t *testing.T) { - now := time.Date(2025, 12, 2, 0, 0, 0, 0, time.UTC) + before := time.Date(2025, 12, 2, 0, 0, 0, 0, time.UTC) tests := []struct { name string @@ -147,7 +147,7 @@ func TestPreviousLog(t *testing.T) { logs: tt.logs, } t.Run(tt.name, func(t *testing.T) { - prev, err := PreviousLog("mock/path", provider, now) + 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) } @@ -172,7 +172,7 @@ func TestPreviousLog_PropagatesProviderError(t *testing.T) { } func TestFindPreviousLog(t *testing.T) { - now := time.Date(2025, 12, 2, 0, 0, 0, 0, time.UTC) + before := time.Date(2025, 12, 2, 0, 0, 0, 0, time.UTC) tests := []struct { name string @@ -214,7 +214,7 @@ func TestFindPreviousLog(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - prev, found := findPreviousLog(tt.logs, now) + prev, found := findPreviousLog(tt.logs, before) if found != tt.found { t.Errorf("findPreviousLog(%v) = found: %v, want: %v", tt.logs, found, tt.found) }