Skip to content

Commit f4243c3

Browse files
feat(calendar): add --all flag to events command
List events from all calendars at once with merged, time-sorted output.
1 parent e7fae16 commit f4243c3

1 file changed

Lines changed: 161 additions & 40 deletions

File tree

internal/cmd/calendar.go

Lines changed: 161 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,25 @@ func newCalendarEventsCmd(flags *rootFlags) *cobra.Command {
131131
var max int64
132132
var page string
133133
var query string
134+
var all bool
134135

135136
cmd := &cobra.Command{
136-
Use: "events <calendarId>",
137-
Short: "List events from a calendar",
138-
Args: cobra.ExactArgs(1),
137+
Use: "events [<calendarId>]",
138+
Short: "List events from a calendar or all calendars",
139+
Args: cobra.MaximumNArgs(1),
139140
RunE: func(cmd *cobra.Command, args []string) error {
140-
u := ui.FromContext(cmd.Context())
141141
account, err := requireAccount(flags)
142142
if err != nil {
143143
return err
144144
}
145-
calendarID := args[0]
145+
146+
// Validate args
147+
if !all && len(args) == 0 {
148+
return errors.New("calendarId required unless --all is specified")
149+
}
150+
if all && len(args) > 0 {
151+
return errors.New("calendarId not allowed with --all flag")
152+
}
146153

147154
now := time.Now().UTC()
148155
oneWeekLater := now.Add(7 * 24 * time.Hour)
@@ -158,43 +165,12 @@ func newCalendarEventsCmd(flags *rootFlags) *cobra.Command {
158165
return err
159166
}
160167

161-
call := svc.Events.List(calendarID).
162-
TimeMin(from).
163-
TimeMax(to).
164-
MaxResults(max).
165-
PageToken(page).
166-
SingleEvents(true).
167-
OrderBy("startTime")
168-
if strings.TrimSpace(query) != "" {
169-
call = call.Q(query)
170-
}
171-
resp, err := call.Do()
172-
if err != nil {
173-
return err
174-
}
175-
if outfmt.IsJSON(cmd.Context()) {
176-
return outfmt.WriteJSON(os.Stdout, map[string]any{
177-
"events": resp.Items,
178-
"nextPageToken": resp.NextPageToken,
179-
})
180-
}
181-
182-
if len(resp.Items) == 0 {
183-
u.Err().Println("No events")
184-
return nil
185-
}
186-
187-
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
188-
fmt.Fprintln(tw, "ID\tSTART\tEND\tSUMMARY")
189-
for _, e := range resp.Items {
190-
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", e.Id, eventStart(e), eventEnd(e), e.Summary)
168+
if all {
169+
return listAllCalendarsEvents(cmd, svc, from, to, max, page, query)
191170
}
192-
_ = tw.Flush()
193171

194-
if resp.NextPageToken != "" {
195-
u.Err().Printf("# Next page: --page %s", resp.NextPageToken)
196-
}
197-
return nil
172+
calendarID := args[0]
173+
return listCalendarEvents(cmd, svc, calendarID, from, to, max, page, query)
198174
},
199175
}
200176

@@ -203,9 +179,154 @@ func newCalendarEventsCmd(flags *rootFlags) *cobra.Command {
203179
cmd.Flags().Int64Var(&max, "max", 10, "Max results")
204180
cmd.Flags().StringVar(&page, "page", "", "Page token")
205181
cmd.Flags().StringVar(&query, "query", "", "Free text search")
182+
cmd.Flags().BoolVar(&all, "all", false, "Fetch events from all calendars")
206183
return cmd
207184
}
208185

186+
func listCalendarEvents(cmd *cobra.Command, svc *calendar.Service, calendarID, from, to string, max int64, page, query string) error {
187+
u := ui.FromContext(cmd.Context())
188+
189+
call := svc.Events.List(calendarID).
190+
TimeMin(from).
191+
TimeMax(to).
192+
MaxResults(max).
193+
PageToken(page).
194+
SingleEvents(true).
195+
OrderBy("startTime")
196+
if strings.TrimSpace(query) != "" {
197+
call = call.Q(query)
198+
}
199+
resp, err := call.Do()
200+
if err != nil {
201+
return err
202+
}
203+
if outfmt.IsJSON(cmd.Context()) {
204+
return outfmt.WriteJSON(os.Stdout, map[string]any{
205+
"events": resp.Items,
206+
"nextPageToken": resp.NextPageToken,
207+
})
208+
}
209+
210+
if len(resp.Items) == 0 {
211+
u.Err().Println("No events")
212+
return nil
213+
}
214+
215+
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
216+
fmt.Fprintln(tw, "ID\tSTART\tEND\tSUMMARY")
217+
for _, e := range resp.Items {
218+
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", e.Id, eventStart(e), eventEnd(e), e.Summary)
219+
}
220+
_ = tw.Flush()
221+
222+
if resp.NextPageToken != "" {
223+
u.Err().Printf("# Next page: --page %s", resp.NextPageToken)
224+
}
225+
return nil
226+
}
227+
228+
type eventWithCalendar struct {
229+
*calendar.Event
230+
CalendarID string
231+
}
232+
233+
func listAllCalendarsEvents(cmd *cobra.Command, svc *calendar.Service, from, to string, max int64, page, query string) error {
234+
u := ui.FromContext(cmd.Context())
235+
236+
// Get all calendars
237+
calResp, err := svc.CalendarList.List().Do()
238+
if err != nil {
239+
return err
240+
}
241+
242+
if len(calResp.Items) == 0 {
243+
u.Err().Println("No calendars")
244+
return nil
245+
}
246+
247+
// Collect events from all calendars
248+
var allEvents []*eventWithCalendar
249+
for _, cal := range calResp.Items {
250+
call := svc.Events.List(cal.Id).
251+
TimeMin(from).
252+
TimeMax(to).
253+
MaxResults(max).
254+
PageToken(page).
255+
SingleEvents(true).
256+
OrderBy("startTime")
257+
if strings.TrimSpace(query) != "" {
258+
call = call.Q(query)
259+
}
260+
resp, err := call.Do()
261+
if err != nil {
262+
// Skip calendars that fail (e.g., due to permissions)
263+
continue
264+
}
265+
for _, e := range resp.Items {
266+
allEvents = append(allEvents, &eventWithCalendar{
267+
Event: e,
268+
CalendarID: cal.Id,
269+
})
270+
}
271+
}
272+
273+
if len(allEvents) == 0 {
274+
u.Err().Println("No events")
275+
return nil
276+
}
277+
278+
// Sort events by start time
279+
sortEventsByStartTime(allEvents)
280+
281+
if outfmt.IsJSON(cmd.Context()) {
282+
events := make([]map[string]any, 0, len(allEvents))
283+
for _, e := range allEvents {
284+
eventMap := map[string]any{
285+
"id": e.Id,
286+
"calendarId": e.CalendarID,
287+
"summary": e.Summary,
288+
"start": e.Start,
289+
"end": e.End,
290+
"status": e.Status,
291+
}
292+
if e.Description != "" {
293+
eventMap["description"] = e.Description
294+
}
295+
if e.Location != "" {
296+
eventMap["location"] = e.Location
297+
}
298+
if len(e.Attendees) > 0 {
299+
eventMap["attendees"] = e.Attendees
300+
}
301+
events = append(events, eventMap)
302+
}
303+
return outfmt.WriteJSON(os.Stdout, map[string]any{"events": events})
304+
}
305+
306+
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
307+
fmt.Fprintln(tw, "CALENDAR\tID\tSTART\tEND\tSUMMARY")
308+
for _, e := range allEvents {
309+
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", e.CalendarID, e.Id, eventStart(e.Event), eventEnd(e.Event), e.Summary)
310+
}
311+
_ = tw.Flush()
312+
313+
return nil
314+
}
315+
316+
func sortEventsByStartTime(events []*eventWithCalendar) {
317+
// Simple insertion sort since we expect relatively small lists
318+
for i := 1; i < len(events); i++ {
319+
key := events[i]
320+
keyStart := eventStart(key.Event)
321+
j := i - 1
322+
for j >= 0 && eventStart(events[j].Event) > keyStart {
323+
events[j+1] = events[j]
324+
j--
325+
}
326+
events[j+1] = key
327+
}
328+
}
329+
209330
func newCalendarEventCmd(flags *rootFlags) *cobra.Command {
210331
return &cobra.Command{
211332
Use: "event <calendarId> <eventId>",

0 commit comments

Comments
 (0)