Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Auth: add gog zoom auth setup / doctor for Zoom S2S OAuth credential storage. (#590) — thanks @mvanhorn.
- Docs: add `gog docs insert-page-break <docId> [--index N | --at-end] [--tab=STRING]` to insert a Google Docs page break directly via `InsertPageBreakRequest` — markdown has no native page-break construct, so this is the only path for multi-page deliverables. Aliases: `page-break`, `pb`. (#604)
- Docs: add `--heading-level N` (1..6 shortcut) and `--named-style NAME` (full enum) to `gog docs format` so existing paragraphs can be promoted to `HEADING_1`..`HEADING_6`, `TITLE`, `SUBTITLE`, or `NORMAL_TEXT`. Both set `paragraphStyle.namedStyleType` on the existing UpdateParagraphStyle request and compose cleanly with `--alignment` / `--line-spacing`. (#605)
- Sheets: add `gog sheets reorder-tab <spreadsheetId> --tab=<name|sheetId> --to=N` to move a tab to a specific 0-based position via `updateSheetProperties` with field mask `index`. `--tab` accepts a title or a numeric sheet ID; `--to=0` is force-sent so the leftmost target reaches the wire as `"index":0`. Aliases: `move-tab`, `reorder-sheet`, `move-sheet`. (#603)

### Fixed

Expand Down
1 change: 1 addition & 0 deletions docs/commands.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ Generated from `gog schema --json`.
- [`gog sheets (sheet) raw <spreadsheetId> [flags]`](commands/gog-sheets-raw.md) - Dump raw Google Sheets API response as JSON (Spreadsheets.Get; lossless; for scripting and LLM consumption)
- [`gog sheets (sheet) read-format (get-format,format-read) <spreadsheetId> <range> [flags]`](commands/gog-sheets-read-format.md) - Read cell formatting from a range
- [`gog sheets (sheet) rename-tab (rename-sheet) <spreadsheetId> <oldName> <newName>`](commands/gog-sheets-rename-tab.md) - Rename a tab/sheet in a spreadsheet
- [`gog sheets (sheet) reorder-tab (move-tab,reorder-sheet,move-sheet) --tab=STRING --to=TO <spreadsheetId>`](commands/gog-sheets-reorder-tab.md) - Move a tab/sheet to a specific 0-based position in the spreadsheet
- [`gog sheets (sheet) resize-columns <spreadsheetId> <columns> [flags]`](commands/gog-sheets-resize-columns.md) - Resize sheet columns
- [`gog sheets (sheet) resize-rows <spreadsheetId> <rows> [flags]`](commands/gog-sheets-resize-rows.md) - Resize sheet rows
- [`gog sheets (sheet) table (tables) <command>`](commands/gog-sheets-table.md) - Manage Google Sheets tables
Expand Down
3 changes: 2 additions & 1 deletion docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: 565.
Generated pages: 566.

## Top-level Commands

Expand Down Expand Up @@ -550,6 +550,7 @@ Generated pages: 565.
- [gog sheets raw](gog-sheets-raw.md) - Dump raw Google Sheets API response as JSON (Spreadsheets.Get; lossless; for scripting and LLM consumption)
- [gog sheets read-format](gog-sheets-read-format.md) - Read cell formatting from a range
- [gog sheets rename-tab](gog-sheets-rename-tab.md) - Rename a tab/sheet in a spreadsheet
- [gog sheets reorder-tab](gog-sheets-reorder-tab.md) - Move a tab/sheet to a specific 0-based position in the spreadsheet
- [gog sheets resize-columns](gog-sheets-resize-columns.md) - Resize sheet columns
- [gog sheets resize-rows](gog-sheets-resize-rows.md) - Resize sheet rows
- [gog sheets table](gog-sheets-table.md) - Manage Google Sheets tables
Expand Down
45 changes: 45 additions & 0 deletions docs/commands/gog-sheets-reorder-tab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# `gog sheets reorder-tab`

> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.

Move a tab/sheet to a specific 0-based position in the spreadsheet

## Usage

```bash
gog sheets (sheet) reorder-tab (move-tab,reorder-sheet,move-sheet) --tab=STRING --to=TO <spreadsheetId>
```

## Parent

- [gog sheets](gog-sheets.md)

## Flags

| Flag | Type | Default | Help |
| --- | --- | --- | --- |
| `--access-token` | `string` | | Use provided access token directly (bypasses stored refresh tokens; token expires in ~1h) |
| `-a`<br>`--account`<br>`--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) |
| `--color` | `string` | auto | Color output: auto\|always\|never |
| `--disable-commands` | `string` | | Comma-separated list of disabled commands; dot paths allowed |
| `-n`<br>`--dry-run`<br>`--dryrun`<br>`--noop`<br>`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
| `--enable-commands` | `string` | | Comma-separated list of enabled commands; dot paths allowed (restricts CLI) |
| `-y`<br>`--force`<br>`--assume-yes`<br>`--yes` | `bool` | | Skip confirmations for destructive commands |
| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
| `-h`<br>`--help` | `kong.helpFlag` | | Show context-sensitive help. |
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
| `-p`<br>`--plain`<br>`--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`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
| `--tab` | `string` | | Target tab by name or numeric sheet ID (see sheets metadata) |
| `--to` | `*int64` | | Destination final 0-based tab index |
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
| `--version` | `kong.VersionFlag` | | Print version and exit |
| `--wrap-untrusted` | `bool` | false | In JSON/raw output, wrap fetched text fields in external untrusted-content markers |

## See Also

- [gog sheets](gog-sheets.md)
- [Command index](README.md)
1 change: 1 addition & 0 deletions docs/commands/gog-sheets.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ gog sheets (sheet) <command> [flags]
- [gog sheets raw](gog-sheets-raw.md) - Dump raw Google Sheets API response as JSON (Spreadsheets.Get; lossless; for scripting and LLM consumption)
- [gog sheets read-format](gog-sheets-read-format.md) - Read cell formatting from a range
- [gog sheets rename-tab](gog-sheets-rename-tab.md) - Rename a tab/sheet in a spreadsheet
- [gog sheets reorder-tab](gog-sheets-reorder-tab.md) - Move a tab/sheet to a specific 0-based position in the spreadsheet
- [gog sheets resize-columns](gog-sheets-resize-columns.md) - Resize sheet columns
- [gog sheets resize-rows](gog-sheets-resize-rows.md) - Resize sheet rows
- [gog sheets table](gog-sheets-table.md) - Manage Google Sheets tables
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/sheets.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type SheetsCmd struct {
AddTab SheetsAddTabCmd `cmd:"" name:"add-tab" aliases:"add-sheet" help:"Add a new tab/sheet to a spreadsheet"`
RenameTab SheetsRenameTabCmd `cmd:"" name:"rename-tab" aliases:"rename-sheet" help:"Rename a tab/sheet in a spreadsheet"`
DeleteTab SheetsDeleteTabCmd `cmd:"" name:"delete-tab" aliases:"delete-sheet" help:"Delete a tab/sheet from a spreadsheet (use --force to skip confirmation)"`
ReorderTab SheetsReorderTabCmd `cmd:"" name:"reorder-tab" aliases:"move-tab,reorder-sheet,move-sheet" help:"Move a tab/sheet to a specific 0-based position in the spreadsheet"`
}

type SheetsExportCmd struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/sheets_range_resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type spreadsheetRangeCatalog struct {

func fetchSpreadsheetRangeCatalog(ctx context.Context, svc *sheets.Service, spreadsheetID string) (*spreadsheetRangeCatalog, error) {
call := svc.Spreadsheets.Get(spreadsheetID).
Fields("sheets(properties(sheetId,title)),namedRanges(namedRangeId,name,range)")
Fields("sheets(properties(sheetId,title,index)),namedRanges(namedRangeId,name,range)")
if ctx != nil {
call = call.Context(ctx)
}
Expand Down
145 changes: 145 additions & 0 deletions internal/cmd/sheets_reorder_tab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package cmd

import (
"context"
"os"
"strconv"
"strings"

"google.golang.org/api/sheets/v4"

"github.com/steipete/gogcli/internal/outfmt"
"github.com/steipete/gogcli/internal/ui"
)

// SheetsReorderTabCmd moves a tab to a specific 0-based position in the
// spreadsheet via spreadsheets.batchUpdate -> updateSheetProperties with field
// mask `index`. Existing tab management (add/rename/delete) does not expose
// this; see #603.
type SheetsReorderTabCmd struct {
SpreadsheetID string `arg:"" name:"spreadsheetId" help:"Spreadsheet ID"`
Tab string `name:"tab" required:"" help:"Target tab by name or numeric sheet ID (see sheets metadata)"`
To *int64 `name:"to" required:"" help:"Destination final 0-based tab index"`
}

func (c *SheetsReorderTabCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx)

spreadsheetID := normalizeGoogleID(strings.TrimSpace(c.SpreadsheetID))
tab := strings.TrimSpace(c.Tab)
if spreadsheetID == "" {
return usage("empty spreadsheetId")
}
if tab == "" {
return usage("--tab is required")
}
if c.To == nil {
return usage("--to is required")
}
if *c.To < 0 {
return usage("--to must be >= 0")
}

if err := dryRunExit(ctx, flags, "sheets.reorder-tab", map[string]any{
"spreadsheet_id": spreadsheetID,
"tab": tab,
"to": *c.To,
}); err != nil {
return err
}

account, err := requireAccount(flags)
if err != nil {
return err
}

svc, err := newSheetsService(ctx, account)
if err != nil {
return err
}

target, err := resolveSheetTab(ctx, svc, spreadsheetID, tab)
if err != nil {
return err
}
if *c.To >= int64(target.Count) {
return usagef("--to must be between 0 and %d", target.Count-1)
}

apiIndex := *c.To
if *c.To > target.Index {
apiIndex++
}

props := &sheets.SheetProperties{
SheetId: target.ID,
Index: apiIndex,
ForceSendFields: []string{"Index"},
}
forceSendSheetPropertiesSheetID(props)

req := &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{
{
UpdateSheetProperties: &sheets.UpdateSheetPropertiesRequest{
Properties: props,
Fields: "index",
},
},
},
}

if _, err := svc.Spreadsheets.BatchUpdate(spreadsheetID, req).Context(ctx).Do(); err != nil {
return err
}

if outfmt.IsJSON(ctx) {
payload := map[string]any{
"spreadsheetId": spreadsheetID,
"sheetId": target.ID,
"index": *c.To,
}
if target.Title != "" {
payload["title"] = target.Title
}
return outfmt.WriteJSON(ctx, os.Stdout, payload)
}

if target.Title != "" {
u.Out().Linef("Moved tab %q (sheetId %d) to index %d in spreadsheet %s", target.Title, target.ID, *c.To, spreadsheetID)
} else {
u.Out().Linef("Moved sheetId %d to index %d in spreadsheet %s", target.ID, *c.To, spreadsheetID)
}
return nil
}

type sheetTabTarget struct {
ID int64
Title string
Index int64
Count int
}

// resolveSheetTab accepts either a tab title or a numeric sheet ID.
func resolveSheetTab(ctx context.Context, svc *sheets.Service, spreadsheetID, tab string) (sheetTabTarget, error) {
catalog, err := fetchSpreadsheetRangeCatalog(ctx, svc, spreadsheetID)
if err != nil {
return sheetTabTarget{}, err
}

for _, props := range catalog.Sheets {
if props != nil && props.Title == tab {
return sheetTabTarget{ID: props.SheetId, Title: props.Title, Index: props.Index, Count: len(catalog.Sheets)}, nil
}
}

if id, err := strconv.ParseInt(tab, 10, 64); err == nil {
for _, props := range catalog.Sheets {
if props != nil && props.SheetId == id {
return sheetTabTarget{ID: id, Title: props.Title, Index: props.Index, Count: len(catalog.Sheets)}, nil
}
}
return sheetTabTarget{}, usagef("unknown sheetId %d", id)
}
return sheetTabTarget{}, usagef("unknown tab %q", tab)
}
Loading
Loading