* 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>
95 lines
1.9 KiB
Go
95 lines
1.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"google.golang.org/api/drive/v3"
|
|
|
|
"github.com/steipete/gogcli/internal/outfmt"
|
|
"github.com/steipete/gogcli/internal/ui"
|
|
)
|
|
|
|
type copyViaDriveOptions struct {
|
|
ArgName string
|
|
ExpectedMime string
|
|
KindLabel string
|
|
}
|
|
|
|
func copyViaDrive(ctx context.Context, flags *RootFlags, opts copyViaDriveOptions, id string, name string, parent string) error {
|
|
u := ui.FromContext(ctx)
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
argName := strings.TrimSpace(opts.ArgName)
|
|
if argName == "" {
|
|
argName = "id"
|
|
}
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
return usage(fmt.Sprintf("empty %s", argName))
|
|
}
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return usage("empty name")
|
|
}
|
|
|
|
svc, err := newDriveService(ctx, account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
meta, err := svc.Files.Get(id).
|
|
SupportsAllDrives(true).
|
|
Fields("id, name, mimeType").
|
|
Context(ctx).
|
|
Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if meta == nil {
|
|
return errors.New("file not found")
|
|
}
|
|
if opts.ExpectedMime != "" && meta.MimeType != opts.ExpectedMime {
|
|
label := strings.TrimSpace(opts.KindLabel)
|
|
if label == "" {
|
|
label = "expected type"
|
|
}
|
|
return fmt.Errorf("file is not a %s (mimeType=%q)", label, meta.MimeType)
|
|
}
|
|
|
|
parent = strings.TrimSpace(parent)
|
|
req := &drive.File{Name: name}
|
|
if parent != "" {
|
|
req.Parents = []string{parent}
|
|
}
|
|
|
|
created, err := svc.Files.Copy(id, req).
|
|
SupportsAllDrives(true).
|
|
Fields("id, name, mimeType, webViewLink").
|
|
Context(ctx).
|
|
Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if created == nil {
|
|
return errors.New("copy 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
|
|
}
|