11package cmd
22
3- import (
4- "context"
5- "fmt"
6- "os"
7- "strings"
8-
9- "google.golang.org/api/calendar/v3"
10-
11- "github.com/steipete/gogcli/internal/outfmt"
12- "github.com/steipete/gogcli/internal/ui"
13- )
14-
153type CalendarCmd struct {
164 Calendars CalendarCalendarsCmd `cmd:"" name:"calendars" help:"List calendars"`
175 Subscribe CalendarSubscribeCmd `cmd:"" name:"subscribe" aliases:"sub,add-calendar" help:"Add a calendar to your calendar list"`
@@ -34,341 +22,3 @@ type CalendarCmd struct {
3422 OOO CalendarOOOCmd `cmd:"" name:"out-of-office" aliases:"ooo" help:"Create an Out of Office event"`
3523 WorkingLocation CalendarWorkingLocationCmd `cmd:"" name:"working-location" aliases:"wl" help:"Set working location (home/office/custom)"`
3624}
37-
38- type CalendarCalendarsCmd struct {
39- Max int64 `name:"max" aliases:"limit" help:"Max results" default:"100"`
40- Page string `name:"page" aliases:"cursor" help:"Page token"`
41- All bool `name:"all" aliases:"all-pages,allpages" help:"Fetch all pages"`
42- FailEmpty bool `name:"fail-empty" aliases:"non-empty,require-results" help:"Exit with code 3 if no results"`
43- }
44-
45- func (c * CalendarCalendarsCmd ) Run (ctx context.Context , flags * RootFlags ) error {
46- u := ui .FromContext (ctx )
47- account , err := requireAccount (flags )
48- if err != nil {
49- return err
50- }
51-
52- svc , err := newCalendarService (ctx , account )
53- if err != nil {
54- return err
55- }
56-
57- fetch := func (pageToken string ) ([]* calendar.CalendarListEntry , string , error ) {
58- call := svc .CalendarList .List ().MaxResults (c .Max )
59- if strings .TrimSpace (pageToken ) != "" {
60- call = call .PageToken (pageToken )
61- }
62- r , err := call .Do ()
63- if err != nil {
64- return nil , "" , err
65- }
66- return r .Items , r .NextPageToken , nil
67- }
68-
69- var items []* calendar.CalendarListEntry
70- nextPageToken := ""
71- if c .All {
72- all , err := collectAllPages (c .Page , fetch )
73- if err != nil {
74- return err
75- }
76- items = all
77- } else {
78- var err error
79- items , nextPageToken , err = fetch (c .Page )
80- if err != nil {
81- return err
82- }
83- }
84- if outfmt .IsJSON (ctx ) {
85- if err := outfmt .WriteJSON (ctx , os .Stdout , map [string ]any {
86- "calendars" : items ,
87- "nextPageToken" : nextPageToken ,
88- }); err != nil {
89- return err
90- }
91- if len (items ) == 0 {
92- return failEmptyExit (c .FailEmpty )
93- }
94- return nil
95- }
96- if len (items ) == 0 {
97- u .Err ().Println ("No calendars" )
98- return failEmptyExit (c .FailEmpty )
99- }
100-
101- w , flush := tableWriter (ctx )
102- defer flush ()
103- fmt .Fprintln (w , "ID\t NAME\t ROLE" )
104- for _ , cal := range items {
105- fmt .Fprintf (w , "%s\t %s\t %s\n " , cal .Id , cal .Summary , cal .AccessRole )
106- }
107- printNextPageHint (u , nextPageToken )
108- return nil
109- }
110-
111- type CalendarSubscribeCmd struct {
112- CalendarID string `arg:"" name:"calendarId" help:"Calendar ID to subscribe to (e.g., user@example.com or calendar ID)"`
113- ColorID string `name:"color-id" help:"Color ID (1-24, see 'calendar colors')"`
114- Hidden bool `name:"hidden" help:"Hide from the calendar list UI"`
115- Selected bool `name:"selected" help:"Show events in the calendar UI" default:"true" negatable:""`
116- }
117-
118- func (c * CalendarSubscribeCmd ) Run (ctx context.Context , flags * RootFlags ) error {
119- u := ui .FromContext (ctx )
120- account , err := requireAccount (flags )
121- if err != nil {
122- return err
123- }
124- calendarID := strings .TrimSpace (c .CalendarID )
125- if calendarID == "" {
126- return usage ("calendarId required" )
127- }
128- colorID , err := validateCalendarColorId (c .ColorID )
129- if err != nil {
130- return err
131- }
132-
133- svc , err := newCalendarService (ctx , account )
134- if err != nil {
135- return err
136- }
137-
138- entry := & calendar.CalendarListEntry {
139- Id : calendarID ,
140- Hidden : c .Hidden ,
141- Selected : c .Selected ,
142- }
143- if colorID != "" {
144- entry .ColorId = colorID
145- }
146-
147- added , err := svc .CalendarList .Insert (entry ).Do ()
148- if err != nil {
149- return err
150- }
151- if outfmt .IsJSON (ctx ) {
152- return outfmt .WriteJSON (ctx , os .Stdout , map [string ]any {"calendar" : added })
153- }
154- u .Out ().Printf ("subscribed\t %s" , added .Id )
155- u .Out ().Printf ("name\t %s" , added .Summary )
156- u .Out ().Printf ("role\t %s" , added .AccessRole )
157- return nil
158- }
159-
160- type CalendarAclCmd struct {
161- CalendarID string `arg:"" name:"calendarId" help:"Calendar ID"`
162- Max int64 `name:"max" aliases:"limit" help:"Max results" default:"100"`
163- Page string `name:"page" aliases:"cursor" help:"Page token"`
164- All bool `name:"all" aliases:"all-pages,allpages" help:"Fetch all pages"`
165- FailEmpty bool `name:"fail-empty" aliases:"non-empty,require-results" help:"Exit with code 3 if no results"`
166- }
167-
168- func (c * CalendarAclCmd ) Run (ctx context.Context , flags * RootFlags ) error {
169- u := ui .FromContext (ctx )
170- account , err := requireAccount (flags )
171- if err != nil {
172- return err
173- }
174- calendarID := strings .TrimSpace (c .CalendarID )
175- if calendarID == "" {
176- return usage ("calendarId required" )
177- }
178-
179- svc , err := newCalendarService (ctx , account )
180- if err != nil {
181- return err
182- }
183- calendarID , err = resolveCalendarID (ctx , svc , calendarID )
184- if err != nil {
185- return err
186- }
187-
188- fetch := func (pageToken string ) ([]* calendar.AclRule , string , error ) {
189- call := svc .Acl .List (calendarID ).MaxResults (c .Max )
190- if strings .TrimSpace (pageToken ) != "" {
191- call = call .PageToken (pageToken )
192- }
193- r , err := call .Do ()
194- if err != nil {
195- return nil , "" , err
196- }
197- return r .Items , r .NextPageToken , nil
198- }
199-
200- var items []* calendar.AclRule
201- nextPageToken := ""
202- if c .All {
203- all , err := collectAllPages (c .Page , fetch )
204- if err != nil {
205- return err
206- }
207- items = all
208- } else {
209- var err error
210- items , nextPageToken , err = fetch (c .Page )
211- if err != nil {
212- return err
213- }
214- }
215- if outfmt .IsJSON (ctx ) {
216- if err := outfmt .WriteJSON (ctx , os .Stdout , map [string ]any {
217- "rules" : items ,
218- "nextPageToken" : nextPageToken ,
219- }); err != nil {
220- return err
221- }
222- if len (items ) == 0 {
223- return failEmptyExit (c .FailEmpty )
224- }
225- return nil
226- }
227- if len (items ) == 0 {
228- u .Err ().Println ("No ACL rules" )
229- return failEmptyExit (c .FailEmpty )
230- }
231-
232- w , flush := tableWriter (ctx )
233- defer flush ()
234- fmt .Fprintln (w , "SCOPE_TYPE\t SCOPE_VALUE\t ROLE" )
235- for _ , rule := range items {
236- scopeType := ""
237- scopeValue := ""
238- if rule .Scope != nil {
239- scopeType = rule .Scope .Type
240- scopeValue = rule .Scope .Value
241- }
242- fmt .Fprintf (w , "%s\t %s\t %s\n " , scopeType , scopeValue , rule .Role )
243- }
244- printNextPageHint (u , nextPageToken )
245- return nil
246- }
247-
248- type CalendarEventsCmd struct {
249- CalendarID string `arg:"" name:"calendarId" optional:"" help:"Calendar ID (default: primary)"`
250- Cal []string `name:"cal" help:"Calendar ID or name (can be repeated)"`
251- Calendars string `name:"calendars" help:"Comma-separated calendar IDs, names, or indices from 'calendar calendars'"`
252- From string `name:"from" help:"Start time (RFC3339 with timezone, date, or relative: today, tomorrow, monday)"`
253- To string `name:"to" help:"End time (RFC3339 with timezone, date, or relative)"`
254- Today bool `name:"today" help:"Today only (timezone-aware)"`
255- Tomorrow bool `name:"tomorrow" help:"Tomorrow only (timezone-aware)"`
256- Week bool `name:"week" help:"This week (uses --week-start, default Mon)"`
257- Days int `name:"days" help:"Next N days (timezone-aware)" default:"0"`
258- WeekStart string `name:"week-start" help:"Week start day for --week (sun, mon, ...)" default:""`
259- Max int64 `name:"max" aliases:"limit" help:"Max results" default:"10"`
260- Page string `name:"page" aliases:"cursor" help:"Page token"`
261- AllPages bool `name:"all-pages" aliases:"allpages" help:"Fetch all pages"`
262- FailEmpty bool `name:"fail-empty" aliases:"non-empty,require-results" help:"Exit with code 3 if no results"`
263- Query string `name:"query" help:"Free text search"`
264- All bool `name:"all" help:"Fetch events from all calendars"`
265- PrivatePropFilter string `name:"private-prop-filter" help:"Filter by private extended property (key=value)"`
266- SharedPropFilter string `name:"shared-prop-filter" help:"Filter by shared extended property (key=value)"`
267- Fields string `name:"fields" help:"Comma-separated fields to return"`
268- Weekday bool `name:"weekday" help:"Include start/end day-of-week columns" default:"${calendar_weekday}"`
269- }
270-
271- func (c * CalendarEventsCmd ) Run (ctx context.Context , flags * RootFlags ) error {
272- account , err := requireAccount (flags )
273- if err != nil {
274- return err
275- }
276-
277- calendarID := strings .TrimSpace (c .CalendarID )
278- calInputs := append ([]string {}, c .Cal ... )
279- if strings .TrimSpace (c .Calendars ) != "" {
280- calInputs = append (calInputs , splitCSV (c .Calendars )... )
281- }
282- if c .All && (calendarID != "" || len (calInputs ) > 0 ) {
283- return usage ("calendarId or --cal/--calendars not allowed with --all flag" )
284- }
285- if calendarID != "" && len (calInputs ) > 0 {
286- return usage ("calendarId not allowed with --cal/--calendars" )
287- }
288- if ! c .All && calendarID == "" && len (calInputs ) == 0 {
289- calendarID = primaryCalendarID
290- }
291-
292- svc , err := newCalendarService (ctx , account )
293- if err != nil {
294- return err
295- }
296- if ! c .All {
297- calendarID , err = resolveCalendarID (ctx , svc , calendarID )
298- if err != nil {
299- return err
300- }
301- }
302-
303- // Use timezone-aware time resolution
304- timeRange , err := ResolveTimeRange (ctx , svc , TimeRangeFlags {
305- From : c .From ,
306- To : c .To ,
307- Today : c .Today ,
308- Tomorrow : c .Tomorrow ,
309- Week : c .Week ,
310- Days : c .Days ,
311- WeekStart : c .WeekStart ,
312- })
313- if err != nil {
314- return err
315- }
316-
317- from , to := timeRange .FormatRFC3339 ()
318-
319- if c .All {
320- return listAllCalendarsEvents (ctx , svc , from , to , c .Max , c .Page , c .AllPages , c .FailEmpty , c .Query , c .PrivatePropFilter , c .SharedPropFilter , c .Fields , c .Weekday )
321- }
322- if len (calInputs ) > 0 {
323- ids , err := resolveCalendarIDs (ctx , svc , calInputs )
324- if err != nil {
325- return err
326- }
327- if len (ids ) == 0 {
328- return usage ("no calendars specified" )
329- }
330- return listSelectedCalendarsEvents (ctx , svc , ids , from , to , c .Max , c .Page , c .AllPages , c .FailEmpty , c .Query , c .PrivatePropFilter , c .SharedPropFilter , c .Fields , c .Weekday )
331- }
332- return listCalendarEvents (ctx , svc , calendarID , from , to , c .Max , c .Page , c .AllPages , c .FailEmpty , c .Query , c .PrivatePropFilter , c .SharedPropFilter , c .Fields , c .Weekday )
333- }
334-
335- type CalendarEventCmd struct {
336- CalendarID string `arg:"" name:"calendarId" help:"Calendar ID"`
337- EventID string `arg:"" name:"eventId" help:"Event ID"`
338- }
339-
340- func (c * CalendarEventCmd ) Run (ctx context.Context , flags * RootFlags ) error {
341- u := ui .FromContext (ctx )
342- account , err := requireAccount (flags )
343- if err != nil {
344- return err
345- }
346- calendarID := strings .TrimSpace (c .CalendarID )
347- eventID := normalizeCalendarEventID (c .EventID )
348- if calendarID == "" {
349- return usage ("empty calendarId" )
350- }
351- if eventID == "" {
352- return usage ("empty eventId" )
353- }
354-
355- svc , err := newCalendarService (ctx , account )
356- if err != nil {
357- return err
358- }
359- calendarID , err = resolveCalendarID (ctx , svc , calendarID )
360- if err != nil {
361- return err
362- }
363-
364- event , err := svc .Events .Get (calendarID , eventID ).Do ()
365- if err != nil {
366- return err
367- }
368- tz , loc , _ := getCalendarLocation (ctx , svc , calendarID )
369- if outfmt .IsJSON (ctx ) {
370- return outfmt .WriteJSON (ctx , os .Stdout , map [string ]any {"event" : wrapEventWithDaysWithTimezone (event , tz , loc )})
371- }
372- printCalendarEventWithTimezone (u , event , tz , loc )
373- return nil
374- }
0 commit comments