gogcli/internal/cmd/slides.go
salmonumbrella 07ffcb5d84
fix(paths): expand ~ in user-provided file paths (#56)
* fix(paths): expand ~ in user-provided file paths

When users specify paths with ~ (e.g., --out ~/Downloads/file.pdf) and
the path is quoted in the shell command, the tilde is not expanded by
the shell. This caused files to be written to a literal ~/Downloads
directory instead of the user's home directory.

Add config.ExpandPath() function that expands ~ at the beginning of
paths to the user's home directory. Apply this fix to all user-provided
file paths across:

- gmail attachment download (--out)
- drive download/export (--out)
- drive upload (localPath argument)
- auth token export (--out)
- auth credentials/import/keep (input paths)
- gmail thread attachments (--out-dir)
- gmail send/drafts (--attach)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(lint): address wrapcheck and wsl issues

* fix(calendar): support ISO 8601 time format and add 'list' alias

- Add parsing for ISO 8601 datetime with numeric timezone without colon
  (e.g., 2026-01-09T16:38:41-0800), which is the format produced by
  macOS `date +%Y-%m-%dT%H:%M:%S%z`
- Add 'list' as an alias for 'events' subcommand for more intuitive CLI
  usage (gog calendar list instead of gog calendar events)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(changelog): note PR #56

* chore(lint): dedupe file string

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-01-10 02:19:30 +00:00

118 lines
3.3 KiB
Go

package cmd
import (
"context"
"errors"
"os"
"strings"
"google.golang.org/api/drive/v3"
"github.com/steipete/gogcli/internal/outfmt"
"github.com/steipete/gogcli/internal/ui"
)
type SlidesCmd struct {
Export SlidesExportCmd `cmd:"" name:"export" help:"Export a Google Slides deck (pdf|pptx)"`
Info SlidesInfoCmd `cmd:"" name:"info" help:"Get Google Slides presentation metadata"`
Create SlidesCreateCmd `cmd:"" name:"create" help:"Create a Google Slides presentation"`
Copy SlidesCopyCmd `cmd:"" name:"copy" help:"Copy a Google Slides presentation"`
}
type SlidesExportCmd struct {
PresentationID string `arg:"" name:"presentationId" help:"Presentation ID"`
Output OutputPathFlag `embed:""`
Format string `name:"format" help:"Export format: pdf|pptx" default:"pptx"`
}
func (c *SlidesExportCmd) Run(ctx context.Context, flags *RootFlags) error {
return exportViaDrive(ctx, flags, exportViaDriveOptions{
ArgName: "presentationId",
ExpectedMime: "application/vnd.google-apps.presentation",
KindLabel: "Google Slides presentation",
DefaultFormat: "pptx",
}, c.PresentationID, c.Output.Path, c.Format)
}
type SlidesInfoCmd struct {
PresentationID string `arg:"" name:"presentationId" help:"Presentation ID"`
}
func (c *SlidesInfoCmd) Run(ctx context.Context, flags *RootFlags) error {
return infoViaDrive(ctx, flags, infoViaDriveOptions{
ArgName: "presentationId",
ExpectedMime: "application/vnd.google-apps.presentation",
KindLabel: "Google Slides presentation",
}, c.PresentationID)
}
type SlidesCreateCmd struct {
Title string `arg:"" name:"title" help:"Presentation title"`
Parent string `name:"parent" help:"Destination folder ID"`
}
func (c *SlidesCreateCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx)
account, err := requireAccount(flags)
if err != nil {
return err
}
title := strings.TrimSpace(c.Title)
if title == "" {
return usage("empty title")
}
svc, err := newDriveService(ctx, account)
if err != nil {
return err
}
f := &drive.File{
Name: title,
MimeType: "application/vnd.google-apps.presentation",
}
parent := strings.TrimSpace(c.Parent)
if parent != "" {
f.Parents = []string{parent}
}
created, err := svc.Files.Create(f).
SupportsAllDrives(true).
Fields("id, name, mimeType, webViewLink").
Context(ctx).
Do()
if err != nil {
return err
}
if created == nil {
return errors.New("create failed")
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(os.Stdout, map[string]any{strFile: created})
}
u.Out().Printf("id\t%s", created.Id)
u.Out().Printf("name\t%s", created.Name)
u.Out().Printf("mime\t%s", created.MimeType)
if created.WebViewLink != "" {
u.Out().Printf("link\t%s", created.WebViewLink)
}
return nil
}
type SlidesCopyCmd struct {
PresentationID string `arg:"" name:"presentationId" help:"Presentation ID"`
Title string `arg:"" name:"title" help:"New title"`
Parent string `name:"parent" help:"Destination folder ID"`
}
func (c *SlidesCopyCmd) Run(ctx context.Context, flags *RootFlags) error {
return copyViaDrive(ctx, flags, copyViaDriveOptions{
ArgName: "presentationId",
ExpectedMime: "application/vnd.google-apps.presentation",
KindLabel: "Google Slides presentation",
}, c.PresentationID, c.Title, c.Parent)
}