refactor(sheets): extract dimension span parsing

This commit is contained in:
Peter Steinberger 2026-03-09 03:33:30 +00:00
parent a394c1c7b0
commit 074c4a5042
3 changed files with 127 additions and 79 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}
})
}