cat / 012
clone

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

main
012 / internal / notebook / notebook.go
notebook.go 4.8K
1// Package notebook is a notebook's document: its cells (code cells2// holding a nushell pipeline, note cells holding Markdown), the names3// cells give their outputs, what each cell reads, the order cells run4// in, and what a cell's last run left (its output). It knows nothing of5// sheets, terminals or nu itself: the engine (internal/sheet) keeps a6// notebook tab's cells in its undo history and its file, and the UI runs7// them and draws them.8package notebook910import (11 "errors"12 "fmt"13 "slices"14 "strings"15 "time"16)1718// Kind is what a cell holds.19type Kind uint82021const (22 // Code cells hold a nushell pipeline, several lines if need be.23 Code Kind = iota24 // Note cells hold Markdown.25 Note26)2728// String is how the file names the kind.29func (k Kind) String() string {30 if k == Note {31 return "note"32 }33 return "code"34}3536// ParseKind reads a kind as the file names it.37func ParseKind(s string) (Kind, bool) {38 switch s {39 case "code", "":40 return Code, true41 case "note":42 return Note, true43 }44 return Code, false45}4647// Cell is one cell of a notebook. Cells are values: a change makes a new48// one, so the undo history can keep the list it replaced.49type Cell struct {50 // ID identifies the cell for as long as the workbook is open: its51 // output is kept by it. It isn't saved.52 ID int53 Kind Kind54 Source string55}5657// Parse reads the cell's source; a note cell's has nothing to read.58func (c Cell) Parse() Source {59 if c.Kind != Code {60 return Source{Text: c.Source}61 }62 return Parse(c.Source)63}6465// Name is the name the cell gives its output: what its last statement66// assigns (`name = pipeline`), or "".67func (c Cell) Name() string { return c.Parse().Name() }6869// WithName is src given the name name, as the name its last statement70// assigns, replacing any it has; "" takes its name away.71func WithName(src, name string) string {72 head := ""73 if name != "" {74 head = name + " = "75 }76 p := Parse(src)77 if len(p.Stmts) == 0 {78 if strings.TrimSpace(src) == "" {79 return head80 }81 return src + "\n" + head82 }83 last := p.Stmts[len(p.Stmts)-1]84 if last.Name != "" {85 return src[:last.From] + head + src[last.Body:]86 }87 return src[:last.From] + head + src[last.From:]88}8990// Reserved are nushell's own variables and the notebook's: $selection91// is the selection on a sheet, $sheet.A1:C9 a range of one.92var Reserved = []string{"in", "env", "nu", "it", "selection", "sheet"}9394// ValidName checks that name can name a cell's output: a nushell95// variable's name of letters, digits and _, not starting with a digit,96// not one of Reserved, and not starting with __, which the notebook's97// own variables do.98func ValidName(name string) error {99 switch {100 case name == "":101 return errors.New("Enter a name")102 case len(name) > 64:103 return errors.New("A name can be at most 64 characters")104 case isDigit(name[0]):105 return errors.New("A name can't start with a digit")106 case strings.HasPrefix(name, "__"):107 return errors.New("Names starting with __ are the notebook's own")108 case slices.Contains(Reserved, strings.ToLower(name)):109 return fmt.Errorf("$%s is nushell's own", name)110 }111 for i := range len(name) {112 if c := name[i]; !isLetter(c) && !isDigit(c) && c != '_' {113 return errors.New("A name can only have letters, digits and _")114 }115 }116 return nil117}118119func isLetter(c byte) bool { return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' }120func isDigit(c byte) bool { return c >= '0' && c <= '9' }121func isWord(c byte) bool { return isLetter(c) || isDigit(c) || c == '_' }122123// Output is what a code cell's last run left: what it printed, as NUON,124// or why it failed. Outputs are values too, replaced whole.125type Output struct {126 // NUON is what the pipeline printed, as NUON; nil when it failed or127 // its output wasn't saved.128 NUON []byte129 // Err is nu's message when the run failed, and Detail the rest of130 // what it said (its help line).131 Err, Detail string132 // Note says what was left out.133 Note string134 // Count is the run's number in this session, [3]; 0 for an output135 // read from the file.136 Count int137 // Seq identifies the output among the workbook's, for telling138 // whether what a cell read has changed since.139 Seq int140 // Vars are the values, as NUON, of the variables the cell assigns141 // other than its output, which later cells read by name. A file142 // doesn't keep them: a cell reading one runs its cell first.143 Vars map[string][]byte144 // Took is how long the run took.145 Took time.Duration146 // Source is the cell's source when it ran.147 Source string148 // Reads are the Seqs of the outputs the run read, by name.149 Reads map[string]int150 // Selection is the range the run read as $selection, "Sheet1!A1:C9".151 Selection string152 // Unsaved is set on an output the file didn't keep, being larger153 // than the caps allowed: there's nothing to show until it runs.154 Unsaved bool155}156157// Failed reports whether the run failed.158func (o *Output) Failed() bool { return o != nil && o.Err != "" }