gogcli/internal/cmd/sheets_table_clear.go
Peter Steinberger aa7c0a2f90
feat(sheets): clear table data rows
Adds header-safe table data row clearing for Google Sheets tables, including --force enforcement, footer-skip range calculation, docs, tests, and live Google smoke verification.
2026-05-04 23:14:42 +01:00

110 lines
2.8 KiB
Go

package cmd
import (
"context"
"fmt"
"os"
"strings"
"google.golang.org/api/sheets/v4"
"github.com/steipete/gogcli/internal/outfmt"
"github.com/steipete/gogcli/internal/ui"
)
type SheetsTableClearCmd struct {
SpreadsheetID string `arg:"" name:"spreadsheetId" help:"Spreadsheet ID"`
TableID string `arg:"" name:"tableId" help:"Table ID or table name"`
}
func (c *SheetsTableClearCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx)
account, err := requireAccount(flags)
if err != nil {
return err
}
spreadsheetID := normalizeGoogleID(strings.TrimSpace(c.SpreadsheetID))
in := strings.TrimSpace(c.TableID)
if spreadsheetID == "" {
return usage("empty spreadsheetId")
}
if in == "" {
return usage("empty tableId")
}
svc, err := newSheetsService(ctx, account)
if err != nil {
return err
}
tables, err := fetchSpreadsheetTables(ctx, svc, spreadsheetID)
if err != nil {
return err
}
table, found, err := resolveSheetsTable(in, tables)
if err != nil {
return err
}
if !found {
return usagef("unknown table %q", in)
}
dataRange := strings.TrimSpace(table.DataA1)
if dataRange == "" {
return fmt.Errorf("table %q has no data rows to clear", table.TableID)
}
if dryRunErr := dryRunExit(ctx, flags, "sheets.table.clear", map[string]any{
"spreadsheet_id": spreadsheetID,
"table_id": table.TableID,
"name": table.Name,
"data_range": dataRange,
"has_footer": table.HasFooter,
}); dryRunErr != nil {
return dryRunErr
}
if flags == nil || !flags.Force {
return usage("sheets table clear requires --force")
}
if confirmErr := confirmDestructiveChecked(ctx, flagsWithoutDryRun(flags), "clear data rows in table "+table.Name); confirmErr != nil {
return confirmErr
}
resp, err := svc.Spreadsheets.Values.Clear(spreadsheetID, dataRange, &sheets.ClearValuesRequest{}).Do()
if err != nil {
return err
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
"tableId": table.TableID,
"name": table.Name,
"tableRange": table.A1,
"clearedRange": resp.ClearedRange,
"hasFooter": table.HasFooter,
})
}
u.Out().Printf("Cleared data rows in %s", resp.ClearedRange)
return nil
}
func sheetsTableHasFooter(table *sheets.Table) bool {
return table != nil && table.RowsProperties != nil && table.RowsProperties.FooterColorStyle != nil
}
func sheetsTableDataRangeA1(sheetTitle string, table *sheets.Table) (string, bool) {
if table == nil || table.Range == nil {
return "", false
}
dataRange := *table.Range
dataRange.StartRowIndex++
if sheetsTableHasFooter(table) {
dataRange.EndRowIndex--
}
if dataRange.EndRowIndex <= dataRange.StartRowIndex {
return "", false
}
return gridRangeToA1(sheetTitle, &dataRange), true
}