Skip to content

Commit 2eeb771

Browse files
Add --break option to finish subcommand (#36)
## Summary • Added a `--break` flag to the `finish` command that automatically starts a break after finishing a pomodoro • The break duration can be specified in minutes (e.g., `--break 5` or `--break 5m`) • Implemented in two separate commits: first working implementation, then refactored to eliminate code duplication ## Test plan - [x] Build successfully compiles - [x] `pomodoro finish --help` shows the new `--break` flag - [ ] Test `pomodoro finish --break 5` starts a 5-minute break after finishing a pomodoro - [ ] Test default break duration works when just using `--break` 🤖 Generated with [Claude Code](https://claude.ai/code)
2 parents ba1ed60 + 54855f3 commit 2eeb771

File tree

5 files changed

+146
-25
lines changed

5 files changed

+146
-25
lines changed

cmd/break.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
package cmd
22

33
import (
4-
"fmt"
5-
"strconv"
6-
"time"
7-
8-
"github.com/justincampbell/go-countdown"
9-
"github.com/justincampbell/go-countdown/format"
104
"github.com/open-pomodoro/openpomodoro-cli/hook"
115
"github.com/spf13/cobra"
126
)
@@ -42,21 +36,3 @@ func breakCmd(cmd *cobra.Command, args []string) error {
4236

4337
return hook.Run(client, "stop")
4438
}
45-
46-
func wait(d time.Duration) error {
47-
err := countdown.For(d, time.Second).Do(func(c *countdown.Countdown) error {
48-
fmt.Printf("\r%s", format.MinSec(c.Remaining()))
49-
return nil
50-
})
51-
52-
fmt.Println()
53-
54-
return err
55-
}
56-
57-
func parseDurationMinutes(s string) (time.Duration, error) {
58-
if _, err := strconv.Atoi(s); err == nil {
59-
s = fmt.Sprintf("%sm", s)
60-
}
61-
return time.ParseDuration(s)
62-
}

cmd/finish.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@ package cmd
22

33
import (
44
"fmt"
5+
"strings"
56
"time"
67

78
"github.com/open-pomodoro/openpomodoro-cli/format"
89
"github.com/open-pomodoro/openpomodoro-cli/hook"
910
"github.com/spf13/cobra"
1011
)
1112

13+
var breakFlag string
14+
1215
func init() {
1316
command := &cobra.Command{
1417
Use: "finish",
1518
Short: "Finish the current Pomodoro",
1619
RunE: finishCmd,
1720
}
1821

22+
command.Flags().StringVarP(&breakFlag, "break", "b", "", "take a break after finishing (duration in minutes)")
23+
command.Flags().Lookup("break").NoOptDefVal = " "
24+
1925
RootCmd.AddCommand(command)
2026
}
2127

@@ -32,5 +38,32 @@ func finishCmd(cmd *cobra.Command, args []string) error {
3238
return err
3339
}
3440

35-
return client.Finish()
41+
if err := client.Finish(); err != nil {
42+
return err
43+
}
44+
45+
if cmd.Flags().Changed("break") {
46+
breakDuration := settings.DefaultBreakDuration
47+
48+
trimmedFlag := strings.TrimSpace(breakFlag)
49+
if trimmedFlag != "" {
50+
var err error
51+
breakDuration, err = parseDurationMinutes(trimmedFlag)
52+
if err != nil {
53+
return err
54+
}
55+
}
56+
57+
if err := hook.Run(client, "break"); err != nil {
58+
return err
59+
}
60+
61+
if err := wait(breakDuration); err != nil {
62+
return err
63+
}
64+
65+
return hook.Run(client, "stop")
66+
}
67+
68+
return nil
3669
}

cmd/util.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"time"
7+
8+
"github.com/justincampbell/go-countdown"
9+
"github.com/justincampbell/go-countdown/format"
10+
)
11+
12+
// wait displays a countdown timer for the specified duration
13+
func wait(d time.Duration) error {
14+
err := countdown.For(d, time.Second).Do(func(c *countdown.Countdown) error {
15+
fmt.Printf("\r%s", format.MinSec(c.Remaining()))
16+
return nil
17+
})
18+
19+
fmt.Println()
20+
21+
return err
22+
}
23+
24+
// parseDurationMinutes parses a duration string, defaulting to minutes if no unit is specified
25+
func parseDurationMinutes(s string) (time.Duration, error) {
26+
if _, err := strconv.Atoi(s); err == nil {
27+
s = fmt.Sprintf("%sm", s)
28+
}
29+
return time.ParseDuration(s)
30+
}

cmd/util_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestParseDurationMinutes(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
input string
15+
expected time.Duration
16+
wantErr bool
17+
}{
18+
{
19+
name: "integer defaults to minutes",
20+
input: "5",
21+
expected: 5 * time.Minute,
22+
},
23+
{
24+
name: "explicit minutes",
25+
input: "10m",
26+
expected: 10 * time.Minute,
27+
},
28+
{
29+
name: "seconds",
30+
input: "30s",
31+
expected: 30 * time.Second,
32+
},
33+
{
34+
name: "hours",
35+
input: "1h",
36+
expected: 1 * time.Hour,
37+
},
38+
{
39+
name: "complex duration",
40+
input: "1h30m",
41+
expected: 90 * time.Minute,
42+
},
43+
{
44+
name: "invalid duration",
45+
input: "invalid",
46+
wantErr: true,
47+
},
48+
{
49+
name: "zero",
50+
input: "0",
51+
expected: 0,
52+
},
53+
}
54+
55+
for _, tt := range tests {
56+
t.Run(tt.name, func(t *testing.T) {
57+
got, err := parseDurationMinutes(tt.input)
58+
if tt.wantErr {
59+
require.Error(t, err)
60+
} else {
61+
require.NoError(t, err)
62+
assert.Equal(t, tt.expected, got)
63+
}
64+
})
65+
}
66+
}

test/finish.bats

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,19 @@ load test_helper
4343
[ "$status" -eq 0 ]
4444
assert_file_empty "current"
4545
}
46+
47+
@test "finish --break flag is accepted" {
48+
create_hook "break" 'exit 1'
49+
50+
pomodoro start "Task" --ago 1m
51+
run pomodoro finish --break
52+
[ "$status" -ne 0 ]
53+
}
54+
55+
@test "finish --break with custom duration is accepted" {
56+
create_hook "break" 'exit 1'
57+
58+
pomodoro start "Task" --ago 1m
59+
run pomodoro finish --break 5
60+
[ "$status" -ne 0 ]
61+
}

0 commit comments

Comments
 (0)