From 074c4a50429c1ea375daecb95ae76292a668231a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Mar 2026 03:33:30 +0000 Subject: [PATCH] refactor(sheets): extract dimension span parsing --- internal/cmd/sheets_dimension_span.go | 85 +++++++++++++++++++++++++++ internal/cmd/sheets_resize.go | 79 ------------------------- internal/cmd/sheets_resize_test.go | 42 +++++++++++++ 3 files changed, 127 insertions(+), 79 deletions(-) create mode 100644 internal/cmd/sheets_dimension_span.go diff --git a/internal/cmd/sheets_dimension_span.go b/internal/cmd/sheets_dimension_span.go new file mode 100644 index 0000000..212f8b5 --- /dev/null +++ b/internal/cmd/sheets_dimension_span.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +var ( + columnsRangeRe = regexp.MustCompile(`^([A-Za-z]+)(?::([A-Za-z]+))?$`) + rowsRangeRe = regexp.MustCompile(`^([0-9]+)(?::([0-9]+))?$`) +) + +type dimensionSpan struct { + SheetName string + StartIndex int64 + EndIndex int64 +} + +func parseColumnsSpan(spec, label string) (dimensionSpan, error) { + sheetName, part, err := splitA1Sheet(strings.TrimSpace(spec)) + if err != nil { + return dimensionSpan{}, fmt.Errorf("parse %s range: %w", label, err) + } + part = strings.ReplaceAll(strings.TrimSpace(part), "$", "") + m := columnsRangeRe.FindStringSubmatch(part) + if m == nil { + return dimensionSpan{}, fmt.Errorf("invalid %s range %q (expected A:C or Sheet!A:C)", label, spec) + } + + startCol, err := colLettersToIndex(m[1]) + if err != nil { + return dimensionSpan{}, err + } + endCol := startCol + if m[2] != "" { + endCol, err = colLettersToIndex(m[2]) + if err != nil { + return dimensionSpan{}, err + } + } + if endCol < startCol { + startCol, endCol = endCol, startCol + } + + return dimensionSpan{ + SheetName: sheetName, + StartIndex: int64(startCol - 1), + EndIndex: int64(endCol), + }, nil +} + +func parseRowsSpan(spec, label string) (dimensionSpan, error) { + sheetName, part, err := splitA1Sheet(strings.TrimSpace(spec)) + if err != nil { + return dimensionSpan{}, fmt.Errorf("parse %s range: %w", label, err) + } + part = strings.ReplaceAll(strings.TrimSpace(part), "$", "") + m := rowsRangeRe.FindStringSubmatch(part) + if m == nil { + return dimensionSpan{}, fmt.Errorf("invalid %s range %q (expected 1:10 or Sheet!1:10)", label, spec) + } + + startRow, err := strconv.ParseInt(m[1], 10, 64) + if err != nil || startRow <= 0 { + return dimensionSpan{}, fmt.Errorf("invalid %s start row %q", label, m[1]) + } + endRow := startRow + if m[2] != "" { + endRow, err = strconv.ParseInt(m[2], 10, 64) + if err != nil || endRow <= 0 { + return dimensionSpan{}, fmt.Errorf("invalid %s end row %q", label, m[2]) + } + } + if endRow < startRow { + startRow, endRow = endRow, startRow + } + + return dimensionSpan{ + SheetName: sheetName, + StartIndex: startRow - 1, + EndIndex: endRow, + }, nil +} diff --git a/internal/cmd/sheets_resize.go b/internal/cmd/sheets_resize.go index 514602f..1fb2d46 100644 --- a/internal/cmd/sheets_resize.go +++ b/internal/cmd/sheets_resize.go @@ -3,18 +3,11 @@ package cmd import ( "context" "fmt" - "regexp" - "strconv" "strings" "google.golang.org/api/sheets/v4" ) -var ( - columnsRangeRe = regexp.MustCompile(`^([A-Za-z]+)(?::([A-Za-z]+))?$`) - rowsRangeRe = regexp.MustCompile(`^([0-9]+)(?::([0-9]+))?$`) -) - type SheetsResizeColumnsCmd struct { SpreadsheetID string `arg:"" name:"spreadsheetId" help:"Spreadsheet ID"` Columns string `arg:"" name:"columns" help:"Columns range (eg. Sheet1!A:C)"` @@ -174,75 +167,3 @@ func (c *SheetsResizeRowsCmd) Run(ctx context.Context, flags *RootFlags) error { }, text, nil }) } - -type dimensionSpan struct { - SheetName string - StartIndex int64 - EndIndex int64 -} - -func parseColumnsSpan(spec, label string) (dimensionSpan, error) { - sheetName, part, err := splitA1Sheet(strings.TrimSpace(spec)) - if err != nil { - return dimensionSpan{}, fmt.Errorf("parse %s range: %w", label, err) - } - part = strings.ReplaceAll(strings.TrimSpace(part), "$", "") - m := columnsRangeRe.FindStringSubmatch(part) - if m == nil { - return dimensionSpan{}, fmt.Errorf("invalid %s range %q (expected A:C or Sheet!A:C)", label, spec) - } - - startCol, err := colLettersToIndex(m[1]) - if err != nil { - return dimensionSpan{}, err - } - endCol := startCol - if m[2] != "" { - endCol, err = colLettersToIndex(m[2]) - if err != nil { - return dimensionSpan{}, err - } - } - if endCol < startCol { - startCol, endCol = endCol, startCol - } - - return dimensionSpan{ - SheetName: sheetName, - StartIndex: int64(startCol - 1), - EndIndex: int64(endCol), - }, nil -} - -func parseRowsSpan(spec, label string) (dimensionSpan, error) { - sheetName, part, err := splitA1Sheet(strings.TrimSpace(spec)) - if err != nil { - return dimensionSpan{}, fmt.Errorf("parse %s range: %w", label, err) - } - part = strings.ReplaceAll(strings.TrimSpace(part), "$", "") - m := rowsRangeRe.FindStringSubmatch(part) - if m == nil { - return dimensionSpan{}, fmt.Errorf("invalid %s range %q (expected 1:10 or Sheet!1:10)", label, spec) - } - - startRow, err := strconv.ParseInt(m[1], 10, 64) - if err != nil || startRow <= 0 { - return dimensionSpan{}, fmt.Errorf("invalid %s start row %q", label, m[1]) - } - endRow := startRow - if m[2] != "" { - endRow, err = strconv.ParseInt(m[2], 10, 64) - if err != nil || endRow <= 0 { - return dimensionSpan{}, fmt.Errorf("invalid %s end row %q", label, m[2]) - } - } - if endRow < startRow { - startRow, endRow = endRow, startRow - } - - return dimensionSpan{ - SheetName: sheetName, - StartIndex: startRow - 1, - EndIndex: endRow, - }, nil -} diff --git a/internal/cmd/sheets_resize_test.go b/internal/cmd/sheets_resize_test.go index d23a759..e76853c 100644 --- a/internal/cmd/sheets_resize_test.go +++ b/internal/cmd/sheets_resize_test.go @@ -99,3 +99,45 @@ func TestSheetsResizeCmds(t *testing.T) { } }) } + +func TestParseColumnsSpan(t *testing.T) { + t.Run("sheet range", func(t *testing.T) { + span, err := parseColumnsSpan("Sheet 1!$C:$A", "columns") + if err != nil { + t.Fatalf("parseColumnsSpan: %v", err) + } + if span.SheetName != "Sheet 1" { + t.Fatalf("SheetName=%q, want %q", span.SheetName, "Sheet 1") + } + if span.StartIndex != 0 || span.EndIndex != 3 { + t.Fatalf("range=%d:%d, want 0:3", span.StartIndex, span.EndIndex) + } + }) + + t.Run("invalid range", func(t *testing.T) { + if _, err := parseColumnsSpan("Sheet1!1:3", "columns"); err == nil { + t.Fatal("expected error for invalid column range") + } + }) +} + +func TestParseRowsSpan(t *testing.T) { + t.Run("sheet range", func(t *testing.T) { + span, err := parseRowsSpan("Sheet 1!$4:$2", "rows") + if err != nil { + t.Fatalf("parseRowsSpan: %v", err) + } + if span.SheetName != "Sheet 1" { + t.Fatalf("SheetName=%q, want %q", span.SheetName, "Sheet 1") + } + if span.StartIndex != 1 || span.EndIndex != 4 { + t.Fatalf("range=%d:%d, want 1:4", span.StartIndex, span.EndIndex) + } + }) + + t.Run("invalid row", func(t *testing.T) { + if _, err := parseRowsSpan("Sheet1!0:2", "rows"); err == nil { + t.Fatal("expected error for invalid row range") + } + }) +}