Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/commands/gog-docs-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ gog docs (doc) update <docId> [flags]
| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
| `--index` | `int64` | | Insert index (default: end of document) |
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
| `--markdown` | `bool` | | Convert markdown to Google Docs formatting |
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
| `--pageless` | `bool` | | Set document to pageless mode |
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
Expand Down
54 changes: 41 additions & 13 deletions internal/cmd/docs_edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ type DocsUpdateCmd struct {
Text string `name:"text" help:"Text to insert"`
File string `name:"file" help:"Text file path ('-' for stdin)"`
Index int64 `name:"index" help:"Insert index (default: end of document)"`
Markdown bool `name:"markdown" help:"Convert markdown to Google Docs formatting"`
Pageless bool `name:"pageless" help:"Set document to pageless mode"`
Tab string `name:"tab" help:"Target a specific tab by title or ID (see docs list-tabs)"`
TabID string `name:"tab-id" hidden:"" help:"(deprecated) Use --tab"`
Expand Down Expand Up @@ -565,6 +566,7 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
"document_id": id,
"written": len(text),
"index": index,
"markdown": c.Markdown,
"pageless": c.Pageless,
"tab": c.Tab,
}); dryRunErr != nil {
Expand Down Expand Up @@ -592,19 +594,37 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
c.Tab = tabID
}

reqs := []*docs.Request{{
InsertText: &docs.InsertTextRequest{
Location: &docs.Location{Index: insertIndex, TabId: c.Tab},
Text: text,
},
}}
requestCount := 0
written := len(text)
var resp *docs.BatchUpdateDocumentResponse

resp, err := svc.Documents.BatchUpdate(id, &docs.BatchUpdateDocumentRequest{Requests: reqs}).Context(ctx).Do()
if err != nil {
if isDocsNotFound(err) {
return fmt.Errorf("doc not found or not a Google Doc (id=%s)", id)
if c.Markdown {
var inserted int
requestCount, inserted, err = insertDocsMarkdownAt(ctx, svc, id, insertIndex, text, c.Tab)
if err != nil {
if isDocsNotFound(err) {
return fmt.Errorf("doc not found or not a Google Doc (id=%s)", id)
}
return err
}
written = inserted
resp = &docs.BatchUpdateDocumentResponse{DocumentId: id}
} else {
reqs := []*docs.Request{{
InsertText: &docs.InsertTextRequest{
Location: &docs.Location{Index: insertIndex, TabId: c.Tab},
Text: text,
},
}}
requestCount = len(reqs)

resp, err = svc.Documents.BatchUpdate(id, &docs.BatchUpdateDocumentRequest{Requests: reqs}).Context(ctx).Do()
if err != nil {
if isDocsNotFound(err) {
return fmt.Errorf("doc not found or not a Google Doc (id=%s)", id)
}
return err
}
return err
}
if c.Pageless {
if err := setDocumentPageless(ctx, svc, id); err != nil {
Expand All @@ -615,9 +635,13 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
if outfmt.IsJSON(ctx) {
payload := map[string]any{
"documentId": resp.DocumentId,
"requests": len(reqs),
"requests": requestCount,
"index": insertIndex,
}
if c.Markdown {
payload["written"] = written
payload["markdown"] = true
}
if c.Tab != "" {
payload["tabId"] = c.Tab
}
Expand All @@ -628,8 +652,12 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
}

u.Out().Linef("id\t%s", resp.DocumentId)
u.Out().Linef("requests\t%d", len(reqs))
u.Out().Linef("requests\t%d", requestCount)
u.Out().Linef("index\t%d", insertIndex)
if c.Markdown {
u.Out().Linef("written\t%d", written)
u.Out().Linef("markdown\ttrue")
}
if c.Tab != "" {
u.Out().Linef("tabId\t%s", c.Tab)
}
Expand Down
82 changes: 82 additions & 0 deletions internal/cmd/docs_write_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,88 @@ func TestDocsWriteUpdate_JSON(t *testing.T) {
}
}

func TestDocsUpdate_MarkdownWithTab(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })

var batchRequests [][]*docs.Request
var includeTabsCalls int

docSvc, cleanup := newDocsServiceForTest(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case r.Method == http.MethodGet && strings.HasPrefix(path, "/v1/documents/"):
if strings.Contains(r.URL.RawQuery, "includeTabsContent=true") {
includeTabsCalls++
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(tabsDocWithEndIndex())
return
case r.Method == http.MethodPost && strings.Contains(path, ":batchUpdate"):
var req docs.BatchUpdateDocumentRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Fatalf("decode request: %v", err)
}
batchRequests = append(batchRequests, req.Requests)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"documentId": "doc1"})
return
default:
http.NotFound(w, r)
return
}
}))
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }

flags := &RootFlags{Account: "a@b.com"}
ctx := newDocsJSONContext(t)

markdown := "## Heading\n\n**bold** and [link](https://example.com)\n"
if err := runKong(t, &DocsUpdateCmd{}, []string{
"doc1", "--text", markdown, "--markdown", "--tab", "Second",
}, ctx, flags); err != nil {
t.Fatalf("update markdown with tab: %v", err)
}

if includeTabsCalls != 1 {
t.Fatalf("expected 1 tab-aware GET, got %d", includeTabsCalls)
}
if len(batchRequests) != 1 {
t.Fatalf("expected 1 batch request, got %d", len(batchRequests))
}
reqs := batchRequests[0]
if len(reqs) < 2 || reqs[0].InsertText == nil {
t.Fatalf("expected markdown insert + formatting requests, got %#v", reqs)
}
insert := reqs[0].InsertText
if insert.Location.TabId != "t.second" || insert.Location.Index != 19 {
t.Fatalf("insert location = %+v, want tab t.second index 19", insert.Location)
}
if got := insert.Text; got != "\nHeading\nbold and link\n" {
t.Fatalf("inserted text = %q, want markdown-rendered text", got)
}
for i, req := range reqs[1:] {
var r *docs.Range
switch {
case req.UpdateTextStyle != nil:
r = req.UpdateTextStyle.Range
case req.UpdateParagraphStyle != nil:
r = req.UpdateParagraphStyle.Range
case req.CreateParagraphBullets != nil:
r = req.CreateParagraphBullets.Range
case req.DeleteParagraphBullets != nil:
r = req.DeleteParagraphBullets.Range
}
if r == nil {
continue
}
if r.TabId != "t.second" {
t.Fatalf("formatting request %d range tab = %q, want t.second", i+1, r.TabId)
}
}
}

func TestDocsWriteUpdate_Pageless(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
Expand Down