Skip to content

Commit 5cdc419

Browse files
alexknowshtmlclaude
andcommitted
feat(sheets): add --safe-formula and --verify flags to update command
--safe-formula reads a formula from a file, avoiding shell ! expansion on cross-tab references (e.g. =IMPORTRANGE!). Forces USER_ENTERED mode. --verify reads the updated range back after write and warns on any spreadsheet error token: #ERROR!, #REF!, #NAME?, #VALUE!, #DIV/0!, Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent d2e0bd7 commit 5cdc419

1 file changed

Lines changed: 32 additions & 1 deletion

File tree

internal/cmd/sheets.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"os"
78
"strings"
89

910
"google.golang.org/api/drive/v3"
@@ -176,6 +177,8 @@ type SheetsUpdateCmd struct {
176177
ValueInput string `name:"input" help:"Value input option: RAW or USER_ENTERED" default:"USER_ENTERED"`
177178
ValuesJSON string `name:"values-json" help:"Values as JSON 2D array"`
178179
CopyValidationFrom string `name:"copy-validation-from" help:"Copy data validation from an A1 range or named range (e.g. 'Sheet1!A2:D2' or MyNamedRange) to the updated cells"`
180+
SafeFormula string `name:"safe-formula" help:"Path to file containing a formula; avoids shell ! expansion and forces USER_ENTERED mode"`
181+
Verify bool `name:"verify" help:"After write, read the cell back and warn if it contains a spreadsheet error (e.g. #ERROR!, #REF!, #NAME?)"`
179182
}
180183

181184
func (c *SheetsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error {
@@ -191,8 +194,16 @@ func (c *SheetsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error {
191194
}
192195

193196
var values [][]interface{}
197+
safeFormulaUsed := false
194198

195199
switch {
200+
case strings.TrimSpace(c.SafeFormula) != "":
201+
content, readErr := os.ReadFile(c.SafeFormula)
202+
if readErr != nil {
203+
return fmt.Errorf("read --safe-formula file: %w", readErr)
204+
}
205+
values = [][]interface{}{{strings.TrimSpace(string(content))}}
206+
safeFormulaUsed = true
196207
case strings.TrimSpace(c.ValuesJSON) != "":
197208
b, err := resolveInlineOrFileBytes(c.ValuesJSON, stdinReader(ctx))
198209
if err != nil {
@@ -210,7 +221,7 @@ func (c *SheetsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error {
210221
}
211222

212223
valueInputOption := strings.TrimSpace(c.ValueInput)
213-
if valueInputOption == "" {
224+
if valueInputOption == "" || safeFormulaUsed {
214225
valueInputOption = sheetsDefaultValueInputOption
215226
}
216227

@@ -256,6 +267,26 @@ func (c *SheetsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error {
256267
}
257268
}
258269

270+
if c.Verify && strings.TrimSpace(resp.UpdatedRange) != "" {
271+
getResp, getErr := svc.Spreadsheets.Values.Get(spreadsheetID, resp.UpdatedRange).
272+
ValueRenderOption("FORMATTED_VALUE").Do()
273+
if getErr != nil {
274+
u.Err().Errorf("--verify: failed to read back cell: %v", getErr)
275+
} else {
276+
errorTokens := []string{"#ERROR!", "#REF!", "#NAME?", "#VALUE!", "#DIV/0!", "#N/A", "#NUM!"}
277+
for _, row := range getResp.Values {
278+
for _, cell := range row {
279+
val := fmt.Sprintf("%v", cell)
280+
for _, token := range errorTokens {
281+
if strings.Contains(val, token) {
282+
u.Err().Printf("--verify: cell %s contains spreadsheet error: %s", resp.UpdatedRange, val)
283+
}
284+
}
285+
}
286+
}
287+
}
288+
}
289+
259290
if outfmt.IsJSON(ctx) {
260291
return outfmt.WriteJSON(ctx, stdoutWriter(ctx), map[string]any{
261292
"updatedRange": resp.UpdatedRange,

0 commit comments

Comments
 (0)