Skip to content

Commit 0d260ac

Browse files
authored
feat(docs): add table header row pinning (#862)
1 parent 42017c5 commit 0d260ac

11 files changed

Lines changed: 236 additions & 23 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Docs: add table cell borders, padding, and vertical content alignment to `docs cell-style`. (#855) — thanks @sebsnyk.
88
- Docs: add minimum height and page-overflow controls to `docs table-row style`. (#855) — thanks @sebsnyk.
9+
- Docs: add `docs table-row pin-header --rows N` for pinning or unpinning repeated table header rows. (#855) — thanks @sebsnyk.
910
- Docs: add paragraph bullet and numbering creation/removal plus indentation, spacing, and keep controls to `docs format` and plain-text `docs write`. (#852) — thanks @sebsnyk.
1011
- Docs: add `replace-image` with exact object-ID, alt-text, single-image, tab, public-URL, and local-file targeting while preserving the original image position and bounds. (#853) — thanks @sebsnyk.
1112
- Docs: add `insert --markdown` to convert markdown to Google Docs formatting and place the converted block at a position resolved by `--index`/`--at`/`--occurrence`, giving `insert` parity with the existing `update --markdown` and `write --markdown` paths. (#851, #854) — thanks @sebsnyk.

docs/commands.generated.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,10 @@ Generated from `gog schema --json`.
278278
- [`gog docs (doc) table-column insert (add,append) <docId> [flags]`](commands/gog-docs-table-column-insert.md) - Insert a native table column
279279
- [`gog docs (doc) table-column-width (table-width,column-width) <docId> [flags]`](commands/gog-docs-table-column-width.md) - Set or reset native table column widths
280280
- [`gog docs (doc) table-merge --range=STRING <docId> [flags]`](commands/gog-docs-table-merge.md) - Merge a native table cell range
281-
- [`gog docs (doc) table-row <command>`](commands/gog-docs-table-row.md) - Insert, delete, or style native table rows
281+
- [`gog docs (doc) table-row <command>`](commands/gog-docs-table-row.md) - Insert, delete, style, or pin native table rows
282282
- [`gog docs (doc) table-row delete (rm,remove,del) --row=INT <docId> [flags]`](commands/gog-docs-table-row-delete.md) - Delete a native table row
283283
- [`gog docs (doc) table-row insert (add,append) <docId> [flags]`](commands/gog-docs-table-row-insert.md) - Insert a native table row
284+
- [`gog docs (doc) table-row pin-header --rows=INT-64 <docId> [flags]`](commands/gog-docs-table-row-pin-header.md) - Pin or unpin leading table header rows
284285
- [`gog docs (doc) table-row style <docId> [flags]`](commands/gog-docs-table-row-style.md) - Set native table row height and overflow styles
285286
- [`gog docs (doc) table-unmerge (table-split) --cell=STRING <docId> [flags]`](commands/gog-docs-table-unmerge.md) - Unmerge the region containing a native table cell
286287
- [`gog docs (doc) tables <command>`](commands/gog-docs-tables.md) - List native tables

docs/commands/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Every `gog` command has a generated docs page. The source of truth is the live CLI schema; run `make docs-commands` after changing command names, flags, help text, aliases, or arguments.
44

5-
Generated pages: 681.
5+
Generated pages: 682.
66

77
## Top-level Commands
88

@@ -329,9 +329,10 @@ Generated pages: 681.
329329
- [gog docs table-column insert](gog-docs-table-column-insert.md) - Insert a native table column
330330
- [gog docs table-column-width](gog-docs-table-column-width.md) - Set or reset native table column widths
331331
- [gog docs table-merge](gog-docs-table-merge.md) - Merge a native table cell range
332-
- [gog docs table-row](gog-docs-table-row.md) - Insert, delete, or style native table rows
332+
- [gog docs table-row](gog-docs-table-row.md) - Insert, delete, style, or pin native table rows
333333
- [gog docs table-row delete](gog-docs-table-row-delete.md) - Delete a native table row
334334
- [gog docs table-row insert](gog-docs-table-row-insert.md) - Insert a native table row
335+
- [gog docs table-row pin-header](gog-docs-table-row-pin-header.md) - Pin or unpin leading table header rows
335336
- [gog docs table-row style](gog-docs-table-row-style.md) - Set native table row height and overflow styles
336337
- [gog docs table-unmerge](gog-docs-table-unmerge.md) - Unmerge the region containing a native table cell
337338
- [gog docs tables](gog-docs-tables.md) - List native tables
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# `gog docs table-row pin-header`
2+
3+
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
4+
5+
Pin or unpin leading table header rows
6+
7+
## Usage
8+
9+
```bash
10+
gog docs (doc) table-row pin-header --rows=INT-64 <docId> [flags]
11+
```
12+
13+
## Parent
14+
15+
- [gog docs table-row](gog-docs-table-row.md)
16+
17+
## Flags
18+
19+
| Flag | Type | Default | Help |
20+
| --- | --- | --- | --- |
21+
| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) |
22+
| `-a`<br>`--account`<br>`--acct` | `string` | | Account email, alias, or auto for authenticated Google API commands |
23+
| `--client` | `string` | | OAuth client name (selects stored credentials + token bucket) |
24+
| `--color` | `string` | auto | Color output: auto\|always\|never |
25+
| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed |
26+
| `-n`<br>`--dry-run`<br>`--dryrun`<br>`--noop`<br>`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
27+
| `--enable-commands` | `string` | | Comma-separated list of enabled command prefixes; dot paths allowed (restricts CLI) |
28+
| `--enable-commands-exact` | `string` | | Comma-separated list of exact enabled commands; dot paths allowed and parent commands do not enable children |
29+
| `-y`<br>`--force`<br>`--assume-yes`<br>`--yes` | `bool` | | Skip confirmations for destructive commands |
30+
| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
31+
| `-h`<br>`--help` | `kong.helpFlag` | | Show context-sensitive help. |
32+
| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) |
33+
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
34+
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
35+
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
36+
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
37+
| `--rows` | `int64` | | Number of leading rows to pin; 0 unpins all header rows |
38+
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
39+
| `--tab` | `string` | | Target a specific tab by title or ID (see docs list-tabs) |
40+
| `--table` | `string` | 1 | Table selector: index, exact first-cell text, *, or text:VALUE for numeric/syntax-looking text |
41+
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
42+
| `--version` | `kong.VersionFlag` | | Print version and exit |
43+
| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
44+
45+
## See Also
46+
47+
- [gog docs table-row](gog-docs-table-row.md)
48+
- [Command index](README.md)

docs/commands/gog-docs-table-row.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
44
5-
Insert, delete, or style native table rows
5+
Insert, delete, style, or pin native table rows
66

77
## Usage
88

@@ -18,6 +18,7 @@ gog docs (doc) table-row <command>
1818

1919
- [gog docs table-row delete](gog-docs-table-row-delete.md) - Delete a native table row
2020
- [gog docs table-row insert](gog-docs-table-row-insert.md) - Insert a native table row
21+
- [gog docs table-row pin-header](gog-docs-table-row-pin-header.md) - Pin or unpin leading table header rows
2122
- [gog docs table-row style](gog-docs-table-row-style.md) - Set native table row height and overflow styles
2223

2324
## Flags

docs/commands/gog-docs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ gog docs (doc) <command> [flags]
5353
- [gog docs table-column](gog-docs-table-column.md) - Insert or delete native table columns
5454
- [gog docs table-column-width](gog-docs-table-column-width.md) - Set or reset native table column widths
5555
- [gog docs table-merge](gog-docs-table-merge.md) - Merge a native table cell range
56-
- [gog docs table-row](gog-docs-table-row.md) - Insert, delete, or style native table rows
56+
- [gog docs table-row](gog-docs-table-row.md) - Insert, delete, style, or pin native table rows
5757
- [gog docs table-unmerge](gog-docs-table-unmerge.md) - Unmerge the region containing a native table cell
5858
- [gog docs tables](gog-docs-tables.md) - List native tables
5959
- [gog docs tabs](gog-docs-tabs.md) - Manage Google Doc tabs

internal/cmd/docs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type DocsCmd struct {
3232
InsertTable DocsInsertTableCmd `cmd:"" name:"insert-table" help:"Insert a native table at a specific position (or end-of-doc with --at-end), optionally populated via --values-json"`
3333
CellUpdate DocsCellUpdateCmd `cmd:"" name:"cell-update" aliases:"update-cell" help:"Replace or append content inside a specific table cell"`
3434
CellStyle DocsCellStyleCmd `cmd:"" name:"cell-style" help:"Apply table cell, border, padding, alignment, and text styling"`
35-
TableRow DocsTableRowCmd `cmd:"" name:"table-row" help:"Insert, delete, or style native table rows"`
35+
TableRow DocsTableRowCmd `cmd:"" name:"table-row" help:"Insert, delete, style, or pin native table rows"`
3636
TableColumn DocsTableColumnCmd `cmd:"" name:"table-column" help:"Insert or delete native table columns"`
3737
TableMerge DocsTableMergeCmd `cmd:"" name:"table-merge" help:"Merge a native table cell range"`
3838
TableUnmerge DocsTableUnmergeCmd `cmd:"" name:"table-unmerge" aliases:"table-split" help:"Unmerge the region containing a native table cell"`

internal/cmd/docs_table_ops.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import (
1616
)
1717

1818
type DocsTableRowCmd struct {
19-
Insert DocsTableRowInsertCmd `cmd:"" name:"insert" aliases:"add,append" help:"Insert a native table row"`
20-
Delete DocsTableRowDeleteCmd `cmd:"" name:"delete" aliases:"rm,remove,del" help:"Delete a native table row"`
21-
Style DocsTableRowStyleCmd `cmd:"" name:"style" help:"Set native table row height and overflow styles"`
19+
Insert DocsTableRowInsertCmd `cmd:"" name:"insert" aliases:"add,append" help:"Insert a native table row"`
20+
Delete DocsTableRowDeleteCmd `cmd:"" name:"delete" aliases:"rm,remove,del" help:"Delete a native table row"`
21+
Style DocsTableRowStyleCmd `cmd:"" name:"style" help:"Set native table row height and overflow styles"`
22+
PinHeader DocsTablePinHeaderCmd `cmd:"" name:"pin-header" help:"Pin or unpin leading table header rows"`
2223
}
2324

2425
type DocsTableColumnCmd struct {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"google.golang.org/api/docs/v1"
8+
)
9+
10+
type DocsTablePinHeaderCmd struct {
11+
DocID string `arg:"" name:"docId" help:"Doc ID"`
12+
Table string `name:"table" help:"Table selector: index, exact first-cell text, *, or text:VALUE for numeric/syntax-looking text" default:"1"`
13+
Rows int64 `name:"rows" required:"" help:"Number of leading rows to pin; 0 unpins all header rows"`
14+
Tab string `name:"tab" help:"Target a specific tab by title or ID (see docs list-tabs)"`
15+
}
16+
17+
func (c *DocsTablePinHeaderCmd) Run(ctx context.Context, flags *RootFlags) error {
18+
docID, err := validateDocsTableMutationArgs(c.DocID, c.Table)
19+
if err != nil {
20+
return err
21+
}
22+
if c.Rows < 0 {
23+
return usage("--rows must be >= 0")
24+
}
25+
if dryRunErr := dryRunExit(ctx, flags, "docs.table-row.pin-header", map[string]any{
26+
"documentId": docID,
27+
"table": c.Table,
28+
"rows": c.Rows,
29+
"tab": c.Tab,
30+
}); dryRunErr != nil {
31+
return dryRunErr
32+
}
33+
34+
svc, loaded, selected, err := loadDocsSelectedTables(ctx, flags, docID, c.Tab, c.Table)
35+
if err != nil {
36+
return err
37+
}
38+
requests := make([]*docs.Request, 0, len(selected))
39+
results := make([]docsTableRowActionResult, 0, len(selected))
40+
for _, target := range selected {
41+
rowCount := int64(len(target.table.TableRows))
42+
if c.Rows > rowCount {
43+
return usagef("cannot pin %d header rows in table %d (table has %d rows)", c.Rows, target.index, rowCount)
44+
}
45+
requests = append(requests, &docs.Request{PinTableHeaderRows: &docs.PinTableHeaderRowsRequest{
46+
TableStartLocation: &docs.Location{Index: target.startIdx, TabId: loaded.tabID},
47+
PinnedHeaderRowsCount: c.Rows,
48+
ForceSendFields: []string{"PinnedHeaderRowsCount"},
49+
}})
50+
results = append(results, docsTableRowActionResult{TableIndex: target.index, Value: c.Rows})
51+
}
52+
if _, err := executeDocsTableRequests(ctx, svc, docID, loaded.full.RevisionId, requests); err != nil {
53+
return fmt.Errorf("pin table header rows: %w", err)
54+
}
55+
return writeDocsTableRowActionResult(ctx, docID, loaded.tabID, "pin-header", "rows", results)
56+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"strings"
7+
"testing"
8+
9+
"google.golang.org/api/docs/v1"
10+
)
11+
12+
func TestDocsTablePinHeaderZeroIsSerializedForAllTables(t *testing.T) {
13+
doc := docsTableOpsTestDocument(
14+
docsTableOpsTestElement(5, "First", 2),
15+
docsTableOpsTestElement(40, "Second", 3),
16+
)
17+
var got map[string]any
18+
docSvc, cleanup := newDocsServiceForTest(t, func(w http.ResponseWriter, r *http.Request) {
19+
w.Header().Set("Content-Type", "application/json")
20+
switch {
21+
case r.Method == http.MethodGet:
22+
_ = json.NewEncoder(w).Encode(doc)
23+
case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, ":batchUpdate"):
24+
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
25+
t.Fatalf("decode batch: %v", err)
26+
}
27+
_ = json.NewEncoder(w).Encode(&docs.BatchUpdateDocumentResponse{DocumentId: "doc1"})
28+
default:
29+
http.NotFound(w, r)
30+
}
31+
})
32+
defer cleanup()
33+
34+
cmd := &DocsTablePinHeaderCmd{}
35+
err := runKong(t, cmd, []string{"doc1", "--table", "*", "--rows", "0"}, newDocsTableOpsTestContext(t, docSvc), &RootFlags{Account: "[email protected]"})
36+
if err != nil {
37+
t.Fatalf("Run: %v", err)
38+
}
39+
writeControl, ok := got["writeControl"].(map[string]any)
40+
if !ok || writeControl["requiredRevisionId"] != "rev-1" {
41+
t.Fatalf("write control = %#v", got["writeControl"])
42+
}
43+
requests, ok := got["requests"].([]any)
44+
if !ok || len(requests) != 2 {
45+
t.Fatalf("requests = %#v", got["requests"])
46+
}
47+
wantStarts := []float64{40, 5}
48+
for i, rawRequest := range requests {
49+
request, ok := rawRequest.(map[string]any)
50+
if !ok {
51+
t.Fatalf("request %d = %#v", i, rawRequest)
52+
}
53+
pin, ok := request["pinTableHeaderRows"].(map[string]any)
54+
if !ok {
55+
t.Fatalf("pin request %d = %#v", i, request)
56+
}
57+
count, exists := pin["pinnedHeaderRowsCount"]
58+
if !exists || count != float64(0) {
59+
t.Fatalf("pinned count %d = %#v, exists=%v", i, count, exists)
60+
}
61+
location := pin["tableStartLocation"].(map[string]any)
62+
if location["index"] != wantStarts[i] {
63+
t.Fatalf("table start %d = %#v, want %v", i, location["index"], wantStarts[i])
64+
}
65+
}
66+
}
67+
68+
func TestDocsTablePinHeaderRejectsTooManyRows(t *testing.T) {
69+
doc := docsTableOpsTestDocument(docsTableOpsTestElement(5, "Header", 2))
70+
postCount := 0
71+
docSvc, cleanup := newDocsServiceForTest(t, func(w http.ResponseWriter, r *http.Request) {
72+
w.Header().Set("Content-Type", "application/json")
73+
if r.Method == http.MethodGet {
74+
_ = json.NewEncoder(w).Encode(doc)
75+
return
76+
}
77+
if r.Method == http.MethodPost {
78+
postCount++
79+
}
80+
http.NotFound(w, r)
81+
})
82+
defer cleanup()
83+
84+
cmd := &DocsTablePinHeaderCmd{}
85+
err := runKong(t, cmd, []string{"doc1", "--rows", "3"}, newDocsTableOpsTestContext(t, docSvc), &RootFlags{Account: "[email protected]"})
86+
if err == nil || !strings.Contains(err.Error(), "table has 2 rows") {
87+
t.Fatalf("expected row-count error, got %v", err)
88+
}
89+
if postCount != 0 {
90+
t.Fatalf("post count = %d, want 0", postCount)
91+
}
92+
}
93+
94+
func TestDocsTablePinHeaderRejectsNegativeRows(t *testing.T) {
95+
cmd := &DocsTablePinHeaderCmd{DocID: "doc1", Table: "1", Rows: -1}
96+
err := cmd.Run(newCmdRuntimeOutputContext(t, nil, nil), &RootFlags{Account: "[email protected]", DryRun: true})
97+
if err == nil || !strings.Contains(err.Error(), "--rows must be >= 0") {
98+
t.Fatalf("expected rows error, got %v", err)
99+
}
100+
}

0 commit comments

Comments
 (0)