worksheets

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 27, 2022 License: Apache-2.0 Imports: 16 Imported by: 0

README

Worksheets -- Overview

CircleCI GoDoc

Prerequisites

This release of worksheets is compatible with go1.13. For previous Go versions refer to release 1.0.0.

Description

Let's start with a motivating example, easily representing a borrower's legal name, date of birth, and determining if they are of legal age to get a mortgage.

We start by giving the definition for what data describe a borrower worksheet

worksheet borrower {
	1:first_name text
	2:last_name text
	3:dob date
	4:can_take_mortgage computed {
		return dob > now + 18 years
	}
}

From this definition, we generate Golang hooks to manipulte borrowers' worksheets, store and retrieve them

joey := worksheet.Create("borrower")
joey.SetText("first_name", "Joey")
joey.SetText("last_name", "Pizzapie")
joey.SetDate("dob", 1980, 5, 23)

We can query worksheet

joey.GetBool("can_take_mortgage")

Store the worksheet

bytes, err := joey.Marshal()

And retrieve the worksheet

joey, err := worksheet.Unmarshal("borrower", bytes)

Contributing

Running Tests

If you want to contribute, you can get tests running locally as follows:

cd path/to/worksheets
createuser --createdb ws_user
createdb --username=ws_user ws_test
psql -U ws_user ws_test -f schema.sql
go get -t ./...
go test -v ./...

or $ go test -v ./... -testify.m <TestName> for individual tests.

To update your schema, you can simply re-run

psql -U ws_user ws_test -f schema.sql

Running Benchmarks

For benchmarks to be interesting, we need to have a primed database with lots of data. The more the better.

In bench_test.go, the start of each benchmark has priming code, e.g. b.prime(100000). You'll want to run benchmarks in two steps: 1. put a high number to prime the databse with lots of data, then run the benchmark

 go test -v -run=NOTHING -bench=. -benchtime=15s

This will take a long time, since before running, the benchmark will first write a lot of data in the local db. Once that completes, you can comment out or change the priming to 0, and run the benchmark again

 go test -v -run=NOTHING -bench=. -benchtime=15s

Worksheet Definition

All centers around the concept of a worksheet which is constituted of typed named fields

worksheet person {
	1:age number[0]
	2:first_name text
}

The general syntax for a field is

index:name type [extras]

(We explain the need for the index in the storage section. Those familiar with Thrift or Protocol Buffers can see the parralel with these data representation tools.)

Input Fields

The simplest fields we have are there to store values. In the example above, both age and first_name are input fields. These can be edited and read freely.

Constrained Fields

Input fields can also be constrained

3:social_security_number number[0] constrained_by {
	ok := 100_00_0000 <= social_security_number
	ok = ok && social_security_number <= 999_99_9999
	return ok
}

When fields are constrained, edits which do not satisfy the constraint are rejected.

Computed Fields

We can also derive values from the various inputs. We call these 'output fields' or computed fields

1:date_of_birth date
2:age number[0] computed_by {
	return (now - date) in years
}

(We discuss the syntax in which expressions can be written in a later section.)

Computed fields are determined when their inputs changes, and then materialized. Said another way, if any of the input of a computed field changes, its value is re-computed, and then the resulting value is stored into the worksheet. Computed fields are not computed on the fly, they are only computed in an edit cycle.

Identity

All worksheets have a unique identifier

id text

Which is set upon creation, and cannot be ever edited.

Versioning

All worksheets are versionned, with their version number starting at 1. The version is stored in a field present on all worksheets

version number[0]

This version field is set to 1 upon creation, and incremented on every edit. Versions are used to detect concurrent edits, and abort an edit that was done on an older version of the worksheet than the one it would now be applied to. Edits are discussed in greater detail later.

Data Representation

Base Types

The various base types are

Type Represents
bool Booleans.
text Text of arbitrary length.
number[n] Numbers with precision of n decimal places.
time Instant in time. (Time zone independent.)
date Specific date like 7/20/1969. (Time zone dependent.)

All base types represent optional values, i.e. bool covers three values true, false, and undefined.

As described later, all operations over base types have an interpretation with respect to undefined. It is often simply treated as an absorbing element such that v OP undefined = undefined OP v = undefined.

Enums

TODO(pascal): We likely want to support enums in the language to allow introspection into the possible values the enum could take. Something to ponder.

While the language does not have a specific support for enums, these can be described easily with the use of single field worksheets

worksheet name_suffixes {
	1:suffix text constrained_by {
		return suffix in [
			"Jr.",
			"Sr."
			...
		]
	}
}

Which can then be used

worksheet borrower {
	...

	3:first_name text
	4:last_name text
	5:suffix name_suffixes

	...
}

And the Golang hooks allow handling of single field worksheets in a natural way, by automatically 'boxing'

borrower.SetText("suffix", "Jr.")

Or 'unboxing'

borrower.GetText("suffix")
Numbers

Numbers in worksheets have a fixed point precision (e.g. 2 decimal places), and all operations on numbers guarantee the precision to be strictly preserved.

Since we intend to eventally have a statically typed langugage, we choose statically determined rules for data flow, though we expect initial implementations to be dynamic.

Syntax

Number literals are represented in base 10, and may contain any number of underscores (_) to be added for clarity. We can write 123098 or 123_098, and we could even write 1____2_3______098.

The decimal portion of number literal is automatically expanded to fit the type it is being assigned to. For instance, if we store 5.2 in a field number[5], this would yield 5.200_00.

Handling Precision, and Rounding

Precision expansion is allowed, such that you can store a number[n] into a number[m] if n is smaller than m. In such cases, we simply do a precision expansion.

Loss of precision however needs to be explicitely handled.

For instance, with the field age number[0], the expression

age, _ = 5.200 round down

would yield 5 in the age field.

Rounding modes supported are up, down, even.

Addition, Substraction

When adding or subtracting numbers, the rule for decimal treatment is

number[n] op number[m] yields number[max(n, m)]

where op is either + or -.

So for instance 5.03 + 6.000 would yield 11.030 as a number[3] even though it could be dynamically represented as a number[2].

Multiplication

When multiplying, the rule for decimal treatment is

number[n] * number[m] yields number[n + m]

So for instance 5.30 * 6.0 would yield 31.800 as a number[3] even though it could be dynamically represented as a number[1].

Explicit Rounding When Dividing

When dividing, a rounding mode must always be provided such that the syntax for division is v1 / v2 round mode.

For instance, consider the following example. We need to create a map[repayment] to represent repayment of $700 in yearly taxes, over a 12 months period. If we were to split in equal parts, we would need to pay $58.33... which is not feasibly. Instead, here we force ourselves to round to cents, i.e. a number[2].

first_month = month of closing_date + 1
total_paid := 0
for current_month := first_month; current_month < first_month + 1 year; current_month++ {
	mp := yearly_taxes / 12 round down
	total_paid += mp
	payment_schedule << payment {
		month = current_month
		amount = mp
	}
}

remainder := yearly_taxes - total_paid
switch remainder % 2 {
	case 0:
		payment_schedule[first_month + 6 months].amount += remainder / 2
		payment_schedule[first_month + 12 months].amount += remainder / 2
	case 1:
		payment_schedule[first_month + 12 months].amount += remainder
}
Time and Date

TODO(pascal) Write this out.

  • time as instant in time, timezone less concept, only display (because it can be lossy) needs timezone in some cases

  • date as a timezone depenedent concept, range of time, so 9/1/2017 ET delineates a specific range

  • date without timezone, to represent input of borrowers, needs to then be interpreted in the context of a timezone to be useful

also, how do we convert to date objects

date(yyyy, mm, dd, tz) could be a function taking three numbers (year, month, day) and a timezone and produce a date object

some_time.date(tz) -> yields date in timezone

some_date.year / .month / .day -> yields sub-component (since date is already tz dependent, this is good)

Slices

  • have slices too!
  • describe operations on slices, simplified because we don't really care about pre-allocating, so the simplest slice = append(slice, value) is enough, the len(slice), then things like slice[index] as well as re-slicing slice[start:], slice[:end], or slice[start:end]
  • would be have undefined for slices, or only empty? having an unknonw number of middle names is different than no middle name for instance, which would push towards having undefined
  • likely same consideration as maps in terms of which values can be placed in a slice

Keyed Worksheets, Maps, and Tuples

In addition to the structures covered earlier, we have

map[W]

which represents a collection of worksheets W, indexed by the key of W.

Tuples

As we shall see in the next section, worksheets' keys can be one or multiple values joined together in N-tuples. To represent this, we introduce

tuple[T1, ..., Tn]

NOTE #1: We don't want to allow tuple[map[foo]], so how do we differentiate the simple types, from more complex types like maps? Maps can be the type of fields, but the type which can go in tuples is a subset of that. Maybe 'base type' as S and field type T is a sufficient distinction? Then maps and tuples would be field types, one over worksheets, the other over only base types.

NOTE #2: It would make sense to have undefindeness defined only over base types, hence a tupe would never itself be undefined, rather all of its components can be.

Keyed Worksheets

Worksheets present in maps must keyed: one or more fields of the worksheet serve as a unique key for the purpose of the mapping

keyed_by {
	first_name
	last_name
}

or

keyed_by identity

When a worksheet is added in a map for the first time, all the fields covered by the key are frozen and are not allowed to be mutated later. This means that if a computed field is part of a key, all the inputs to this computed fields will be frozen.

NOTE: To be consistent with above, we could only allow fields of base types to be part of a key.

operations on maps
  • add: can only add if key not present (no implicit replace)
  • delete: should we be strict about deletion, i.e. require the key to be present, or not?
  • check presence, like _, ok := the_map[the_key]
  • size: gets the size of the map (note: should we keep len to be closer to go?)
  • iterations for k := range range the_map { or for k, v := range the_map

when iterating over maps, iteration order is the order in which items were added in (essentially a hash map + linked list of the elements)

Views

have a way to have 'interface' worksheets which expose set of fields, e.g.

view income {
	yearly_gross_income number[2]
}

and then describe certain worksheets as conforming with these views

worksheet w2_income implements income {
	...
}

Editing Worksheets

There are a three basic steps to editing a worksheet

  1. Proposing an edit (only inputs)
  2. Determining the actual edit (fixed-point calculation)
  3. Applying the actual edit (atomic)

Let's go through them with the help of a contrived example

worksheet borrower {
	1:name text
	2:greeting text computed {
		return "Hello, " + name
	}
}

We can propose the edit

set name "Joey"

Which yields the actual edit

[ on borrower(the-id-here) @ version 5 ]
[ set version 6                        ]
set name "Joey"
set greeting "Hello, Joey"

And when applied mutates the worksheet as intended. (In future examples, we omit the concurrent modification part of edits.)

Edit Blocks, and Individual Edits

We call the set of edits an edit block, which is itself constituted of individual edits.

Some of the possible individual edits are

  • Setting a field to a specific value
  • Unsettting a field, i.e. settting it to undefined
  • Adding, or removing to a map

In a given edit block, fields can be edited only once, and we allow only one operation per map key. (Adding a worksheet into a map with the contains another worksheet with the same key causes a replace.) As such, the order in which edits are applied is semantically irrelevant.

Proposed Edits, Tentative Edits, and Actual Edits

Proposed edit blocks can modify any number of inputs in a worksheet. However, as described earlier, computed fields cannot be modified directly.

When a proposed edit block is applied to a worksheet, all computed fields whose inputs were modified are re-computed. This yields a new tentative edit block. In the case where computed fields depend on other computed fields, the process may need to be repeated until we reach a 'fixed point' to get the actual edit block.

Let's consider the worksheet

worksheet borrower {
	1:name text
	2:name_short text computed {
		if len(name) > 5 {
			return substr(name, 5)
		}
		return name
	}
	3:greeting text computed {
		return "Hello, " + name_short
	}
}

And the proposed edit

set name "Samantha"

The first tentative edit would be

set name "Samantha"
set name_short "Saman"

The second (and final) edit would be

set name "Samantha"
set name_short "Saman"
set greeting "Hello, Saman"

Applying Edits

When edit blocks are applied to a worksheet, all individual edits are applied atomically, i.e. all individual edits succeed or none of the individual edits succeed.

Edit Reacting Code

We also make it possible for user specified code to intercept the fixed-point calculation introduce any edit. This can be useful for cases where you are unable to express computed fields, or cases where doing so in the worksheet language would be unclear. One example could be setting various dates when certain events occur.

Example

func (myEditor *) OnEdit(current Worksheet, proposed_edit Edit) (Edit, error) {
	if current.GetText("name") == "Joey" {
		return proposed_edit.SetText("name", "Joey Pizzapie"), nil
	}
	return proposed_edit, nil
}

NOTE: We'd want the Edit struct to have sufficient introspection such that we can clearly write cases like the TRID Rule where we need to capture the first time all 6 fields are set on a specific worksheet. Maybe we should also provide a pre/post worksheet with the state before any edit, the state after if the edit were to succeed as is? Need to think through what that code would look like and design the hook with that in mind.

One idea would be to be able to verify 'is any of these six fields being modified?', and 'is the resulting edit one where all six fields are complete?', and 'has the trid rule triggered date been set?'.

if current.GetDate("trid_rule_triggered").IsUndefined() {
	if proposed_edit.IsSetting("ssn")
	proposed_edit.IsSetting("...") ||
	proposed_edit.IsSetting("...") ||
	... {
		if !current.GetText("ssn").IsUndefined() &&
		... {

		}
	}
}

Though we'd not even need to verify whether these fields are part of the edit, it's implicit, and more 'underlying structure proof' not too. By 'underlying structure proof' we mean that the code assumes less about the internal structure of the fields, they could be inputs or computed fields, and we wouldn't really care.

Preventing Unstable Edits

Assume we have the worksheet

worksheet cyclic_edits {
	1:right bool
	2:wrong bool computed_by {
		return !right
	}
}

And we propose the edit

set right false

Due to the computed field, this would yield 'actual edit #1'

set right false
set wrong true

Now, assume that we have edit reacting code which flips these fields around

if wrong {
	set right true
} else {
	set right false
}

We would then yield the 'actual edit #2'

set right true
set wrong true

And, due to the computed field, this would yield 'actual edit #3'

set right true
set wrong false

Further modified via the event reacting code into 'actual edit #4'

set right false
set wrong false

And again, due to the computed field, this would yield 'actual edit #5'

set right false
set wrong true

Which is in fact the same as 'actual edit #1'. We have created an infinite loop in the fixed-point calculation!

To prevent such 'unstable edits', we detect cycles of edits, and error out, hence rejecting the proposed edit as being invalid. (Implementation note: this must be done by comparing worksheets', not edits, since two different edits can yield the same worksheet transformation.)

Storing Worksheets

  • storage of worksheets can be totally orthogonal from the system itself

  • simply need to be able to walk worksheets, and reconstruct them

  • basically marshalling and unmarshalling primitives, rather than concrete implementations

  • nice direction, would still want to provide base binary representation out of the box

  • of the worksheets

  • of edits (i.e. the redo log)

  • marshalling: models can be saved in same format as thrift (hence, need index marker for each values)

  • discuss uniqueness, GUID uniqueness out of the box, if need global uniqueness, needs to be provided

Computational Model of Computed Fields, and Constrained Fields

  • all values are optional

  • operations take into account optionality e.g. undefined + 6 yields undefined

  • no cycles in function graphs, no recursion, etc. but must be without cycles (outputs from inputs)

Privacy, and Data Protection

to cover

ACLs

Encyption

Audit Trail

Introspection, and Reflection

  • query an input and get concrete AST of how it is calculated from all raw values
  • query an input to see every value it flows into, i.e. all computed fields using this input

Implementation Notes

Efficient Edits

ideas from

Storage

Type System, from Dynamic to Static

  • describe how we'd deal with constants like "0", and type number[?]
  • explain how we grow a language to be statically typed

Examples

Final Sign-Off

We have a complex sheet requiring a formal sign off by an operator

worksheet requires_review { ...

We track the version at which the operator signed off in signed_off_version, and the property of being signed_off is computed

worksheet sign_off {
	1:requires_review requires_review
	2:signed_off_version number[0]
	3:signed_off computed {
		return requires_review.version == signed_off_version
	}
}

This ensures that any modification to the sheet requires_review nullifies the sign off.

Modeling Declarations

TBD

Modeling Fees

TBD

Modeling Assets, Debts, and Income

TBD

Documentation

Index

Constants

View Source
const (
	ModeUp   RoundingMode = "up"
	ModeDown              = "down"
	ModeHalf              = "half"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Bool

type Bool struct {
	// contains filtered or unexported fields
}

Bool represents a boolean.

func (*Bool) Equal

func (value *Bool) Equal(that Value) bool

func (*Bool) String

func (value *Bool) String() string

func (*Bool) Type

func (value *Bool) Type() Type

func (*Bool) Value

func (value *Bool) Value() bool

type BoolType

type BoolType struct{}

func (*BoolType) String

func (typ *BoolType) String() string

type ComputedBy

type ComputedBy interface {
	Args() []string
	Compute(...Value) Value
}

type DbStore

type DbStore struct {
	// contains filtered or unexported fields
}

func NewStore

func NewStore(defs *Definitions) *DbStore

func (*DbStore) Open

func (s *DbStore) Open(tx *runner.Tx) *Session

type Definition

type Definition struct {
	// contains filtered or unexported fields
}

func (*Definition) FieldByName

func (def *Definition) FieldByName(name string) *Field

func (*Definition) Fields

func (def *Definition) Fields() []*Field

func (*Definition) Name

func (def *Definition) Name() string

func (*Definition) String

func (def *Definition) String() string

type Definitions

type Definitions struct {
	// contains filtered or unexported fields
}

Definitions groups all definitions for a workbook, which may consists of multiple worksheet definitions, custom types, etc.

func MustNewDefinitions

func MustNewDefinitions(reader io.Reader, opts ...Options) *Definitions

func NewDefinitions

func NewDefinitions(reader io.Reader, opts ...Options) (*Definitions, error)

NewDefinitions parses one or more worksheet definitions, and creates worksheet models from them.

func (*Definitions) MustNewWorksheet

func (defs *Definitions) MustNewWorksheet(name string) *Worksheet

func (*Definitions) NewWorksheet

func (defs *Definitions) NewWorksheet(name string) (*Worksheet, error)

type EnumType

type EnumType struct {
	// contains filtered or unexported fields
}

func (*EnumType) Name

func (typ *EnumType) Name() string

func (*EnumType) String

func (typ *EnumType) String() string

type Field

type Field struct {
	// contains filtered or unexported fields
}

func (*Field) IsComputedBy

func (f *Field) IsComputedBy() bool

func (*Field) Name

func (f *Field) Name() string

func (*Field) String

func (f *Field) String() string

func (*Field) Type

func (f *Field) Type() Type

type NamedType

type NamedType interface {
	Type

	// Name returns the name of the named type.
	Name() string
}

NamedType represents types which are uniquely identified by their names, such worksheet types, or enums.

type Number

type Number struct {
	// contains filtered or unexported fields
}

Number represents a fixed decimal number.

func NewNumberFromFloat32

func NewNumberFromFloat32(num float32) *Number

NewNumberFromFloat32 returns a new Number from float32.

func NewNumberFromFloat64

func NewNumberFromFloat64(num float64) *Number

NewNumberFromFloat64 returns a new Number from float64.

func NewNumberFromInt

func NewNumberFromInt(num int) *Number

NewNumberFromInt returns a new Number from int.

func NewNumberFromInt16

func NewNumberFromInt16(num int16) *Number

NewNumberFromInt16 returns a new Number from int16.

func NewNumberFromInt32

func NewNumberFromInt32(num int32) *Number

NewNumberFromInt32 returns a new Number from int32.

func NewNumberFromInt64

func NewNumberFromInt64(num int64) *Number

NewNumberFromInt64 returns a new Number from int64.

func NewNumberFromInt8

func NewNumberFromInt8(num int8) *Number

NewNumberFromInt8 returns a new Number from int8.

func NewNumberFromString

func NewNumberFromString(value string) (*Number, error)

NewNumberFromString returns a new Number from a string representation.

func NewNumberFromUint

func NewNumberFromUint(num uint) *Number

NewNumberFromUint returns a new Number from uint.

func NewNumberFromUint16

func NewNumberFromUint16(num uint16) *Number

NewNumberFromUint16 returns a new Number from uint16.

func NewNumberFromUint32

func NewNumberFromUint32(num uint32) *Number

NewNumberFromUint32 returns a new Number from uint32.

func NewNumberFromUint8

func NewNumberFromUint8(num uint8) *Number

NewNumberFromUint8 returns a new Number from uint8.

func (*Number) Div

func (left *Number) Div(right *Number, mode RoundingMode, scale int) *Number

func (*Number) Equal

func (value *Number) Equal(that Value) bool

func (*Number) GreaterThan

func (left *Number) GreaterThan(right *Number) bool

func (*Number) GreaterThanOrEqual

func (left *Number) GreaterThanOrEqual(right *Number) bool

func (*Number) LessThan

func (left *Number) LessThan(right *Number) bool

func (*Number) LessThanOrEqual

func (left *Number) LessThanOrEqual(right *Number) bool

func (*Number) Minus

func (left *Number) Minus(right *Number) *Number

func (*Number) Mult

func (left *Number) Mult(right *Number) *Number

func (*Number) Plus

func (left *Number) Plus(right *Number) *Number

func (*Number) Round

func (value *Number) Round(mode RoundingMode, scale int) *Number

func (*Number) String

func (value *Number) String() string

func (*Number) Type

func (value *Number) Type() Type

type NumberType

type NumberType struct {
	// contains filtered or unexported fields
}

func (*NumberType) Scale

func (t *NumberType) Scale() int

func (*NumberType) String

func (typ *NumberType) String() string

type Options

type Options struct {
	// Plugins is a map of workshet names, to field names, to plugins for
	// externally computed fields.
	Plugins map[string]map[string]ComputedBy
}

type RoundingMode

type RoundingMode string

RoundingMode describes the rounding mode to be used in an operation.

type Session

type Session struct {
	*DbStore
	// contains filtered or unexported fields
}

Session is the ... TODO(pascal): write

func (*Session) Edit

func (s *Session) Edit(editId string) (time.Time, map[string]int, error)

func (*Session) EditContext added in v1.2.0

func (s *Session) EditContext(ctx context.Context, editId string) (time.Time, map[string]int, error)

func (*Session) Load

func (s *Session) Load(id string) (*Worksheet, error)

func (*Session) LoadContext added in v1.2.0

func (s *Session) LoadContext(ctx context.Context, id string) (*Worksheet, error)

func (*Session) Save

func (s *Session) Save(ws *Worksheet) (string, error)

func (*Session) SaveContext added in v1.2.0

func (s *Session) SaveContext(ctx context.Context, ws *Worksheet) (string, error)

func (*Session) SaveOrUpdate

func (s *Session) SaveOrUpdate(ws *Worksheet) (string, error)

func (*Session) SaveOrUpdateContext added in v1.2.0

func (s *Session) SaveOrUpdateContext(ctx context.Context, ws *Worksheet) (string, error)

func (*Session) Update

func (s *Session) Update(ws *Worksheet) (string, error)

func (*Session) UpdateContext added in v1.2.0

func (s *Session) UpdateContext(ctx context.Context, ws *Worksheet) (string, error)

type Slice

type Slice struct {
	// contains filtered or unexported fields
}

func (*Slice) Elements

func (slice *Slice) Elements() []Value

func (*Slice) Equal

func (value *Slice) Equal(that Value) bool

func (*Slice) Len

func (slice *Slice) Len() int

func (*Slice) String

func (value *Slice) String() string

func (*Slice) Type

func (value *Slice) Type() Type

type SliceType

type SliceType struct {
	// contains filtered or unexported fields
}

func (*SliceType) ElementType

func (s *SliceType) ElementType() Type

func (*SliceType) String

func (typ *SliceType) String() string

type Store

type Store interface {
	// Load loads the worksheet with identifier `id` from the store.
	Load(id string) (*Worksheet, error)
	LoadContext(ctx context.Context, id string) (*Worksheet, error)

	// SaveOrUpdate saves or updates a worksheet to the store. On success,
	// returns an edit identifier.
	SaveOrUpdate(ws *Worksheet) (string, error)
	SaveOrUpdateContext(ctx context.Context, ws *Worksheet) (string, error)

	// Save saves a new worksheet to the store. On success, returns an edit
	// identifier.
	Save(ws *Worksheet) (string, error)
	SaveContext(ctx context.Context, ws *Worksheet) (string, error)

	// Update updates an existing worksheet in the store. On success, returns an
	// edit identifier.
	Update(ws *Worksheet) (string, error)
	UpdateContext(ctx context.Context, ws *Worksheet) (string, error)

	// Edit returns a specific edit, the time at which the edit occurred, and all
	// worksheets modified as a map of their ids to the resulting version.
	Edit(editId string) (time.Time, map[string]int, error)
	EditContext(ctx context.Context, editId string) (time.Time, map[string]int, error)
}

Store ... TODO(pascal): write about abstraction.

type StructScanner

type StructScanner struct {
	AllowUndefinedToNonPointer bool // relax conversion, can populate with zero value
	// contains filtered or unexported fields
}

StructScanner stores state allowing overrides for scanning of registered types.

func NewStructScanner

func NewStructScanner() *StructScanner

func (*StructScanner) RegisterConverter

func (ss *StructScanner) RegisterConverter(t reflect.Type, converterFn func(Value) (interface{}, error))

func (*StructScanner) StructScan

func (ss *StructScanner) StructScan(ws *Worksheet, dest interface{}) error

type Text

type Text struct {
	// contains filtered or unexported fields
}

Text represents a string.

func (*Text) Equal

func (value *Text) Equal(that Value) bool

func (*Text) String

func (value *Text) String() string

func (*Text) Type

func (value *Text) Type() Type

func (*Text) Value

func (value *Text) Value() string

type TextType

type TextType struct{}

func (*TextType) String

func (typ *TextType) String() string

type Type

type Type interface {
	// String returns a string representation of the type.
	String() string
	// contains filtered or unexported methods
}

Type represents the type of a value.

type Undefined

type Undefined struct{}

Undefined represents an undefined value.

func (*Undefined) Equal

func (value *Undefined) Equal(that Value) bool

func (*Undefined) String

func (value *Undefined) String() string

func (*Undefined) Type

func (value *Undefined) Type() Type

type UndefinedType

type UndefinedType struct{}

func (*UndefinedType) String

func (typ *UndefinedType) String() string

type Value

type Value interface {
	// Type returns this value's type.
	Type() Type

	// Equal returns whether this value is equal to that value. Value equality
	// is defined by 'being the same value' inasmuch that you can replace this
	// value in an expression by that value, and have the same result.
	//
	// One could formally define equality by saying that any expression with a
	// hole E[?] would evaluate to the same value with E[this] or E[that].
	Equal(that Value) bool

	// String returns a string representation of the value.
	String() string
	// contains filtered or unexported methods
}

Value represents a runtime value.

func MustNewValue

func MustNewValue(value string) Value

func NewBool

func NewBool(value bool) Value

func NewText

func NewText(value string) Value

func NewUndefined

func NewUndefined() Value

func NewValue

func NewValue(value string) (Value, error)

type Worksheet

type Worksheet struct {
	// contains filtered or unexported fields
}

Worksheet is ... TODO(pascal): documentation binge

func (*Worksheet) Append

func (ws *Worksheet) Append(name string, element Value) error

func (*Worksheet) Clone

func (ws *Worksheet) Clone() *Worksheet

Clone duplicates this worksheet, and all worksheets it points to, in order to create a deep-copy.

func (*Worksheet) Del

func (ws *Worksheet) Del(name string, index int) error

func (*Worksheet) Equal

func (ws *Worksheet) Equal(that Value) bool

func (*Worksheet) Get

func (ws *Worksheet) Get(name string) (Value, error)

Get gets a value for base types, e.g. text, number, or bool. For other kinds of values, use specific getters such as `GetSlice`.

func (*Worksheet) GetSlice

func (ws *Worksheet) GetSlice(name string) ([]Value, error)

func (*Worksheet) Id

func (ws *Worksheet) Id() string

func (*Worksheet) IsSet

func (ws *Worksheet) IsSet(name string) (bool, error)

func (*Worksheet) MarshalJSON

func (ws *Worksheet) MarshalJSON() ([]byte, error)

func (*Worksheet) MustAppend

func (ws *Worksheet) MustAppend(name string, value Value)

func (*Worksheet) MustDel

func (ws *Worksheet) MustDel(name string, index int)

func (*Worksheet) MustGet

func (ws *Worksheet) MustGet(name string) Value

func (*Worksheet) MustGetSlice

func (ws *Worksheet) MustGetSlice(name string) []Value

func (*Worksheet) MustIsSet

func (ws *Worksheet) MustIsSet(name string) bool

func (*Worksheet) MustSet

func (ws *Worksheet) MustSet(name string, value Value)

func (*Worksheet) MustUnset

func (ws *Worksheet) MustUnset(name string)

func (*Worksheet) Name

func (ws *Worksheet) Name() string

func (*Worksheet) Set

func (ws *Worksheet) Set(name string, value Value) error

func (*Worksheet) String

func (ws *Worksheet) String() string

func (*Worksheet) StructScan

func (ws *Worksheet) StructScan(dest interface{}) error

func (*Worksheet) Type

func (ws *Worksheet) Type() Type

func (*Worksheet) Unset

func (ws *Worksheet) Unset(name string) error

func (*Worksheet) Version

func (ws *Worksheet) Version() int

type WorksheetConverter

type WorksheetConverter interface {
	// WorksheetConvert assigns a value from a worksheet field.
	//
	// The src value can be any defined worksheet value, e.g. text, bool,
	// number[n], or even a worksheet value.
	//
	// An error should be returned if the conversion cannot be done.
	WorksheetConvert(src Value) error
}

WorksheetConverter is an interface used by StructScan.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL