Detailed changes
@@ -151,5 +151,6 @@ linters:
- path: internal/ui/
linters:
- gochecknoglobals # Style constants are package-level vars
+ - ireturn # Generic spinner helper uses type params
- path: internal/config/
text: "0o700" # Config directory permissions are intentional
@@ -63,7 +63,11 @@ func runTrack(cmd *cobra.Command, args []string) error {
req := &lunatask.TrackHabitActivityRequest{PerformedOn: date}
- if _, err := apiClient.TrackHabitActivity(cmd.Context(), habit.ID, req); err != nil {
+ if err := ui.SpinVoid("Tracking habit…", func() error {
+ _, err := apiClient.TrackHabitActivity(cmd.Context(), habit.ID, req)
+
+ return err
+ }); err != nil {
return err
}
@@ -13,7 +13,6 @@ import (
"git.secluded.site/go-lunatask"
"github.com/charmbracelet/huh"
- "github.com/charmbracelet/huh/spinner"
"github.com/spf13/cobra"
"git.secluded.site/lune/internal/client"
@@ -205,19 +204,9 @@ func promptForToken(out io.Writer) error {
}
func validateWithSpinner(token string) error {
- var validationErr error
-
- err := spinner.New().
- Title("Verifying access token...").
- Action(func() {
- validationErr = validateTokenWithPing(token)
- }).
- Run()
- if err != nil {
- return err
- }
-
- return validationErr
+ return ui.SpinVoid("Verifying access token…", func() error {
+ return validateTokenWithPing(token)
+ })
}
func validateTokenWithPing(token string) error {
@@ -9,6 +9,7 @@ import (
"os"
"strings"
+ "git.secluded.site/go-lunatask"
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/dateutil"
"git.secluded.site/lune/internal/ui"
@@ -60,7 +61,9 @@ func runAdd(cmd *cobra.Command, args []string) error {
builder.WithName(name)
}
- entry, err := builder.Create(cmd.Context())
+ entry, err := ui.Spin("Creating journal entry…", func() (*lunatask.JournalEntry, error) {
+ return builder.Create(cmd.Context())
+ })
if err != nil {
return err
}
@@ -72,7 +72,9 @@ func runAdd(cmd *cobra.Command, args []string) error {
applySource(cmd, builder)
- note, err := builder.Create(cmd.Context())
+ note, err := ui.Spin("Creating note…", func() (*lunatask.Note, error) {
+ return builder.Create(cmd.Context())
+ })
if err != nil {
return err
}
@@ -7,6 +7,7 @@ package note
import (
"fmt"
+ "git.secluded.site/go-lunatask"
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/ui"
"git.secluded.site/lune/internal/validate"
@@ -48,7 +49,9 @@ func runDelete(cmd *cobra.Command, args []string) error {
return err
}
- note, err := apiClient.DeleteNote(cmd.Context(), id)
+ note, err := ui.Spin("Deleting note…", func() (*lunatask.Note, error) {
+ return apiClient.DeleteNote(cmd.Context(), id)
+ })
if err != nil {
return err
}
@@ -47,7 +47,9 @@ func runList(cmd *cobra.Command, _ []string) error {
opts := buildListOptions(cmd)
- notes, err := apiClient.ListNotes(cmd.Context(), opts)
+ notes, err := ui.Spin("Fetching notes…", func() ([]lunatask.Note, error) {
+ return apiClient.ListNotes(cmd.Context(), opts)
+ })
if err != nil {
return err
}
@@ -45,7 +45,9 @@ func runShow(cmd *cobra.Command, args []string) error {
return err
}
- note, err := apiClient.GetNote(cmd.Context(), id)
+ note, err := ui.Spin("Fetching note…", func() (*lunatask.Note, error) {
+ return apiClient.GetNote(cmd.Context(), id)
+ })
if err != nil {
return err
}
@@ -67,7 +67,9 @@ func runUpdate(cmd *cobra.Command, args []string) error {
return err
}
- note, err := builder.Update(cmd.Context())
+ note, err := ui.Spin("Updating note…", func() (*lunatask.Note, error) {
+ return builder.Update(cmd.Context())
+ })
if err != nil {
return err
}
@@ -51,7 +51,9 @@ func runAdd(cmd *cobra.Command, args []string) error {
applySource(cmd, builder)
- person, err := builder.Create(cmd.Context())
+ person, err := ui.Spin("Creating person…", func() (*lunatask.Person, error) {
+ return builder.Create(cmd.Context())
+ })
if err != nil {
return err
}
@@ -7,6 +7,7 @@ package person
import (
"fmt"
+ "git.secluded.site/go-lunatask"
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/ui"
"git.secluded.site/lune/internal/validate"
@@ -48,7 +49,9 @@ func runDelete(cmd *cobra.Command, args []string) error {
return err
}
- person, err := apiClient.DeletePerson(cmd.Context(), id)
+ person, err := ui.Spin("Deleting person…", func() (*lunatask.Person, error) {
+ return apiClient.DeletePerson(cmd.Context(), id)
+ })
if err != nil {
return err
}
@@ -45,7 +45,9 @@ func runList(cmd *cobra.Command, _ []string) error {
opts := buildListOptions(cmd)
- people, err := apiClient.ListPeople(cmd.Context(), opts)
+ people, err := ui.Spin("Fetching people…", func() ([]lunatask.Person, error) {
+ return apiClient.ListPeople(cmd.Context(), opts)
+ })
if err != nil {
return err
}
@@ -44,7 +44,9 @@ func runShow(cmd *cobra.Command, args []string) error {
return err
}
- person, err := apiClient.GetPerson(cmd.Context(), id)
+ person, err := ui.Spin("Fetching person…", func() (*lunatask.Person, error) {
+ return apiClient.GetPerson(cmd.Context(), id)
+ })
if err != nil {
return err
}
@@ -54,7 +54,9 @@ func runTimeline(cmd *cobra.Command, args []string) error {
return err
}
- note, err := builder.Create(cmd.Context())
+ note, err := ui.Spin("Creating timeline note…", func() (*lunatask.PersonTimelineNote, error) {
+ return builder.Create(cmd.Context())
+ })
if err != nil {
return err
}
@@ -54,7 +54,9 @@ func runUpdate(cmd *cobra.Command, args []string) error {
return err
}
- person, err := builder.Update(cmd.Context())
+ person, err := ui.Spin("Updating person…", func() (*lunatask.Person, error) {
+ return builder.Update(cmd.Context())
+ })
if err != nil {
return err
}
@@ -7,6 +7,7 @@ package cmd
import (
"fmt"
+ "git.secluded.site/go-lunatask"
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/ui"
"github.com/spf13/cobra"
@@ -21,7 +22,9 @@ var pingCmd = &cobra.Command{
return err
}
- resp, err := c.Ping(cmd.Context())
+ resp, err := ui.Spin("Verifying token…", func() (*lunatask.PingResponse, error) {
+ return c.Ping(cmd.Context())
+ })
if err != nil {
return err
}
@@ -84,7 +84,9 @@ func runAdd(cmd *cobra.Command, args []string) error {
return err
}
- task, err := builder.Create(cmd.Context())
+ task, err := ui.Spin("Creating task…", func() (*lunatask.Task, error) {
+ return builder.Create(cmd.Context())
+ })
if err != nil {
return err
}
@@ -49,7 +49,11 @@ func runDelete(cmd *cobra.Command, args []string) error {
return err
}
- if _, err := apiClient.DeleteTask(cmd.Context(), id); err != nil {
+ if err := ui.SpinVoid("Deleting task…", func() error {
+ _, err := apiClient.DeleteTask(cmd.Context(), id)
+
+ return err
+ }); err != nil {
return err
}
@@ -55,7 +55,9 @@ func runList(cmd *cobra.Command, _ []string) error {
return err
}
- tasks, err := apiClient.ListTasks(cmd.Context(), nil)
+ tasks, err := ui.Spin("Fetching tasks…", func() ([]lunatask.Task, error) {
+ return apiClient.ListTasks(cmd.Context(), nil)
+ })
if err != nil {
return err
}
@@ -45,7 +45,9 @@ func runShow(cmd *cobra.Command, args []string) error {
return err
}
- task, err := apiClient.GetTask(cmd.Context(), id)
+ task, err := ui.Spin("Fetching task…", func() (*lunatask.Task, error) {
+ return apiClient.GetTask(cmd.Context(), id)
+ })
if err != nil {
return err
}
@@ -79,7 +79,9 @@ func runUpdate(cmd *cobra.Command, args []string) error {
return err
}
- task, err := builder.Update(cmd.Context())
+ task, err := ui.Spin("Updating task…", func() (*lunatask.Task, error) {
+ return builder.Update(cmd.Context())
+ })
if err != nil {
return err
}
@@ -10,6 +10,7 @@ import (
"fmt"
"git.secluded.site/go-lunatask"
+ "git.secluded.site/lune/internal/ui"
)
// TaskCounter provides methods to count tasks by various criteria.
@@ -71,7 +72,9 @@ func (tc *TaskCounter) ensureTasks(ctx context.Context) error {
return nil
}
- tasks, err := tc.client.ListTasks(ctx, nil)
+ tasks, err := ui.Spin("Fetching tasks…", func() ([]lunatask.Task, error) {
+ return tc.client.ListTasks(ctx, nil)
+ })
if err != nil {
return fmt.Errorf("listing tasks: %w", err)
}
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package ui
+
+import (
+ "fmt"
+
+ "github.com/charmbracelet/huh/spinner"
+)
+
+// Spin executes fn while displaying a spinner with the given title.
+// Uses generics to preserve the return type of the wrapped function.
+func Spin[T any](title string, fn func() (T, error)) (T, error) {
+ var result T
+
+ var fnErr error
+
+ spinErr := spinner.New().
+ Title(title).
+ Action(func() {
+ result, fnErr = fn()
+ }).
+ Run()
+ if spinErr != nil {
+ return result, fmt.Errorf("spinner: %w", spinErr)
+ }
+
+ return result, fnErr
+}
+
+// SpinVoid executes fn while displaying a spinner with the given title.
+// Use for functions that only return an error.
+func SpinVoid(title string, fn func() error) error {
+ var fnErr error
+
+ spinErr := spinner.New().
+ Title(title).
+ Action(func() {
+ fnErr = fn()
+ }).
+ Run()
+ if spinErr != nil {
+ return fmt.Errorf("spinner: %w", spinErr)
+ }
+
+ return fnErr
+}