cat / 012
clone

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

main
012 / internal / nuon / value.go
value.go 4.7K
1// Package nuon reads and writes NUON, nushell's object notation: the2// text `to nuon` writes and `from nuon` reads, which keeps nushell's3// types (file sizes, durations, dates, binary) where JSON would turn4// them into numbers and strings. JSON is NUON too, so the same reader5// takes JSON documents, and a run of top-level values one after another6// (NDJSON, or records nushell streams) reads as one table.7//8// Tables are read a row at a time: a Reader takes an io.Reader and9// yields each row as it's parsed, never the whole document, with the10// header known up front for nushell's table form ([[a, b]; [1, 2]]) and11// from the records themselves for a list of records. So a reader may sit12// on a pipe that's still being written and hand over rows as they come.13// Each row's own values (a nested record, a list) are parsed whole.14//15// Writing goes the other way: Append writes a value as `to nuon` would,16// and a TableWriter writes rows in the table form as they're given.17// The package knows nothing of sheets; see internal/fileio for how18// values become cells.19package nuon2021import (22 "math"23 "time"24)2526// Kind is a NUON value's type.27type Kind uint82829// The kinds of value, as nushell's describe names them.30const (31 Null Kind = iota32 Bool // true, false33 Int // 42, 0x2a, 1_00034 Float // 1.5, 1e3, inf, NaN35 String // "quoted", 'single', `backtick`, r#'raw'#, or a bare word36 Filesize // 1646b, 1.5kb, 1MiB: Int bytes37 Duration // 90sec, 1500000000ns: Int nanoseconds38 Date // 2026-09-27T11:27:31-06:0039 Binary // 0x[DEAD]40 List // [1, 2]41 Record // {a: 1}42)4344var kindNames = [...]string{"nothing", "bool", "int", "float", "string", "filesize", "duration", "datetime", "binary", "list", "record"}4546// String is the kind's name in nushell, e.g. "filesize".47func (k Kind) String() string {48 if int(k) < len(kindNames) {49 return kindNames[k]50 }51 return "unknown"52}5354// Value is one NUON value. Which fields hold it depends on Kind: Int for55// Int, Filesize (bytes) and Duration (nanoseconds); Float; Str for56// String; Time for Date; Bytes for Binary; List; Fields for Record.57type Value struct {58 Kind Kind59 Bool bool60 Int int6461 Float float6462 Str string63 Time time.Time64 Bytes []byte65 List []Value66 Fields []Field67}6869// Field is one of a record's fields, in the record's order.70type Field struct {71 Key string72 Value Value73}7475// Row is one row of a table: its columns' names and values, in order.76type Row = []Field7778// Constructors for each kind.7980// NullValue is null.81func NullValue() Value { return Value{} }8283// BoolValue is true or false.84func BoolValue(b bool) Value { return Value{Kind: Bool, Bool: b} }8586// IntValue is an integer.87func IntValue(n int64) Value { return Value{Kind: Int, Int: n} }8889// FloatValue is a float.90func FloatValue(f float64) Value { return Value{Kind: Float, Float: f} }9192// StringValue is a string.93func StringValue(s string) Value { return Value{Kind: String, Str: s} }9495// FilesizeValue is a file size in bytes.96func FilesizeValue(bytes int64) Value { return Value{Kind: Filesize, Int: bytes} }9798// DurationValue is a duration.99func DurationValue(d time.Duration) Value { return Value{Kind: Duration, Int: int64(d)} }100101// DateValue is a date and time, with its offset from UTC.102func DateValue(t time.Time) Value { return Value{Kind: Date, Time: t} }103104// BinaryValue is bytes.105func BinaryValue(b []byte) Value { return Value{Kind: Binary, Bytes: b} }106107// ListValue is a list.108func ListValue(vs ...Value) Value { return Value{Kind: List, List: vs} }109110// RecordValue is a record.111func RecordValue(fs ...Field) Value { return Value{Kind: Record, Fields: fs} }112113// Equal reports whether a and b are the same value: the same kind and114// contents, dates at the same instant with the same offset, and NaN115// equal to NaN.116func Equal(a, b Value) bool {117 if a.Kind != b.Kind {118 return false119 }120 switch a.Kind {121 case Bool:122 return a.Bool == b.Bool123 case Int, Filesize, Duration:124 return a.Int == b.Int125 case Float:126 return a.Float == b.Float || math.IsNaN(a.Float) && math.IsNaN(b.Float)127 case String:128 return a.Str == b.Str129 case Date:130 _, ao := a.Time.Zone()131 _, bo := b.Time.Zone()132 return a.Time.Equal(b.Time) && ao == bo133 case Binary:134 return string(a.Bytes) == string(b.Bytes)135 case List:136 return equalLists(a.List, b.List)137 case Record:138 return equalFields(a.Fields, b.Fields)139 }140 return true141}142143func equalLists(a, b []Value) bool {144 if len(a) != len(b) {145 return false146 }147 for i := range a {148 if !Equal(a[i], b[i]) {149 return false150 }151 }152 return true153}154155func equalFields(a, b []Field) bool {156 if len(a) != len(b) {157 return false158 }159 for i := range a {160 if a[i].Key != b[i].Key || !Equal(a[i].Value, b[i].Value) {161 return false162 }163 }164 return true165}