gogcli/internal/cmd/docs.go
2025-12-31 19:47:32 +01:00

182 lines
4.4 KiB
Go

package cmd
import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"google.golang.org/api/drive/v3"
"github.com/steipete/gogcli/internal/outfmt"
"github.com/steipete/gogcli/internal/ui"
)
type DocsCmd struct {
Export DocsExportCmd `cmd:"" name:"export" help:"Export a Google Doc (pdf|docx|txt)"`
Info DocsInfoCmd `cmd:"" name:"info" help:"Get Google Doc metadata"`
Create DocsCreateCmd `cmd:"" name:"create" help:"Create a Google Doc"`
Copy DocsCopyCmd `cmd:"" name:"copy" help:"Copy a Google Doc"`
Cat DocsCatCmd `cmd:"" name:"cat" help:"Print a Google Doc as plain text"`
}
type DocsExportCmd struct {
DocID string `arg:"" name:"docId" help:"Doc ID"`
Out string `name:"out" help:"Output file path (default: gogcli config dir)"`
Format string `name:"format" help:"Export format: pdf|docx|txt" default:"pdf"`
}
func (c *DocsExportCmd) Run(ctx context.Context, flags *RootFlags) error {
return exportViaDrive(ctx, flags, exportViaDriveOptions{
ArgName: "docId",
ExpectedMime: "application/vnd.google-apps.document",
KindLabel: "Google Doc",
DefaultFormat: "pdf",
}, c.DocID, c.Out, c.Format)
}
type DocsInfoCmd struct {
DocID string `arg:"" name:"docId" help:"Doc ID"`
}
func (c *DocsInfoCmd) Run(ctx context.Context, flags *RootFlags) error {
return infoViaDrive(ctx, flags, infoViaDriveOptions{
ArgName: "docId",
ExpectedMime: "application/vnd.google-apps.document",
KindLabel: "Google Doc",
}, c.DocID)
}
type DocsCreateCmd struct {
Title string `arg:"" name:"title" help:"Doc title"`
Parent string `name:"parent" help:"Destination folder ID"`
}
func (c *DocsCreateCmd) 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.document",
}
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{"file": 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 DocsCopyCmd struct {
DocID string `arg:"" name:"docId" help:"Doc ID"`
Title string `arg:"" name:"title" help:"New title"`
Parent string `name:"parent" help:"Destination folder ID"`
}
func (c *DocsCopyCmd) Run(ctx context.Context, flags *RootFlags) error {
return copyViaDrive(ctx, flags, copyViaDriveOptions{
ArgName: "docId",
ExpectedMime: "application/vnd.google-apps.document",
KindLabel: "Google Doc",
}, c.DocID, c.Title, c.Parent)
}
type DocsCatCmd struct {
DocID string `arg:"" name:"docId" help:"Doc ID"`
MaxBytes int64 `name:"max-bytes" help:"Max bytes to read (0 = unlimited)" default:"2000000"`
}
func (c *DocsCatCmd) Run(ctx context.Context, flags *RootFlags) error {
account, err := requireAccount(flags)
if err != nil {
return err
}
id := strings.TrimSpace(c.DocID)
if id == "" {
return usage("empty docId")
}
svc, err := newDriveService(ctx, account)
if err != nil {
return err
}
meta, err := svc.Files.Get(id).
SupportsAllDrives(true).
Fields("id, mimeType").
Context(ctx).
Do()
if err != nil {
return err
}
if meta == nil {
return errors.New("file not found")
}
if meta.MimeType != "application/vnd.google-apps.document" {
return fmt.Errorf("file is not a Google Doc (mimeType=%q)", meta.MimeType)
}
resp, err := driveExportDownload(ctx, svc, id, "text/plain")
if err != nil {
return err
}
if resp == nil || resp.Body == nil {
return errors.New("empty response")
}
defer resp.Body.Close()
var r io.Reader = resp.Body
if c.MaxBytes > 0 {
r = io.LimitReader(resp.Body, c.MaxBytes)
}
b, err := io.ReadAll(r)
if err != nil {
return err
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(os.Stdout, map[string]any{"text": string(b)})
}
_, err = os.Stdout.Write(b)
return err
}