Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions internal/daylog/daylog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
61 changes: 61 additions & 0 deletions internal/daylog/daylog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 9 additions & 11 deletions internal/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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
}
}
Expand Down
8 changes: 4 additions & 4 deletions internal/file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Loading