1package sheet23import (4 "bufio"5 "encoding/json"6 "fmt"7)89// fileRegion is a region in the file: its definition, one line per10// region after the cells of its sheet, never its table, which its11// source sends again when the file is opened. They need no version:12// earlier builds ignore them and open the sheet without its regions.13//14// "regions": [15// {"name": "sales", "at": "A1", "output": true},16// {"name": "app", "at": "F1", "path": "logs/app.csv", "window": 500}17// ]18//19// A region with a command is a notebook sheet's region as earlier20// builds wrote them, with its label line at "at" and its table under21// it; opening the file converts it (convertOld).22type fileRegion struct {23 Name string `json:"name"`24 At string `json:"at"`25 // A sent output's.26 Output bool `json:"output,omitempty"`27 // A linked file's: see linked.go.28 Path string `json:"path,omitempty"`29 Format string `json:"format,omitempty"`30 Table string `json:"table,omitempty"`31 Query string `json:"query,omitempty"`32 Window int `json:"window,omitempty"`33 // A linked source's: see sourcefile.go.34 Paged bool `json:"paged,omitempty"`35 Order *fileSourceOrder `json:"order,omitempty"`36 // A command region's, read to convert it.37 Command string `json:"command,omitempty"`38 Input string `json:"input,omitempty"`39 Rows int `json:"rows,omitempty"`40 Cols int `json:"cols,omitempty"`41 Reads []string `json:"reads,omitempty"`42 Sort []fileRegionSort `json:"sort,omitempty"`43}4445// fileRegionSort is a column a command region's table was sorted by.46type fileRegionSort struct {47 Column int `json:"column"`48 Desc bool `json:"desc,omitempty"`49}5051// writeRegionDefs writes the regions, one per line.52func (s *Sheet) writeRegionDefs(b *bufio.Writer, indent string) error {53 if len(s.regions.list) == 0 {54 return nil55 }56 b.WriteString(",\n" + indent + `"regions": [`)57 for i, r := range s.regions.list {58 fr := fileRegion{Name: r.Name, At: r.At.String(), Output: r.Output,59 Path: r.File.Path, Format: r.File.Format, Table: r.File.Table, Query: r.File.Query, Window: r.File.Window,60 Paged: r.File.Paged, Order: encodeOrder(r.File.Order)}61 raw, err := json.Marshal(fr)62 if err != nil {63 return err64 }65 if i > 0 {66 b.WriteByte(',')67 }68 fmt.Fprintf(b, "\n%s %s", indent, raw)69 }70 b.WriteString("\n" + indent + "]")71 return nil72}7374// readRegions reads a sheet's regions, stale until their sources send75// their rows, adding the command regions of earlier builds to old.76func (s *Sheet) readRegions(frs []fileRegion, old *[]oldRegion) error {77 for _, fr := range frs {78 if err := ValidRegionName(fr.Name); err != nil {79 return fmt.Errorf("region %q: %w", fr.Name, err)80 }81 if _, _, dup := s.wb.Region(fr.Name); dup || s.regionIndex(nameKey(fr.Name)) >= 0 || s.wb.converting(*old, fr.Name) {82 return fmt.Errorf("region %q is defined twice", fr.Name)83 }84 at, ok := ParseAddr(fr.At)85 if !ok {86 return fmt.Errorf("region %s: invalid cell %q", fr.Name, fr.At)87 }88 r := Region{Name: fr.Name, At: at, Output: fr.Output,89 File: LinkSource{Path: fr.Path, Format: fr.Format, Table: fr.Table, Query: fr.Query, Window: fr.Window}}90 if fr.Paged || fr.Order != nil {91 if err := s.readSource(fr, r); err != nil {92 return fmt.Errorf("region %s: %w", fr.Name, err)93 }94 continue95 }96 switch {97 case fr.Window < 0:98 return fmt.Errorf("region %s: invalid window %d", fr.Name, fr.Window)99 case r.Linked() && (r.Output || fr.Command != ""):100 return fmt.Errorf("region %s: a linked file has no command", fr.Name)101 case r.Output && fr.Command != "":102 return fmt.Errorf("region %s: a sent output has no command", fr.Name)103 case !r.Linked() && !r.Output:104 *old = append(*old, oldRegion{s, fr})105 continue106 }107 s.regions.list = append(s.regions.list, r)108 s.meta(nameKey(r.Name)).stale = true109 s.regionsStale = true110 }111 return nil112}113114// converting reports whether name is among the command regions being115// converted.116func (w *Workbook) converting(old []oldRegion, name string) bool {117 for _, o := range old {118 if nameKey(o.fr.Name) == nameKey(name) {119 return true120 }121 }122 return false123}