Skip to content

Commit 42017c5

Browse files
authored
feat(docs): add table row style controls (#861)
1 parent 42de135 commit 42017c5

10 files changed

Lines changed: 323 additions & 6 deletions

CHANGELOG.md

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

77
- Docs: add table cell borders, padding, and vertical content alignment to `docs cell-style`. (#855) — thanks @sebsnyk.
8+
- Docs: add minimum height and page-overflow controls to `docs table-row style`. (#855) — thanks @sebsnyk.
89
- 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.
910
- 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.
1011
- 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 or delete native table rows
281+
- [`gog docs (doc) table-row <command>`](commands/gog-docs-table-row.md) - Insert, delete, or style 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 style <docId> [flags]`](commands/gog-docs-table-row-style.md) - Set native table row height and overflow styles
284285
- [`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
285286
- [`gog docs (doc) tables <command>`](commands/gog-docs-tables.md) - List native tables
286287
- [`gog docs (doc) tables list (ls) <docId> [flags]`](commands/gog-docs-tables-list.md) - List native tables in document order

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: 680.
5+
Generated pages: 681.
66

77
## Top-level Commands
88

@@ -329,9 +329,10 @@ Generated pages: 680.
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 or delete native table rows
332+
- [gog docs table-row](gog-docs-table-row.md) - Insert, delete, or style 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 style](gog-docs-table-row-style.md) - Set native table row height and overflow styles
335336
- [gog docs table-unmerge](gog-docs-table-unmerge.md) - Unmerge the region containing a native table cell
336337
- [gog docs tables](gog-docs-tables.md) - List native tables
337338
- [gog docs tables list](gog-docs-tables-list.md) - List native tables in document order
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# `gog docs table-row style`
2+
3+
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
4+
5+
Set native table row height and overflow styles
6+
7+
## Usage
8+
9+
```bash
10+
gog docs (doc) table-row style <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+
| `--min-height` | `string` | | Minimum row height (points by default; supports pt, in, cm, mm) |
35+
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
36+
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
37+
| `--prevent-overflow` | `*bool` | | Keep the row within one page or column; use --no-prevent-overflow to clear |
38+
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
39+
| `--row` | `*int` | | 1-based row number; negative indexes count from the end; omit to style all rows |
40+
| `--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. |
41+
| `--tab` | `string` | | Target a specific tab by title or ID (see docs list-tabs) |
42+
| `--table` | `string` | 1 | Table selector: index, exact first-cell text, *, or text:VALUE for numeric/syntax-looking text |
43+
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
44+
| `--version` | `kong.VersionFlag` | | Print version and exit |
45+
| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |
46+
47+
## See Also
48+
49+
- [gog docs table-row](gog-docs-table-row.md)
50+
- [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 or delete native table rows
5+
Insert, delete, or style 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 style](gog-docs-table-row-style.md) - Set native table row height and overflow styles
2122

2223
## Flags
2324

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 or delete native table rows
56+
- [gog docs table-row](gog-docs-table-row.md) - Insert, delete, or style 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 or delete native table rows"`
35+
TableRow DocsTableRowCmd `cmd:"" name:"table-row" help:"Insert, delete, or style 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
type DocsTableRowCmd struct {
1919
Insert DocsTableRowInsertCmd `cmd:"" name:"insert" aliases:"add,append" help:"Insert a native table row"`
2020
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"`
2122
}
2223

2324
type DocsTableColumnCmd struct {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sort"
7+
"strings"
8+
9+
"google.golang.org/api/docs/v1"
10+
11+
"github.com/steipete/gogcli/internal/outfmt"
12+
"github.com/steipete/gogcli/internal/ui"
13+
)
14+
15+
type DocsTableRowStyleCmd struct {
16+
DocID string `arg:"" name:"docId" help:"Doc ID"`
17+
Table string `name:"table" help:"Table selector: index, exact first-cell text, *, or text:VALUE for numeric/syntax-looking text" default:"1"`
18+
Row *int `name:"row" help:"1-based row number; negative indexes count from the end; omit to style all rows"`
19+
MinHeight string `name:"min-height" help:"Minimum row height (points by default; supports pt, in, cm, mm)"`
20+
PreventOverflow *bool `name:"prevent-overflow" negatable:"" help:"Keep the row within one page or column; use --no-prevent-overflow to clear"`
21+
Tab string `name:"tab" help:"Target a specific tab by title or ID (see docs list-tabs)"`
22+
}
23+
24+
type docsTableRowStyleResult struct {
25+
TableIndex int `json:"tableIndex"`
26+
Row any `json:"row"`
27+
}
28+
29+
func (c *DocsTableRowStyleCmd) Run(ctx context.Context, flags *RootFlags) error {
30+
docID, err := validateDocsTableMutationArgs(c.DocID, c.Table)
31+
if err != nil {
32+
return err
33+
}
34+
style, fields, err := c.buildStyle()
35+
if err != nil {
36+
return err
37+
}
38+
if c.Row != nil && *c.Row == 0 {
39+
return usage("--row cannot be 0")
40+
}
41+
row := any(literalAll)
42+
if c.Row != nil {
43+
row = *c.Row
44+
}
45+
if dryRunErr := dryRunExit(ctx, flags, "docs.table-row.style", map[string]any{
46+
"documentId": docID,
47+
"table": c.Table,
48+
"row": row,
49+
"minHeight": c.MinHeight,
50+
"preventOverflow": c.PreventOverflow,
51+
"tab": c.Tab,
52+
}); dryRunErr != nil {
53+
return dryRunErr
54+
}
55+
56+
svc, loaded, selected, err := loadDocsSelectedTables(ctx, flags, docID, c.Tab, c.Table)
57+
if err != nil {
58+
return err
59+
}
60+
requests := make([]*docs.Request, 0, len(selected))
61+
results := make([]docsTableRowStyleResult, 0, len(selected))
62+
for _, target := range selected {
63+
rowIndices, resolvedRow, resolveErr := resolveDocsTableStyleRow(target.table, c.Row)
64+
if resolveErr != nil {
65+
return resolveErr
66+
}
67+
requests = append(requests, &docs.Request{UpdateTableRowStyle: &docs.UpdateTableRowStyleRequest{
68+
TableStartLocation: &docs.Location{Index: target.startIdx, TabId: loaded.tabID},
69+
RowIndices: rowIndices,
70+
TableRowStyle: style,
71+
Fields: strings.Join(fields, ","),
72+
}})
73+
results = append(results, docsTableRowStyleResult{TableIndex: target.index, Row: resolvedRow})
74+
}
75+
if _, err := executeDocsTableRequests(ctx, svc, docID, loaded.full.RevisionId, requests); err != nil {
76+
return fmt.Errorf("style table row: %w", err)
77+
}
78+
return writeDocsTableRowStyleResult(ctx, docID, loaded.tabID, results)
79+
}
80+
81+
func (c *DocsTableRowStyleCmd) buildStyle() (*docs.TableRowStyle, []string, error) {
82+
style := &docs.TableRowStyle{}
83+
fields := []string{}
84+
if raw := strings.TrimSpace(c.MinHeight); raw != "" {
85+
dimension, _, err := parseDocsDimension("min-height", raw, true)
86+
if err != nil {
87+
return nil, nil, err
88+
}
89+
style.MinRowHeight = dimension
90+
fields = append(fields, "minRowHeight")
91+
}
92+
if c.PreventOverflow != nil {
93+
style.PreventOverflow = *c.PreventOverflow
94+
style.ForceSendFields = append(style.ForceSendFields, "PreventOverflow")
95+
fields = append(fields, "preventOverflow")
96+
}
97+
if len(fields) == 0 {
98+
return nil, nil, usage("no row style flags provided")
99+
}
100+
return style, fields, nil
101+
}
102+
103+
func resolveDocsTableStyleRow(table *docs.Table, requested *int) ([]int64, any, error) {
104+
if table == nil || len(table.TableRows) == 0 {
105+
return nil, nil, usage("target table has no rows")
106+
}
107+
if requested == nil {
108+
return nil, literalAll, nil
109+
}
110+
resolved := *requested
111+
if resolved < 0 {
112+
resolved = len(table.TableRows) + resolved + 1
113+
}
114+
if resolved < 1 || resolved > len(table.TableRows) {
115+
return nil, nil, usagef("row %d out of range (table has %d rows)", *requested, len(table.TableRows))
116+
}
117+
return []int64{int64(resolved - 1)}, resolved, nil
118+
}
119+
120+
func writeDocsTableRowStyleResult(
121+
ctx context.Context,
122+
docID, tabID string,
123+
results []docsTableRowStyleResult,
124+
) error {
125+
sort.Slice(results, func(i, j int) bool {
126+
return results[i].TableIndex < results[j].TableIndex
127+
})
128+
if outfmt.IsJSON(ctx) {
129+
payload := map[string]any{
130+
"documentId": docID,
131+
"action": "style",
132+
"target": "row",
133+
"updated": true,
134+
"tables": results,
135+
}
136+
if len(results) == 1 {
137+
payload["tableIndex"] = results[0].TableIndex
138+
payload["row"] = results[0].Row
139+
}
140+
if tabID != "" {
141+
payload["tabId"] = tabID
142+
}
143+
return outfmt.WriteJSON(ctx, stdoutWriter(ctx), payload)
144+
}
145+
146+
u := ui.FromContext(ctx)
147+
u.Out().Linef("documentId\t%s", docID)
148+
u.Out().Linef("action\tstyle")
149+
u.Out().Linef("target\trow")
150+
for _, result := range results {
151+
u.Out().Linef("table\t%d\trow\t%v", result.TableIndex, result.Row)
152+
}
153+
u.Out().Linef("updated\ttrue")
154+
if tabID != "" {
155+
u.Out().Linef("tabId\t%s", tabID)
156+
}
157+
return nil
158+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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 TestDocsTableRowStyleBuildsExplicitFalseValues(t *testing.T) {
13+
falseValue := false
14+
cmd := &DocsTableRowStyleCmd{
15+
MinHeight: "0",
16+
PreventOverflow: &falseValue,
17+
}
18+
style, fields, err := cmd.buildStyle()
19+
if err != nil {
20+
t.Fatalf("buildStyle: %v", err)
21+
}
22+
if got := strings.Join(fields, ","); got != "minRowHeight,preventOverflow" {
23+
t.Fatalf("fields = %q", got)
24+
}
25+
encoded, err := json.Marshal(style)
26+
if err != nil {
27+
t.Fatalf("marshal style: %v", err)
28+
}
29+
if got := string(encoded); !strings.Contains(got, `"preventOverflow":false`) {
30+
t.Fatalf("style JSON = %s", got)
31+
}
32+
if style.MinRowHeight == nil || style.MinRowHeight.Magnitude != 0 || style.MinRowHeight.Unit != "PT" {
33+
t.Fatalf("min row height = %#v", style.MinRowHeight)
34+
}
35+
}
36+
37+
func TestResolveDocsTableStyleRow(t *testing.T) {
38+
table := docsTableOpsTestElement(5, "Header", 3).Table
39+
rows, resolved, err := resolveDocsTableStyleRow(table, nil)
40+
if err != nil || rows != nil || resolved != "all" {
41+
t.Fatalf("all rows = %#v, %#v, %v", rows, resolved, err)
42+
}
43+
last := -1
44+
rows, resolved, err = resolveDocsTableStyleRow(table, &last)
45+
if err != nil || len(rows) != 1 || rows[0] != 2 || resolved != 3 {
46+
t.Fatalf("last row = %#v, %#v, %v", rows, resolved, err)
47+
}
48+
outOfRange := 4
49+
if _, _, err := resolveDocsTableStyleRow(table, &outOfRange); err == nil || !strings.Contains(err.Error(), "out of range") {
50+
t.Fatalf("expected out-of-range error, got %v", err)
51+
}
52+
}
53+
54+
func TestDocsTableRowStyleRunUsesRevisionAndResolvedRow(t *testing.T) {
55+
doc := docsTableOpsTestDocument(docsTableOpsTestElement(5, "Header", 2))
56+
var got docs.BatchUpdateDocumentRequest
57+
docSvc, cleanup := newDocsServiceForTest(t, func(w http.ResponseWriter, r *http.Request) {
58+
w.Header().Set("Content-Type", "application/json")
59+
switch {
60+
case r.Method == http.MethodGet:
61+
_ = json.NewEncoder(w).Encode(doc)
62+
case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, ":batchUpdate"):
63+
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
64+
t.Fatalf("decode batch: %v", err)
65+
}
66+
_ = json.NewEncoder(w).Encode(&docs.BatchUpdateDocumentResponse{DocumentId: "doc1"})
67+
default:
68+
http.NotFound(w, r)
69+
}
70+
})
71+
defer cleanup()
72+
73+
cmd := &DocsTableRowStyleCmd{}
74+
err := runKong(t, cmd, []string{
75+
"doc1", "--row=-1", "--min-height", "24pt", "--prevent-overflow",
76+
}, newDocsTableOpsTestContext(t, docSvc), &RootFlags{Account: "[email protected]"})
77+
if err != nil {
78+
t.Fatalf("Run: %v", err)
79+
}
80+
if got.WriteControl == nil || got.WriteControl.RequiredRevisionId != "rev-1" {
81+
t.Fatalf("write control = %#v", got.WriteControl)
82+
}
83+
if len(got.Requests) != 1 || got.Requests[0].UpdateTableRowStyle == nil {
84+
t.Fatalf("requests = %#v", got.Requests)
85+
}
86+
request := got.Requests[0].UpdateTableRowStyle
87+
if request.TableStartLocation.Index != 5 || len(request.RowIndices) != 1 || request.RowIndices[0] != 1 {
88+
t.Fatalf("target = %#v rows=%#v", request.TableStartLocation, request.RowIndices)
89+
}
90+
if request.Fields != "minRowHeight,preventOverflow" {
91+
t.Fatalf("fields = %q", request.Fields)
92+
}
93+
if request.TableRowStyle.MinRowHeight.Magnitude != 24 || !request.TableRowStyle.PreventOverflow {
94+
t.Fatalf("style = %#v", request.TableRowStyle)
95+
}
96+
}
97+
98+
func TestDocsTableRowStyleRequiresStyleFlag(t *testing.T) {
99+
cmd := &DocsTableRowStyleCmd{DocID: "doc1", Table: "1"}
100+
err := cmd.Run(newCmdRuntimeOutputContext(t, nil, nil), &RootFlags{Account: "[email protected]", DryRun: true})
101+
if err == nil || !strings.Contains(err.Error(), "no row style flags") {
102+
t.Fatalf("expected style error, got %v", err)
103+
}
104+
}

0 commit comments

Comments
 (0)