1package fileio23import (4 "strings"56 "src.rest/cat/012/internal/notebook"7 "src.rest/cat/012/internal/sheet"8)910// NUON has no currency or percentage, so a cell showing $3.50 reaches11// nushell as the number 3.5, and a date as a datetime, whatever its12// pattern. When a notebook cell's output comes back to a sheet, a13// column takes again the format its namesake had in the ranges the cell14// read ($sheet.A1:C9, $selection), when its values are still of that15// type: a price column read as currency and sent back is currency16// again, a date column shows its dates as it did (see17// docs/nushell/types.md).1819// TextInput is s as an entry that stays text, whatever it looks like:20// '00123 for 00123, as importing stores text.21func TextInput(s string) string { return textInput(s) }2223// NumInput is v as an entry: 3.5, 1E+21.24func NumInput(v float64) string { return numInput(v) }2526// CodeKind says whether a number format code shows a date, a time,27// both or a duration; FmtCustom for anything else.28func CodeKind(code string) sheet.FormatKind { return dateKind(code) }2930// SnapColumns are the names of snap's columns, from its range's first31// row, as a table written as NUON or JSON names them.32func SnapColumns(snap *Snapshot) []string { return snapColumns(snap) }3334// ColumnFormats are the formats of snap's columns, by the columns'35// names: the format of each column's first number under the header,36// when it isn't Automatic.37func ColumnFormats(snap *Snapshot) map[string]sheet.Format {38 r := snap.Range39 out := map[string]sheet.Format{}40 for i, name := range snapColumns(snap) {41 col := r.From.Col + i42 for row := r.From.Row + 1; row <= r.To.Row; row++ {43 c, ok := snap.Cells[sheet.Addr{Col: col, Row: row}]44 if !ok || c.Value.Kind != sheet.Number {45 continue46 }47 if !c.Format.IsZero() {48 out[name] = c.Format49 }50 break51 }52 }53 return out54}5556// nuonKind is the NUON type a number shown in f is written as: a size,57// a duration (a time of day among them), a date, or a plain number.58func nuonKind(f sheet.Format) sheet.FormatKind {59 switch k := f.Kind; k {60 case sheet.FmtSize, sheet.FmtDuration:61 return k62 case sheet.FmtTime:63 return sheet.FmtDuration64 case sheet.FmtDate, sheet.FmtDateTime:65 return sheet.FmtDate66 case sheet.FmtCustom:67 return nuonKind(sheet.Format{Kind: dateKind(f.Pattern)})68 }69 return sheet.FmtNumber70}7172// KeepFormats gives each number under a header named in formats that73// format, when NUON writes both as the same type: a plain number takes74// its column's currency, a date its column's pattern, a size its75// column's decimals.76func KeepFormats(rows *TailRows, formats map[string]sheet.Format) {77 if len(formats) == 0 {78 return79 }80 for i, h := range rows.Header {81 f, ok := formats[strings.TrimSpace(h.V.String())]82 if !ok {83 continue84 }85 for _, row := range rows.Rows {86 if i < len(row) && row[i].V.Kind == sheet.Number && nuonKind(row[i].F) == nuonKind(f) {87 row[i].F = f88 }89 }90 }91}9293// RangeResolver finds the sheet and range a $sheet reference names.94type RangeResolver func(ref string) (*sheet.Sheet, sheet.Rect, error)9596// OutputFormats are the formats KeepFormats keeps for output o of w:97// those of the columns of the ranges its run read, found by resolve,98// and of its selection.99func OutputFormats(w *sheet.Workbook, o *notebook.Output, resolve RangeResolver) map[string]sheet.Format {100 out := map[string]sheet.Format{}101 add := func(t *sheet.Sheet, r sheet.Rect) {102 for name, f := range ColumnFormats(Snap(t, r, t.Name())) {103 if _, ok := out[name]; !ok {104 out[name] = f105 }106 }107 }108 _, _, refs := notebook.Parse(o.Source).Command()109 for _, ref := range refs {110 if t, r, err := resolve(ref.Ref); err == nil {111 add(t, r)112 }113 }114 if o.Selection != "" {115 name, cells := sheet.SplitSheet(o.Selection)116 r, ok := sheet.ParseRange(cells)117 if t := w.Lookup(name); ok && t != nil {118 add(t, r)119 }120 }121 return out122}