diff --git a/CHANGELOG.md b/CHANGELOG.md index 82fba0fc..b19f28ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.20.1 - Unreleased +### Added + +- Docs: add `docs table-column-width` to set fixed native table column widths or reset columns to evenly distributed sizing. (#631) — thanks @sebsnyk. + ### Fixed ## 0.20.0 - 2026-05-30 diff --git a/docs/commands.generated.md b/docs/commands.generated.md index fcf26553..491b3cdf 100644 --- a/docs/commands.generated.md +++ b/docs/commands.generated.md @@ -253,6 +253,7 @@ Generated from `gog schema --json`. - [`gog docs (doc) rename-tab [flags]`](commands/gog-docs-rename-tab.md) - Rename a tab in a Google Doc - [`gog docs (doc) sed [] [flags]`](commands/gog-docs-sed.md) - Regex find/replace (sed-style: s/pattern/replacement/g) - [`gog docs (doc) structure (struct) [flags]`](commands/gog-docs-structure.md) - Show document structure with numbered paragraphs + - [`gog docs (doc) table-column-width (table-width,column-width) [flags]`](commands/gog-docs-table-column-width.md) - Set or reset native table column widths - [`gog docs (doc) tabs `](commands/gog-docs-tabs.md) - Manage Google Doc tabs - [`gog docs (doc) tabs add (create,new) [flags]`](commands/gog-docs-tabs-add.md) - Add a tab to a Google Doc - [`gog docs (doc) tabs delete (rm,remove,del) [flags]`](commands/gog-docs-tabs-delete.md) - Delete a tab from a Google Doc diff --git a/docs/commands/README.md b/docs/commands/README.md index f423ef52..3c0194ee 100644 --- a/docs/commands/README.md +++ b/docs/commands/README.md @@ -2,7 +2,7 @@ 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. -Generated pages: 581. +Generated pages: 582. ## Top-level Commands @@ -304,6 +304,7 @@ Generated pages: 581. - [gog docs rename-tab](gog-docs-rename-tab.md) - Rename a tab in a Google Doc - [gog docs sed](gog-docs-sed.md) - Regex find/replace (sed-style: s/pattern/replacement/g) - [gog docs structure](gog-docs-structure.md) - Show document structure with numbered paragraphs + - [gog docs table-column-width](gog-docs-table-column-width.md) - Set or reset native table column widths - [gog docs tabs](gog-docs-tabs.md) - Manage Google Doc tabs - [gog docs tabs add](gog-docs-tabs-add.md) - Add a tab to a Google Doc - [gog docs tabs delete](gog-docs-tabs-delete.md) - Delete a tab from a Google Doc diff --git a/docs/commands/gog-docs-table-column-width.md b/docs/commands/gog-docs-table-column-width.md new file mode 100644 index 00000000..81fce550 --- /dev/null +++ b/docs/commands/gog-docs-table-column-width.md @@ -0,0 +1,50 @@ +# `gog docs table-column-width` + +> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`. + +Set or reset native table column widths + +## Usage + +```bash +gog docs (doc) table-column-width (table-width,column-width) [flags] +``` + +## Parent + +- [gog docs](gog-docs.md) + +## Flags + +| Flag | Type | Default | Help | +| --- | --- | --- | --- | +| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) | +| `-a`
`--account`
`--acct` | `string` | | Account email for API commands (gmail/calendar/chat/classroom/drive/drivelabels/docs/slides/contacts/tasks/people/sheets/forms/sites/appscript/analytics/searchconsole/ads/photos) | +| `--client` | `string` | | OAuth client name (selects stored credentials + token bucket) | +| `--col` | `int` | | 1-based column number. Omit with --evenly-distributed to reset all columns. | +| `--color` | `string` | auto | Color output: auto\|always\|never | +| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed | +| `-n`
`--dry-run`
`--dryrun`
`--noop`
`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully | +| `--enable-commands` | `string` | | Comma-separated list of enabled command prefixes; dot paths allowed (restricts CLI) | +| `--enable-commands-exact` | `string` | | Comma-separated list of exact enabled commands; dot paths allowed and parent commands do not enable children | +| `--evenly-distributed`
`--even` | `bool` | | Reset selected column, or all columns when --col is omitted, to Docs-managed equal width | +| `-y`
`--force`
`--assume-yes`
`--yes` | `bool` | | Skip confirmations for destructive commands | +| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) | +| `-h`
`--help` | `kong.helpFlag` | | Show context-sensitive help. | +| `--home` | `string` | | Override gogcli config/data/state/cache root (equivalent to GOG_HOME) | +| `-j`
`--json`
`--machine` | `bool` | false | Output JSON to stdout (best for scripting) | +| `--no-input`
`--non-interactive`
`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) | +| `-p`
`--plain`
`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) | +| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) | +| `--select`
`--pick`
`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. | +| `--tab` | `string` | | Target a specific tab by title or ID (see docs list-tabs) | +| `--table-index` | `int` | 1 | 1-based table index in document order; negative indexes count from the end | +| `-v`
`--verbose` | `bool` | | Enable verbose logging | +| `--version` | `kong.VersionFlag` | | Print version and exit | +| `--width` | `float64` | | Fixed column width in points (minimum 5pt) | +| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers | + +## See Also + +- [gog docs](gog-docs.md) +- [Command index](README.md) diff --git a/docs/commands/gog-docs.md b/docs/commands/gog-docs.md index 164af9e6..5dc7a00e 100644 --- a/docs/commands/gog-docs.md +++ b/docs/commands/gog-docs.md @@ -44,6 +44,7 @@ gog docs (doc) [flags] - [gog docs rename-tab](gog-docs-rename-tab.md) - Rename a tab in a Google Doc - [gog docs sed](gog-docs-sed.md) - Regex find/replace (sed-style: s/pattern/replacement/g) - [gog docs structure](gog-docs-structure.md) - Show document structure with numbered paragraphs +- [gog docs table-column-width](gog-docs-table-column-width.md) - Set or reset native table column widths - [gog docs tabs](gog-docs-tabs.md) - Manage Google Doc tabs - [gog docs update](gog-docs-update.md) - Insert or replace text at a specific index or range in a Google Doc - [gog docs write](gog-docs-write.md) - Write content to a Google Doc diff --git a/docs/docs-editing.md b/docs/docs-editing.md index cbe0e891..2ab22ce0 100644 --- a/docs/docs-editing.md +++ b/docs/docs-editing.md @@ -128,10 +128,21 @@ gog docs cell-update --table-index 1 --row 2 --col 3 \ Coordinates are 1-based. `--tab` targets a specific tab, and `--append` inserts at the end of the cell instead of replacing its current content. +Set or reset native table column widths after inserting or importing tables: + +```bash +gog docs table-column-width --table-index 1 --col 1 --width 120 +gog docs table-column-width --table-index 1 --evenly-distributed +``` + +`--width` uses points and requires `--col`. `--evenly-distributed` resets one +column when `--col` is supplied, or all columns when it is omitted. + Command page: - [`gog docs insert-table`](commands/gog-docs-insert-table.md) - [`gog docs cell-update`](commands/gog-docs-cell-update.md) +- [`gog docs table-column-width`](commands/gog-docs-table-column-width.md) ## Tabs diff --git a/internal/cmd/docs.go b/internal/cmd/docs.go index 14a154d3..42e0da97 100644 --- a/internal/cmd/docs.go +++ b/internal/cmd/docs.go @@ -18,37 +18,38 @@ import ( var newDocsService = googleapi.NewDocs type DocsCmd struct { - Export DocsExportCmd `cmd:"" name:"export" aliases:"download,dl" help:"Export a Google Doc (pdf|docx|txt|md|html)"` - Info DocsInfoCmd `cmd:"" name:"info" aliases:"get,show" help:"Get Google Doc metadata"` - Create DocsCreateCmd `cmd:"" name:"create" aliases:"add,new" help:"Create a Google Doc"` - Copy DocsCopyCmd `cmd:"" name:"copy" aliases:"cp,duplicate" help:"Copy a Google Doc"` - Cat DocsCatCmd `cmd:"" name:"cat" aliases:"text,read" help:"Print a Google Doc as plain text"` - Comments DocsCommentsCmd `cmd:"" name:"comments" help:"Manage comments on files"` - Tabs DocsTabsCmd `cmd:"" name:"tabs" help:"Manage Google Doc tabs"` - AddTab DocsAddTabCmd `cmd:"" name:"add-tab" help:"Add a tab to a Google Doc"` - RenameTab DocsRenameTabCmd `cmd:"" name:"rename-tab" help:"Rename a tab in a Google Doc"` - DeleteTab DocsDeleteTabCmd `cmd:"" name:"delete-tab" help:"Delete a tab from a Google Doc"` - ListTabs DocsListTabsCmd `cmd:"" name:"list-tabs" help:"List all tabs in a Google Doc"` - Write DocsWriteCmd `cmd:"" name:"write" help:"Write content to a Google Doc"` - Insert DocsInsertCmd `cmd:"" name:"insert" help:"Insert text at a specific position"` - 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"` - CellUpdate DocsCellUpdateCmd `cmd:"" name:"cell-update" aliases:"update-cell" help:"Replace or append content inside a specific table cell"` - CellStyle DocsCellStyleCmd `cmd:"" name:"cell-style" help:"Apply table cell background and text styling"` - InsertImage DocsInsertImageCmd `cmd:"" name:"insert-image" help:"Upload a local image and insert it into a Google Doc"` - InsertPerson DocsInsertPersonCmd `cmd:"" name:"insert-person" help:"Insert a native person smart chip"` - InsertFileChip DocsInsertFileChipCmd `cmd:"" name:"insert-file-chip" aliases:"insert-rich-link" help:"Insert a native Drive file smart chip"` - InsertDateChip DocsInsertDateChipCmd `cmd:"" name:"insert-date-chip" help:"Insert a native date smart chip"` - InsertPageBreak DocsInsertPageBreakCmd `cmd:"" name:"insert-page-break" aliases:"page-break,pb" help:"Insert a page break at a specific position (or end-of-doc with --at-end)"` - Delete DocsDeleteCmd `cmd:"" name:"delete" help:"Delete text range from document"` - FindReplace DocsFindReplaceCmd `cmd:"" name:"find-replace" help:"Find and replace text. Supports plain text or markdown with images; use --first for a single occurrence."` - Update DocsUpdateCmd `cmd:"" name:"update" help:"Insert or replace text at a specific index or range in a Google Doc"` - Edit DocsEditCmd `cmd:"" name:"edit" help:"Find and replace text in a Google Doc"` - Format DocsFormatCmd `cmd:"" name:"format" help:"Apply text or paragraph formatting to a Google Doc"` - Sed DocsSedCmd `cmd:"" name:"sed" help:"Regex find/replace (sed-style: s/pattern/replacement/g)"` - Clear DocsClearCmd `cmd:"" name:"clear" help:"Clear all content from a Google Doc"` - Structure DocsStructureCmd `cmd:"" name:"structure" aliases:"struct" help:"Show document structure with numbered paragraphs"` - Raw DocsRawCmd `cmd:"" name:"raw" help:"Dump raw Google Docs API response as JSON (Documents.Get; lossless; for scripting and LLM consumption)"` - PageLayout DocsPageLayoutCmd `cmd:"" name:"page-layout" aliases:"set-page-layout,page-setup" help:"Set page layout (pageless|pages) on an existing Google Doc"` + Export DocsExportCmd `cmd:"" name:"export" aliases:"download,dl" help:"Export a Google Doc (pdf|docx|txt|md|html)"` + Info DocsInfoCmd `cmd:"" name:"info" aliases:"get,show" help:"Get Google Doc metadata"` + Create DocsCreateCmd `cmd:"" name:"create" aliases:"add,new" help:"Create a Google Doc"` + Copy DocsCopyCmd `cmd:"" name:"copy" aliases:"cp,duplicate" help:"Copy a Google Doc"` + Cat DocsCatCmd `cmd:"" name:"cat" aliases:"text,read" help:"Print a Google Doc as plain text"` + Comments DocsCommentsCmd `cmd:"" name:"comments" help:"Manage comments on files"` + Tabs DocsTabsCmd `cmd:"" name:"tabs" help:"Manage Google Doc tabs"` + AddTab DocsAddTabCmd `cmd:"" name:"add-tab" help:"Add a tab to a Google Doc"` + RenameTab DocsRenameTabCmd `cmd:"" name:"rename-tab" help:"Rename a tab in a Google Doc"` + DeleteTab DocsDeleteTabCmd `cmd:"" name:"delete-tab" help:"Delete a tab from a Google Doc"` + ListTabs DocsListTabsCmd `cmd:"" name:"list-tabs" help:"List all tabs in a Google Doc"` + Write DocsWriteCmd `cmd:"" name:"write" help:"Write content to a Google Doc"` + Insert DocsInsertCmd `cmd:"" name:"insert" help:"Insert text at a specific position"` + 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"` + CellUpdate DocsCellUpdateCmd `cmd:"" name:"cell-update" aliases:"update-cell" help:"Replace or append content inside a specific table cell"` + CellStyle DocsCellStyleCmd `cmd:"" name:"cell-style" help:"Apply table cell background and text styling"` + TableColumnWidth DocsTableColumnWidthCmd `cmd:"" name:"table-column-width" aliases:"table-width,column-width" help:"Set or reset native table column widths"` + InsertImage DocsInsertImageCmd `cmd:"" name:"insert-image" help:"Upload a local image and insert it into a Google Doc"` + InsertPerson DocsInsertPersonCmd `cmd:"" name:"insert-person" help:"Insert a native person smart chip"` + InsertFileChip DocsInsertFileChipCmd `cmd:"" name:"insert-file-chip" aliases:"insert-rich-link" help:"Insert a native Drive file smart chip"` + InsertDateChip DocsInsertDateChipCmd `cmd:"" name:"insert-date-chip" help:"Insert a native date smart chip"` + InsertPageBreak DocsInsertPageBreakCmd `cmd:"" name:"insert-page-break" aliases:"page-break,pb" help:"Insert a page break at a specific position (or end-of-doc with --at-end)"` + Delete DocsDeleteCmd `cmd:"" name:"delete" help:"Delete text range from document"` + FindReplace DocsFindReplaceCmd `cmd:"" name:"find-replace" help:"Find and replace text. Supports plain text or markdown with images; use --first for a single occurrence."` + Update DocsUpdateCmd `cmd:"" name:"update" help:"Insert or replace text at a specific index or range in a Google Doc"` + Edit DocsEditCmd `cmd:"" name:"edit" help:"Find and replace text in a Google Doc"` + Format DocsFormatCmd `cmd:"" name:"format" help:"Apply text or paragraph formatting to a Google Doc"` + Sed DocsSedCmd `cmd:"" name:"sed" help:"Regex find/replace (sed-style: s/pattern/replacement/g)"` + Clear DocsClearCmd `cmd:"" name:"clear" help:"Clear all content from a Google Doc"` + Structure DocsStructureCmd `cmd:"" name:"structure" aliases:"struct" help:"Show document structure with numbered paragraphs"` + Raw DocsRawCmd `cmd:"" name:"raw" help:"Dump raw Google Docs API response as JSON (Documents.Get; lossless; for scripting and LLM consumption)"` + PageLayout DocsPageLayoutCmd `cmd:"" name:"page-layout" aliases:"set-page-layout,page-setup" help:"Set page layout (pageless|pages) on an existing Google Doc"` } type DocsTabsCmd struct { diff --git a/internal/cmd/docs_remaining_features_test.go b/internal/cmd/docs_remaining_features_test.go index df9e1882..b96d5ea8 100644 --- a/internal/cmd/docs_remaining_features_test.go +++ b/internal/cmd/docs_remaining_features_test.go @@ -78,6 +78,86 @@ func TestDocsCellStyleBuildsTableAndTextRequests(t *testing.T) { } } +func TestDocsTableColumnWidthBuildsFixedRequest(t *testing.T) { + cmd := &DocsTableColumnWidthCmd{Col: 2, Width: 120} + req, err := cmd.buildRequest(5, 3, "tab-1") + if err != nil { + t.Fatalf("buildRequest: %v", err) + } + got := req.UpdateTableColumnProperties + if got == nil { + t.Fatalf("missing update request: %#v", req) + } + if got.TableStartLocation.Index != 5 || got.TableStartLocation.TabId != "tab-1" { + t.Fatalf("table start = %#v", got.TableStartLocation) + } + if len(got.ColumnIndices) != 1 || got.ColumnIndices[0] != 1 { + t.Fatalf("column indices = %#v", got.ColumnIndices) + } + if got.Fields != "width,widthType" { + t.Fatalf("fields = %q", got.Fields) + } + props := got.TableColumnProperties + if props.WidthType != "FIXED_WIDTH" || props.Width == nil || props.Width.Magnitude != 120 || props.Width.Unit != "PT" { + t.Fatalf("properties = %#v", props) + } +} + +func TestDocsTableColumnWidthBuildsEvenAllColumnsRequest(t *testing.T) { + cmd := &DocsTableColumnWidthCmd{EvenlyDistributed: true} + req, err := cmd.buildRequest(7, 2, "") + if err != nil { + t.Fatalf("buildRequest: %v", err) + } + got := req.UpdateTableColumnProperties + if got == nil { + t.Fatalf("missing update request: %#v", req) + } + if len(got.ColumnIndices) != 0 { + t.Fatalf("column indices = %#v", got.ColumnIndices) + } + if got.Fields != "widthType" || got.TableColumnProperties.WidthType != "EVENLY_DISTRIBUTED" { + t.Fatalf("unexpected request: %#v", got) + } +} + +func TestDocsTableColumnWidthValidation(t *testing.T) { + tests := []struct { + name string + cmd DocsTableColumnWidthCmd + want string + }{ + { + name: "missing mode", + cmd: DocsTableColumnWidthCmd{TableIndex: 1, Col: 1}, + want: "set --width or --evenly-distributed", + }, + { + name: "conflicting mode", + cmd: DocsTableColumnWidthCmd{TableIndex: 1, Col: 1, Width: 120, EvenlyDistributed: true}, + want: "mutually exclusive", + }, + { + name: "fixed requires column", + cmd: DocsTableColumnWidthCmd{TableIndex: 1, Width: 120}, + want: "--col is required", + }, + { + name: "minimum width", + cmd: DocsTableColumnWidthCmd{TableIndex: 1, Col: 1, Width: 4.9}, + want: "--width must be >= 5pt", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cmd.validate() + if err == nil || !strings.Contains(err.Error(), tt.want) { + t.Fatalf("expected %q error, got %v", tt.want, err) + } + }) + } +} + func TestDocsSmartChipCommandsBuildRequests(t *testing.T) { person := &docs.Request{InsertPerson: &docs.InsertPersonRequest{PersonProperties: &docs.PersonProperties{Email: "a@example.com"}}} setDocsInsertRequestLocation(person, 7, "tab-1") diff --git a/internal/cmd/docs_table_column_width.go b/internal/cmd/docs_table_column_width.go new file mode 100644 index 00000000..ff8f8566 --- /dev/null +++ b/internal/cmd/docs_table_column_width.go @@ -0,0 +1,200 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "strings" + + "google.golang.org/api/docs/v1" + + "github.com/steipete/gogcli/internal/outfmt" + "github.com/steipete/gogcli/internal/ui" +) + +type DocsTableColumnWidthCmd struct { + DocID string `arg:"" name:"docId" help:"Doc ID"` + TableIndex int `name:"table-index" help:"1-based table index in document order; negative indexes count from the end" default:"1"` + Col int `name:"col" help:"1-based column number. Omit with --evenly-distributed to reset all columns."` + Width float64 `name:"width" help:"Fixed column width in points (minimum 5pt)"` + EvenlyDistributed bool `name:"evenly-distributed" aliases:"even" help:"Reset selected column, or all columns when --col is omitted, to Docs-managed equal width"` + Tab string `name:"tab" help:"Target a specific tab by title or ID (see docs list-tabs)"` +} + +func (c *DocsTableColumnWidthCmd) Run(ctx context.Context, flags *RootFlags) error { + u := ui.FromContext(ctx) + docID := strings.TrimSpace(c.DocID) + if docID == "" { + return usage("empty docId") + } + if err := c.validate(); err != nil { + return err + } + dryRunPayload := map[string]any{ + "documentId": docID, + "tableIndex": c.TableIndex, + "evenlyDistributed": c.EvenlyDistributed, + "tab": c.Tab, + } + if c.Col > 0 { + dryRunPayload["col"] = c.Col + } else { + dryRunPayload["allColumns"] = true + } + if c.Width > 0 { + dryRunPayload["width"] = c.Width + } + if dryRunErr := dryRunExit(ctx, flags, "docs.table-column-width", dryRunPayload); dryRunErr != nil { + return dryRunErr + } + + svc, err := requireDocsService(ctx, flags) + if err != nil { + return err + } + loaded, err := loadDocsTargetDocument(ctx, svc, docID, c.Tab) + if err != nil { + return err + } + c.Tab = loaded.tabID + + table, resolvedTableIndex, err := resolveDocsTableWithIndex(loaded.target, c.TableIndex) + if err != nil { + return err + } + req, err := c.buildRequest(table.startIdx, docsTableColumnCount(table.table), c.Tab) + if err != nil { + return err + } + resp, err := svc.Documents.BatchUpdate(docID, &docs.BatchUpdateDocumentRequest{ + WriteControl: &docs.WriteControl{RequiredRevisionId: loaded.full.RevisionId}, + Requests: []*docs.Request{req}, + }).Context(ctx).Do() + if err != nil { + if isDocsNotFound(err) { + return fmt.Errorf("doc not found or not a Google Doc (id=%s)", docID) + } + return fmt.Errorf("table column width: %w", err) + } + + widthType := req.UpdateTableColumnProperties.TableColumnProperties.WidthType + if outfmt.IsJSON(ctx) { + payload := map[string]any{ + "documentId": resp.DocumentId, + "tableIndex": resolvedTableIndex, + "widthType": widthType, + "updated": true, + } + if c.Col > 0 { + payload["col"] = c.Col + } else { + payload["allColumns"] = true + } + if c.Width > 0 { + payload["width"] = c.Width + } + if c.Tab != "" { + payload["tabId"] = c.Tab + } + return outfmt.WriteJSON(ctx, os.Stdout, payload) + } + u.Out().Linef("documentId\t%s", resp.DocumentId) + u.Out().Linef("table_index\t%d", resolvedTableIndex) + if c.Col > 0 { + u.Out().Linef("col\t%d", c.Col) + } else { + u.Out().Linef("all_columns\ttrue") + } + u.Out().Linef("width_type\t%s", widthType) + if c.Width > 0 { + u.Out().Linef("width\t%.3f", c.Width) + } + u.Out().Linef("updated\ttrue") + if c.Tab != "" { + u.Out().Linef("tabId\t%s", c.Tab) + } + return nil +} + +func (c *DocsTableColumnWidthCmd) validate() error { + if c.TableIndex == 0 { + return usage("--table-index cannot be 0") + } + if c.Col < 0 { + return usage("--col must be >= 1") + } + if c.EvenlyDistributed { + if c.Width != 0 { + return usage("--width and --evenly-distributed are mutually exclusive") + } + return nil + } + if c.Width == 0 { + return usage("set --width or --evenly-distributed") + } + if c.Width < 5 { + return usage("--width must be >= 5pt") + } + if c.Col < 1 { + return usage("--col is required when setting --width") + } + return nil +} + +func (c *DocsTableColumnWidthCmd) buildRequest(tableStart int64, colCount int, tabID string) (*docs.Request, error) { + if colCount < 1 { + return nil, fmt.Errorf("target table has no columns") + } + var columnIndices []int64 + if c.Col > 0 { + if c.Col > colCount { + return nil, fmt.Errorf("col %d out of range (table has %d columns)", c.Col, colCount) + } + columnIndices = []int64{int64(c.Col - 1)} + } + + props := &docs.TableColumnProperties{} + fields := "widthType" + if c.EvenlyDistributed { + props.WidthType = "EVENLY_DISTRIBUTED" + } else { + props.WidthType = "FIXED_WIDTH" + props.Width = &docs.Dimension{Magnitude: c.Width, Unit: "PT"} + fields = "width,widthType" + } + + return &docs.Request{UpdateTableColumnProperties: &docs.UpdateTableColumnPropertiesRequest{ + TableStartLocation: &docs.Location{Index: tableStart, TabId: tabID}, + ColumnIndices: columnIndices, + TableColumnProperties: props, + Fields: fields, + }}, nil +} + +func resolveDocsTableWithIndex(doc *docs.Document, requested int) (tableWithIndex, int, error) { + tables := collectAllTablesWithIndex(doc) + if len(tables) == 0 { + return tableWithIndex{}, 0, fmt.Errorf("document has no tables") + } + idx := requested + if idx < 0 { + idx = len(tables) + idx + 1 + } + if idx < 1 || idx > len(tables) { + return tableWithIndex{}, 0, fmt.Errorf("table %d out of range (document has %d tables)", requested, len(tables)) + } + return tables[idx-1], idx, nil +} + +func docsTableColumnCount(table *docs.Table) int { + if table == nil { + return 0 + } + if table.Columns > 0 { + return int(table.Columns) + } + if len(table.TableRows) > 0 { + return len(table.TableRows[0].TableCells) + } + return 0 +}