cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

012 get and set find tables by name and structured reference

Dami committed 3be58e8
commit
3be58e82b6b0afd86ca696ab42f9f85c1a4ffb1e
parent
c984e368941272401bafa315a49fd931607cbf1c
author
Dami <dami.etoile@gmail.com>
kind
feat risk score 1.6 of 3, Jev's estimate
4 files changed, +89 -5
Mdocs/files/scripts.md3+-
Minternal/headless/headless_test.go39++++++++++++++++++++++++++++-
Minternal/headless/ref.go11++++++--
Minternal/sheet/tablebind.go41++++++++++++++++++++++++++++++
M docs/files/scripts.md +2 -1
M internal/headless/headless_test.go +38 -1
@@ -63,7 +63,7 @@ func TestResolve(t *testing.T) {
6363 }6464 for ref, want := range map[string]string{6565 "Q4!A1": `no sheet named "Q4" (sheets: Sheet1, Q3 plan)`,66- "what": `"what" isn't a cell, a range, a named range or a sheet`,66+ "what": `"what" isn't a cell, a range, a named range, a table or a sheet`,6767 } {6868 if _, err := Resolve(w, ref); err == nil || !strings.Contains(err.Error(), want) {6969 t.Errorf("Resolve(%q): %v, want %q", ref, err, want)
@@ -74,6 +74,43 @@ func TestResolve(t *testing.T) {
7474 }7575 }7676 77+// Tables are found by name, whole, or by structured references as78+// formulas read them; set types into a cell one names.79+func TestTables(t *testing.T) {80+ w := book(t)81+ if err := w.Sheet(0).CreateTable("Fruit", sheet.NewRect(addr("A1"), addr("B3"))); err != nil {82+ t.Fatal(err)83+ }84+ for ref, want := range map[string]string{85+ "Fruit": "Sheet1!A1:B3",86+ "fruit[Price]": "Sheet1!B2:B3",87+ "Fruit[#All]": "Sheet1!A1:B3",88+ "Fruit[[#Headers],[Price]]": "Sheet1!B1",89+ } {90+ if got, err := Resolve(w, ref); err != nil || got.String() != want {91+ t.Errorf("Resolve(%q) = %s, %v; want %s", ref, got, err, want)92+ }93+ }94+ for ref, want := range map[string]string{95+ "Fruit[Cost]": "Fruit has no such column (columns: Item, Price)",96+ "Fruit[@Item]": "names no rows of Fruit here",97+ "Veg[Item]": "there's no table named Veg",98+ } {99+ if _, err := Resolve(w, ref); err == nil || !strings.Contains(err.Error(), want) {100+ t.Errorf("Resolve(%q): %v, want %q", ref, err, want)101+ }102+ }103+ if got := get(t, w, "Fruit", GetOptions{Format: "nuon"}); got != "[[Item, Price]; [Apple, 1.5],\n[Pear, 2]]\n" {104+ t.Errorf("get Fruit %q", got)105+ }106+ if _, err := Set(w, []Entry{{"Fruit[[#Headers],[Price]]", "Cost"}}, SetOptions{}); err != nil {107+ t.Fatal(err)108+ }109+ if got := get(t, w, "Fruit[[#Headers],[Cost]]", GetOptions{}); got != "Cost\n" {110+ t.Errorf("the header set %q", got)111+ }112+}113+77114 func get(t *testing.T, w *sheet.Workbook, ref string, o GetOptions) string {78115 t.Helper()79116 target, err := Resolve(w, ref)
M internal/headless/ref.go +8 -3
M internal/sheet/tablebind.go +41 -0
@@ -1,6 +1,7 @@
11 package sheet22 33 import (4+ "fmt"45 "slices"56 "strings"67
@@ -334,3 +335,43 @@ func (s *Sheet) TableLook(a Addr) (header, band bool) {
334335 }335336 return false, false336337 }338+339+// TableRange finds the cells a structured reference written alone340+// stands for (Sales[Amount], Sales[#All]), as a formula outside the341+// table reads them, or with a table's name alone its whole table,342+// header row included. ok is false when ref is neither; err says why343+// one names no cells.344+func (w *Workbook) TableRange(ref string) (s *Sheet, r Rect, ok bool, err error) {345+ n, perr := formula.Parse(ref, parserFuncs)346+ if perr != nil {347+ return nil, Rect{}, false, nil348+ }349+ var t formula.TableRef350+ switch n := n.(type) {351+ case formula.TableRef:352+ t = n353+ case formula.Name:354+ t = formula.TableRef{Table: n.Name, Items: formula.ItemAll}355+ default:356+ return nil, Rect{}, false, nil357+ }358+ v, found := w.findTable(nameKey(t.Table))359+ switch {360+ case !found:361+ if _, isName := n.(formula.Name); isName {362+ return nil, Rect{}, false, nil363+ }364+ return nil, Rect{}, true, fmt.Errorf("there's no table named %s", t.Table)365+ case !v.ok:366+ return nil, Rect{}, true, fmt.Errorf("%s has no rows yet", t.Table)367+ }368+ lo, hi, okRows := v.rows(t.Rows(), Addr{}, false)369+ c0, c1, okCols := v.colSpan(t)370+ switch {371+ case !okCols:372+ return nil, Rect{}, true, fmt.Errorf("%s has no such column (columns: %s)", t.Table, strings.Join(v.cols, ", "))373+ case !okRows:374+ return nil, Rect{}, true, fmt.Errorf("%s names no rows of %s here: a formula's own row, or rows it hasn't", ref, t.Table)375+ }376+ return v.s, Rect{From: Addr{Col: c0, Row: lo}, To: Addr{Col: c1, Row: hi}}, true, nil377+}