cat / 012
clone

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

main
012 / internal / macro / answer.go
answer.go 3.7K
1package macro23import (4 "bytes"5 "encoding/json"6 "fmt"7 "io"8 "strconv"9 "strings"1011 "go.starlark.net/starlark"12 "go.starlark.net/syntax"13)1415// Structured answers. A dialog's choices (the columns of a sort, the16// values a filter hides, a chart's options) don't fit one string, so a17// recording writes them as a dict, run("data.sort_range", answer={"by":18// [{"column": "B"}]}), and run hands a dict or list answer to the Host19// as JSON text, keys in the order written.2021// JSON is JSON text an Action writes as a Starlark dict or list literal,22// keys in the order they come.23type JSON string2425// starlarkLiteral writes JSON text as a Starlark literal: objects as26// dicts, arrays as lists, and null, true and false as None, True and27// False.28func starlarkLiteral(text string) (string, error) {29 d := json.NewDecoder(strings.NewReader(text))30 d.UseNumber()31 var b strings.Builder32 if err := writeLiteral(&b, d); err != nil {33 return "", err34 }35 if _, err := d.Token(); err != io.EOF {36 return "", fmt.Errorf("more than one value in %q", text)37 }38 return b.String(), nil39}4041// writeLiteral writes the next JSON value of d.42func writeLiteral(b *strings.Builder, d *json.Decoder) error {43 t, err := d.Token()44 if err != nil {45 return err46 }47 switch t := t.(type) {48 case json.Delim:49 return writeContainer(b, d, t)50 case string:51 b.WriteString(syntax.Quote(t, false))52 case json.Number:53 b.WriteString(t.String())54 case bool:55 b.WriteString(map[bool]string{true: "True", false: "False"}[t])56 case nil:57 b.WriteString("None")58 }59 return nil60}6162// writeContainer writes an object or array whose opening delimiter was63// open.64func writeContainer(b *strings.Builder, d *json.Decoder, open json.Delim) error {65 obj := open == '{'66 if obj {67 b.WriteByte('{')68 } else {69 b.WriteByte('[')70 }71 for i := 0; d.More(); i++ {72 if i > 0 {73 b.WriteString(", ")74 }75 if obj {76 k, err := d.Token()77 if err != nil {78 return err79 }80 b.WriteString(syntax.Quote(k.(string), false) + ": ")81 }82 if err := writeLiteral(b, d); err != nil {83 return err84 }85 }86 if _, err := d.Token(); err != nil { // the closing delimiter87 return err88 }89 if obj {90 b.WriteByte('}')91 } else {92 b.WriteByte(']')93 }94 return nil95}9697// answerJSON writes a script's dict or list answer as JSON text, keys in98// the order the dict holds them.99func answerJSON(v starlark.Value) (string, error) {100 var b bytes.Buffer101 if err := writeJSON(&b, v); err != nil {102 return "", err103 }104 return b.String(), nil105}106107func writeJSON(b *bytes.Buffer, v starlark.Value) error {108 switch v := v.(type) {109 case starlark.NoneType:110 b.WriteString("null")111 case starlark.Bool:112 b.WriteString(strconv.FormatBool(bool(v)))113 case starlark.Int:114 b.WriteString(v.String())115 case starlark.Float:116 raw, err := json.Marshal(float64(v))117 if err != nil {118 return fmt.Errorf("run: answer: %v", err)119 }120 b.Write(raw)121 case starlark.String:122 raw, _ := json.Marshal(string(v))123 b.Write(raw)124 case *starlark.Dict:125 return writeDict(b, v)126 case starlark.Indexable: // lists and tuples127 b.WriteByte('[')128 for i := range v.Len() {129 if i > 0 {130 b.WriteByte(',')131 }132 if err := writeJSON(b, v.Index(i)); err != nil {133 return err134 }135 }136 b.WriteByte(']')137 default:138 return fmt.Errorf("run: an answer can't hold a %s", v.Type())139 }140 return nil141}142143func writeDict(b *bytes.Buffer, d *starlark.Dict) error {144 b.WriteByte('{')145 for i, kv := range d.Items() {146 k, ok := kv[0].(starlark.String)147 if !ok {148 return fmt.Errorf("run: an answer's keys are strings, not %s", kv[0].Type())149 }150 if i > 0 {151 b.WriteByte(',')152 }153 raw, _ := json.Marshal(string(k))154 b.Write(raw)155 b.WriteByte(':')156 if err := writeJSON(b, kv[1]); err != nil {157 return err158 }159 }160 b.WriteByte('}')161 return nil162}