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
10 changes: 5 additions & 5 deletions internal/calendar/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

// Client wraps the Google Calendar API service
type Client struct {
Service *calendar.Service
service *calendar.Service
}

// NewClient creates a new Calendar client with OAuth2 authentication
Expand All @@ -28,13 +28,13 @@ func NewClient(ctx context.Context) (*Client, error) {
}

return &Client{
Service: srv,
service: srv,
}, nil
}

// ListCalendars returns all calendars the user has access to
func (c *Client) ListCalendars() ([]*calendar.CalendarListEntry, error) {
resp, err := c.Service.CalendarList.List().Do()
resp, err := c.service.CalendarList.List().Do()
if err != nil {
return nil, fmt.Errorf("failed to list calendars: %w", err)
}
Expand All @@ -43,7 +43,7 @@ func (c *Client) ListCalendars() ([]*calendar.CalendarListEntry, error) {

// ListEvents returns events from the specified calendar within the given time range
func (c *Client) ListEvents(calendarID string, timeMin, timeMax string, maxResults int64) ([]*calendar.Event, error) {
call := c.Service.Events.List(calendarID).
call := c.service.Events.List(calendarID).
SingleEvents(true).
OrderBy("startTime")

Expand All @@ -66,7 +66,7 @@ func (c *Client) ListEvents(calendarID string, timeMin, timeMax string, maxResul

// GetEvent retrieves a single event by ID
func (c *Client) GetEvent(calendarID, eventID string) (*calendar.Event, error) {
event, err := c.Service.Events.Get(calendarID, eventID).Do()
event, err := c.service.Events.Get(calendarID, eventID).Do()
if err != nil {
return nil, fmt.Errorf("failed to get event: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/calendar/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

func TestClientStructure(t *testing.T) {
t.Run("Client has Service field", func(t *testing.T) {
t.Run("Client has private service field", func(t *testing.T) {
client := &Client{}
assert.Nil(t, client.Service)
assert.Nil(t, client.service)
})
}
37 changes: 9 additions & 28 deletions internal/cmd/calendar/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"time"

"github.com/spf13/cobra"

"github.com/open-cli-collective/google-readonly/internal/calendar"
)

func newEventsCommand() *cobra.Command {
Expand Down Expand Up @@ -68,32 +66,15 @@ Examples:
timeMax = endOfDay(t).Format(time.RFC3339)
}

events, err := client.ListEvents(calID, timeMin, timeMax, maxResults)
if err != nil {
return fmt.Errorf("failed to list events: %w", err)
}

if len(events) == 0 {
fmt.Println("No events found.")
return nil
}

// Convert to our format
parsedEvents := make([]*calendar.Event, len(events))
for i, e := range events {
parsedEvents[i] = calendar.ParseEvent(e)
}

if jsonOutput {
return printJSON(parsedEvents)
}

fmt.Printf("Found %d event(s):\n\n", len(events))
for _, event := range parsedEvents {
printEventSummary(event)
}

return nil
return listAndPrintEvents(client, EventListOptions{
CalendarID: calID,
TimeMin: timeMin,
TimeMax: timeMax,
MaxResults: maxResults,
JSONOutput: jsonOutput,
Header: "", // Will be generated based on count
EmptyMessage: "No events found.",
})
},
}

Expand Down
59 changes: 59 additions & 0 deletions internal/cmd/calendar/events_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package calendar

import (
"fmt"

"github.com/open-cli-collective/google-readonly/internal/calendar"
)

// EventListOptions configures how events are listed and displayed.
type EventListOptions struct {
CalendarID string
TimeMin string // RFC3339 format
TimeMax string // RFC3339 format
MaxResults int64
JSONOutput bool
Header string // Header message to print (empty to show count-based header)
EmptyMessage string // Message when no events found
}

// listAndPrintEvents fetches events and prints them according to the options.
// This is a shared helper used by today, week, and events commands.
func listAndPrintEvents(client calendar.CalendarClientInterface, opts EventListOptions) error {
events, err := client.ListEvents(opts.CalendarID, opts.TimeMin, opts.TimeMax, opts.MaxResults)
if err != nil {
return err
}

if len(events) == 0 {
if opts.EmptyMessage != "" {
fmt.Println(opts.EmptyMessage)
} else {
fmt.Println("No events found.")
}
return nil
}

// Convert to our format
parsedEvents := make([]*calendar.Event, len(events))
for i, e := range events {
parsedEvents[i] = calendar.ParseEvent(e)
}

if opts.JSONOutput {
return printJSON(parsedEvents)
}

// Print header
if opts.Header != "" {
fmt.Printf("%s\n\n", opts.Header)
} else {
fmt.Printf("Found %d event(s):\n\n", len(events))
}

for _, event := range parsedEvents {
printEventSummary(event)
}

return nil
}
7 changes: 2 additions & 5 deletions internal/cmd/calendar/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ package calendar

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/open-cli-collective/google-readonly/internal/calendar"
"github.com/open-cli-collective/google-readonly/internal/output"
)

// ClientFactory is the function used to create Calendar clients.
Expand All @@ -22,9 +21,7 @@ func newCalendarClient() (calendar.CalendarClientInterface, error) {

// printJSON outputs data as indented JSON
func printJSON(data any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
return output.JSONStdout(data)
}

// printEvent prints a single event in text format
Expand Down
40 changes: 9 additions & 31 deletions internal/cmd/calendar/today.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"time"

"github.com/spf13/cobra"

"github.com/open-cli-collective/google-readonly/internal/calendar"
)

func newTodayCommand() *cobra.Command {
Expand Down Expand Up @@ -36,35 +34,15 @@ Examples:
now := time.Now()
startOfDay, endOfDayTime := todayBounds(now)

timeMin := startOfDay.Format(time.RFC3339)
timeMax := endOfDayTime.Format(time.RFC3339)

events, err := client.ListEvents(calendarID, timeMin, timeMax, 50)
if err != nil {
return fmt.Errorf("failed to list today's events: %w", err)
}

if len(events) == 0 {
fmt.Println("No events today.")
return nil
}

// Convert to our format
parsedEvents := make([]*calendar.Event, len(events))
for i, e := range events {
parsedEvents[i] = calendar.ParseEvent(e)
}

if jsonOutput {
return printJSON(parsedEvents)
}

fmt.Printf("Today's events (%s):\n\n", now.Format("Mon, Jan 2, 2006"))
for _, event := range parsedEvents {
printEventSummary(event)
}

return nil
return listAndPrintEvents(client, EventListOptions{
CalendarID: calendarID,
TimeMin: startOfDay.Format(time.RFC3339),
TimeMax: endOfDayTime.Format(time.RFC3339),
MaxResults: 50,
JSONOutput: jsonOutput,
Header: fmt.Sprintf("Today's events (%s):", now.Format("Mon, Jan 2, 2006")),
EmptyMessage: "No events today.",
})
},
}

Expand Down
44 changes: 11 additions & 33 deletions internal/cmd/calendar/week.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"time"

"github.com/spf13/cobra"

"github.com/open-cli-collective/google-readonly/internal/calendar"
)

func newWeekCommand() *cobra.Command {
Expand Down Expand Up @@ -36,37 +34,17 @@ Examples:
now := time.Now()
startOfWeek, endOfWeek := weekBounds(now)

timeMin := startOfWeek.Format(time.RFC3339)
timeMax := endOfWeek.Format(time.RFC3339)

events, err := client.ListEvents(calendarID, timeMin, timeMax, 100)
if err != nil {
return fmt.Errorf("failed to list week's events: %w", err)
}

if len(events) == 0 {
fmt.Println("No events this week.")
return nil
}

// Convert to our format
parsedEvents := make([]*calendar.Event, len(events))
for i, e := range events {
parsedEvents[i] = calendar.ParseEvent(e)
}

if jsonOutput {
return printJSON(parsedEvents)
}

fmt.Printf("This week's events (%s - %s):\n\n",
startOfWeek.Format("Mon, Jan 2"),
endOfWeek.Format("Mon, Jan 2, 2006"))
for _, event := range parsedEvents {
printEventSummary(event)
}

return nil
return listAndPrintEvents(client, EventListOptions{
CalendarID: calendarID,
TimeMin: startOfWeek.Format(time.RFC3339),
TimeMax: endOfWeek.Format(time.RFC3339),
MaxResults: 100,
JSONOutput: jsonOutput,
Header: fmt.Sprintf("This week's events (%s - %s):",
startOfWeek.Format("Mon, Jan 2"),
endOfWeek.Format("Mon, Jan 2, 2006")),
EmptyMessage: "No events this week.",
})
},
}

Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func runShow(cmd *cobra.Command, args []string) error {
// Show email if we can get it without triggering auth
if keychain.HasStoredToken() && credStatus == "OK" {
if client, err := gmail.NewClient(context.Background()); err == nil {
if profile, err := client.Service.Users.GetProfile("me").Do(); err == nil {
if profile, err := client.GetProfile(); err == nil {
fmt.Printf("Email: %s\n", profile.EmailAddress)
}
}
Expand Down Expand Up @@ -151,7 +151,7 @@ func runTest(cmd *cobra.Command, args []string) error {
fmt.Println(" Token valid: OK")

// Test API access
profile, err := client.Service.Users.GetProfile("me").Do()
profile, err := client.GetProfile()
if err != nil {
fmt.Println(" Gmail API: FAILED")
return fmt.Errorf("failed to access Gmail API: %w", err)
Expand Down
7 changes: 2 additions & 5 deletions internal/cmd/contacts/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ package contacts

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/open-cli-collective/google-readonly/internal/contacts"
"github.com/open-cli-collective/google-readonly/internal/output"
)

// ClientFactory is the function used to create Contacts clients.
Expand All @@ -22,9 +21,7 @@ func newContactsClient() (contacts.ContactsClientInterface, error) {

// printJSON outputs data as indented JSON
func printJSON(data any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
return output.JSONStdout(data)
}

// printContact prints a single contact in text format
Expand Down
7 changes: 2 additions & 5 deletions internal/cmd/drive/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package drive

import (
"context"
"encoding/json"
"os"

"github.com/open-cli-collective/google-readonly/internal/drive"
"github.com/open-cli-collective/google-readonly/internal/output"
)

// ClientFactory is the function used to create Drive clients.
Expand All @@ -21,7 +20,5 @@ func newDriveClient() (drive.DriveClientInterface, error) {

// printJSON encodes data as indented JSON to stdout
func printJSON(data any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
return output.JSONStdout(data)
}
2 changes: 1 addition & 1 deletion internal/cmd/initcmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func verifyConnectivity() error {
fmt.Println(" OAuth token: OK")

// Get profile to verify connectivity and get email address
profile, err := client.Service.Users.GetProfile("me").Do()
profile, err := client.GetProfile()
if err != nil {
fmt.Println(" Gmail API: FAILED")
return fmt.Errorf("failed to access Gmail API: %w", err)
Expand Down
7 changes: 2 additions & 5 deletions internal/cmd/mail/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package mail

import (
"context"
"encoding/json"
"fmt"
"os"
"strings"

"github.com/open-cli-collective/google-readonly/internal/gmail"
"github.com/open-cli-collective/google-readonly/internal/output"
)

// ClientFactory is the function used to create Gmail clients.
Expand All @@ -23,9 +22,7 @@ func newGmailClient() (gmail.GmailClientInterface, error) {

// printJSON encodes data as indented JSON to stdout
func printJSON(data any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
return output.JSONStdout(data)
}

// MessagePrintOptions controls which fields to include in message output
Expand Down
Loading
Loading