Help us shape the future of Go! Take the 2026 Go Developer Survey

serval

package module
v0.1.2-alpha Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 8 Imported by: 0

README

serval

serval — Scoped Enquiry over Records, Values And Lists (a wild cat of the tall grass, which finds what it wants in a field by listening and takes it in one leap)

If you use this, please support me on ko-fi: https://ko-fi.com/jeffday

ko-fi

Reading a long sequence of records a screenful at a time, without ever holding all of it and without asking twice for what you already have. It knows nothing about terminals, protocols, rendering or documents, and its only dependency outside the standard library is github.com/phroun/pawscript, for reading PSL.

It's a sibling to the rest of the line: Mew (text editor), PurfecTerm (terminal emulator), PawScript (language), KittyTK (UI toolkit), khatool (Hebrew and Arabic text) and ifitfits (viewport tiling).

Model

  • A query is asked once and answered once. You state a sequence — a source, a filter, a sort — and that naming never changes: a different filter is a different sequence, opened alongside the first and taking its place. Then you ask for scopes of it. Each scope is a separate question, and Complete ends the answer rather than the interest.
  • A scope is not a filter and names no field. It says start past this record, give me this many, stop if you reach that one, and walk backwards if I say so. Both ends are optional and both are identities, never positions — where a record stands is the business of whoever put it there, and a source made of several others hands each of them that one's own identity rather than passing one down.
  • An answer says how far it is complete. A watermark means nothing between where you asked from and this record is missing. Three ways to stop — filled, joined, exhausted — because a scope that ran out of records and one that reached its count look identical from outside and mean opposite things about whether to ask again.
  • Sources compose. AmendedSource lays additions, replacements and deletions over another source without touching it. ComposedSource reads several as one merged sequence, in the merged order, with each include asked only what it can answer. Both are Sources, so they nest.
  • Answers are kept. A cached scope is a run of records guaranteed complete between its two ends — the same claim a watermark makes, which is why an answer and a cache entry are the same object. Scrolling extends a run rather than making another; two runs that meet join; a run cut in two by an insert is two runs and not a re-query. Eviction is segmented, so a scan of a million records cannot flush out the handful you were actually using.
  • Caching is a wrapper. NewCachedSource(src) answers out of what the process already holds wherever it can and asks the source below wherever it cannot, filing what comes back. It is a Source like the other two, so it composes with them in either order — and a source that should not be cached simply is not wrapped. There is one cache and one quota for the whole process, however many wrappers draw on it; SetCacheLimit sizes it.
  • Order and values are two caches. Where a record stands belongs to a data set — a source, a sort and a filter. What it holds belongs to the source, and is kept once there for every data set drawing on it. Each has its own room and its own eviction, and neither keeps the other alive: close one sort and open another and the values are all still here, while a run whose records have been evicted still knows what comes after what — which is what a top-up is asked against. So a second sort costs places and not fields, and a stretch already fetched for one field is topped up with another rather than fetched again.
  • Two ways to miss, answered differently. Falling off the end of a run is not knowing what comes next, and only reading the stretch will tell you. But a walk that got where it was going over records that aren't known well enough knows the order perfectly — so it asks the narrow question that makes possible: these identities, these fields, nothing re-walked and nothing already known re-sent. A sink that implements Placing skips even that and is handed the order at once, as results for the records it can have and places for the rest — a record's position and whatever is known of it, with no claim about how much. Places are additional, never substitutional, so a sink that does not take them is handed every record it would have been handed anyway.
  • Staleness is told, never decided. Nothing polls, nothing expires and no generation is compared. A source says what happened — a record added, removed, replaced, or altered in named fields — and the reason is what decides the price. A record that has left a sequence is unlinked and the run's claim survives it; one that may have moved takes its run with it. An altered field costs the order only in the sequences that field decides, and costs the values once, for the source. Nothing is fetched either way: invalidation causes forgetting, not traffic.
  • How many there are is a figure, a floor, or nothing. RecordCount is the whole sequence's total, and it belongs to the FILTER rather than the order — sorting the same records cannot make there be more or fewer — so clicking a column header costs a new order and not a new count. A source holding its own records has it for free, one that has read part of a sequence has a floor, and a notice moves it: Added is one more, Removed one fewer, and anything leaving membership in doubt lowers the floor rather than losing the figure. Composing adds the includes' figures; amending asks one question twice — does the filter admit the child's version, and does it admit ours — which is exact wherever the child's has been seen, and a floor where it has not.
  • What is depended on can be stated. Covers(descriptor) is the stretches of one sequence actually held, read off the runs rather than tracked beside them, and DataSetDescriptor.Roles() groups a sequence's fields by the part each plays — so a change is classified by looking a name up, rather than by evaluating a filter or comparing two boundaries.
  • A record says how many members it has. Every subset carries two counts — how many members stand by position, how many by name — so a later question can be answered without asking. Three of the first means 0, 1 and 2 and nothing else; eight of the second, eight of which are known, leaves nothing for a ninth name to be. A field a record has not got comes back as undefined rather than being left out, which is a guarantee instead of a silence. Subsets that between them cover a record leave it known entire, without anyone deciding to.
  • Identity is not a spelling. Key and Equal decide whether two values are the same value, and they owe nothing to any grammar: text and bytes are different kinds, 3 and 3.0 are different numbers, and floats compare by their bits so that a key is reflexive.

Docs

  • docs/sources.md — what a source is, what a format loads into one, how several compose under names of their own, and what a scope costs at either end of a long sequence.
  • docs/ordering.md — the comparison and membership rules, which two holders of one sequence have to compute the same way without conferring.
  • docs/trees.md — built over records that are here: a tree as a source, presenting the rows that are visible flat, so that a scrollbar, a thumb drag and a cache work over one without knowing it is one.
  • docs/census.md — built over records that are here: RecordCount partitioned by a field's value, so that a page of counts is one question rather than a page of questions.

Use

import "github.com/phroun/serval"

src := serval.NewCachedSource(serval.NewPSLSource(node, serval.Whole))

set, err := src.Open(&serval.DataSetDescriptor{
    Sort: []serval.SortLevel{{Field: "name"}},
})
if err != nil {
    return err
}
defer set.Close()

// One screenful, then the next, from where the first one got to.
err = set.Read(&serval.Scope{Count: 30}, sink)
err = set.Read(&serval.Scope{After: watermark, Count: 30}, sink)

A sink takes the records one at a time and is told what ended them:

type Sink interface {
    Ordered()                                  // before the first record, or not at all
    Record(id *serval.Value, f serval.Record) error  // the record entire
    Subset(id *serval.Value, f serval.Record,        // only what was asked for,
           has serval.Totals) error                  // out of this many members
    Done(c serval.Complete)
}

Test

go test ./...

License

MIT — see LICENSE.

Documentation

Index

Constants

View Source
const (
	CensusValue = "value" // the distinct value, which is also the record's identity
	CensusCount = "count" // how many records hold it
	CensusExact = "exact" // whether that count is the whole figure or a floor
)

The fields a census record carries. They are bare rather than dotted because a census MAKES its records, and a dot is how a name says it is a member of a record somebody else wrote.

View Source
const (
	CollateExact   = "exact"   // codepoint order, rune by rune
	CollateFold    = "fold"    // ASCII case folded, then exact
	CollateNatural = "natural" // digit runs as numbers, then fold
)

The collations text may be compared under. A symbol and a run of bytes take none: a symbol is a name, and bytes are not text.

View Source
const (
	RankUndefined = iota
	RankNil
	RankFalse
	RankTrue
	RankNumber // int and float share one rank and interleave by value
	RankSymbol
	RankString
	RankBytes
	RankUnordered // no order of its own; the sort's last level settles it
)

A value's rank, which its kind decides before anything in it is read.

View Source
const (
	KeyField   = "key"
	ValueField = "value"
)

The two names the Whole reading gives a record's own key and value.

These are fields, and only fields. A record read this way carries `key` because the document gave it one and somebody may want to sort or show it -- not because this end works by it. What identifies the record travels beside the bag, and a document is free to hold a member called `key` of its own without the two ever meaning the same thing.

View Source
const (
	OpAnd      = "and"
	OpOr       = "or"
	OpNot      = "not"
	OpEq       = "eq"
	OpNe       = "ne"
	OpLt       = "lt"
	OpLe       = "le"
	OpGt       = "gt"
	OpGe       = "ge"
	OpIn       = "in"
	OpContains = "contains"
	OpStarts   = "starts"
	OpEnds     = "ends"

	// OpHas and OpLacks ask whether a record carries a field at all, and take
	// no value. Every other operator compares one, and a field holding
	// something with no order of its own -- a nested list -- cannot be
	// compared, so presence needs an operator that does not try.
	OpHas   = "has"
	OpLacks = "lacks"

	// OpID matches a record's identity against a set of them, the way `in`
	// matches a field against a set of values. It names no field, because an
	// identity is not one: it travels beside a record's fields rather than
	// among them, and a field called `key` is a field like any other.
	OpID = "id"
)

The operators a filter is built from.

View Source
const MostRevisits = 3

The most laps anybody may ask for. The reason to allow any is to make a cycle VISIBLE, and nobody has ever needed a fourth to see one.

Variables

View Source
var TreeFieldsDefault = TreeFields{
	Depth:      "depth",
	Path:       "path",
	Expandable: "expandable",
	State:      "state",
	Kind:       "kind",
	Chain:      "chain",
}

TreeFieldsDefault is what a tree writes where the caller says nothing.

Functions

func AppendKey

func AppendKey(b []byte, v *Value) []byte

AppendKey appends a value's Key to b, for a caller building a key out of more than one thing. A table keyed by a sequence and an identity wants one buffer and one string, not a key per part and a join afterwards.

func Arrives

func Arrives(src Source) bool

Arrives reports whether a source may answer AFTER its read returns -- itself, or anything it wraps.

**It is not the same question as "does it implement Arriving", and that is the whole reason it exists.** A wrapper hands the notice on, so it implements the interface whatever its child does: a cache over a list of records in hand is `Arriving` and will never fire, because there is nothing under it to fire.

Asserting the interface to decide whether to WAIT is therefore wrong, and wrong in the expensive direction. A tree that concluded its levels might answer late walked on a goroutine of its own and returned no rows, expecting a notice to bring the reader back -- and no notice ever came, the records having been there all along. The tree read empty forever. That is what this is for; see `answersLater`.

A wrapper is asked about its child. Anything else is asked whether it can arrive at all, which for a source that is not one of these wrappers is the same question.

func CacheCost

func CacheCost() int

CacheCost is how much of that is in use.

func CacheLimit

func CacheLimit() int

CacheLimit is how much the process holds, and SetCacheLimit changes it, evicting down to the new figure at once. It is one figure for every CachedSource there is.

func Compare

func Compare(a, b *Value, collation string) int

Compare orders two values: -1, 0 or 1. The collation applies to the string rank alone and is CollateExact when empty.

Two values of different ranks are decided by the ranks. Two of the same rank are decided by what is in them, except the ranks that hold one value each (undefined, nil, false, true) and the unordered rank, which compare equal and leave the answer to the sort's last level.

func CompareLevels

func CompareLevels(a, b []*Value, levels []Level) int

CompareLevels walks the levels in order and answers on the first that separates the two runs, so each level settles only what the ones above it left equal. A run shorter than the levels stops where it ends.

The caller appends the record's identity as a final level: two records equal on every level a sort names must still have an order, or "the record after this point" names more than one place.

func Equal

func Equal(a, b *Value) bool

Equal reports whether two values are the same value.

Three things it decides, and each is written down in 0_identity_test.go so that changing one is a deliberate act:

  • Text and bytes of the same content are NOT the same value. They are different kinds -- one is characters and one is not -- and they do not even sort together.
  • An integer and a float of the same magnitude are not the same value either. 3 and 3.0 are different numbers here, and a boundary that changed type between two holders of the same sequence would be comparing different things.
  • Floats compare by their bits and not by ==, which makes NaN equal to itself and 0.0 different from -0.0. That is not a preference: a key must be reflexive, or a record whose identity does not equal itself is filed and never found again, and == on NaN gives exactly that.

func FilterKey

func FilterKey(f *Filter) string

func Key

func Key(v *Value) string

Key is a value's canonical form: two values have the same Key exactly when Equal says they are the same value.

Self-delimiting, so keys can be concatenated -- which is what a table keyed by more than one thing does, and what a list of them does inside one key.

func Match

func Match(id *Value, rec Subject, f *Filter) bool

Match reports whether a record passes a filter. A nil filter passes everything, which is what an unfiltered sequence is.

The identity comes in beside the record rather than as a field of it, because that is what it is: `id` asks about the identity, and every other operator asks about a field. A record whose fields happen to include one called `key` is answering an ordinary question about an ordinary field. Pass nil for id where there is none, and `id` then matches nothing.

func MemberIndex

func MemberIndex(name string) (int, bool)

MemberIndex reads a field name as an ordered member's index.

An ordered member is named by its position written out -- `0`, `1`, `2`, or `.0`, `.1`, `.2` where the source marks its members with a leading dot -- and only in that spelling: no sign, and no leading zeros but `0` itself. So `007` is a NAME that happens to be digits rather than the member at seven, which is the same distinction everything else here makes between a name and a number.

**This is the rule, and a source counting its members has to count by it.** Totals that said a record had no ordered members while naming one `.0` would have this answer "index 0 is not there" about a member that is, which is the one way a total can be worse than no total at all. Tally counts by this rule and is what a source should use.

func ParseDelimited

func ParseDelimited(text string, opts Delimited) (*ListSource, []Complaint, error)

ParseDelimited reads a delimited file as records.

The error is for text that is not rows at all. Everything else is a complaint, and a file that produced complaints is still a source.

func Rank

func Rank(v *Value) int

Rank is where a value sits before its contents matter. A value that is not there at all is undefined, which is the bottom of the order.

The kind decides it outright. Nothing is read out of the value and nothing is spelled: which rank a value has is a fact about what it IS.

func Segment

func Segment(v *Value) string

Segment is a value as a piece of a path: the characters themselves, and not the readable rendering.

Value.String quotes text, which is right for standing and wrong here -- a path segment is what the field HOLDS. Nothing is parsed back out of this: it is compared, and joined, and used to key a mark.

func SetCacheLimit

func SetCacheLimit(n int)

func SortKey

func SortKey(levels []SortLevel) string

SortKey and FilterKey name the other two halves of a prepared sequence, for the same reason and on the same terms. A sequence is a source, a sort and a filter, and whatever holds one per sequence has to be able to say when two are the same sequence.

func TellOnArrival

func TellOnArrival(src Source, tell func()) bool

TellOnArrival asks a source to say when records land, and reports whether it can.

False is not a failure: it is a source that answers `Read` before it returns, which needs no telling and is the ordinary case. A reader that gets false has nothing to arrange and reads as it always did.

Types

type AmendedSource

type AmendedSource struct {
	// What it says its records ARE, where somebody has said; see treehint.go.
	HintSaid
	// contains filtered or unexported fields
}

An AmendedSource holds replacements and deletions against a child's records.

func NewAmendedSource

func NewAmendedSource(child Source) *AmendedSource

NewAmendedSource amends the records of a child source. The child is any kind -- records here, records an application's, or another amended source.

func (*AmendedSource) Add

func (a *AmendedSource) Add(key *Value, fields Record)

Add says this source holds a record of its own under this key.

It is not a statement about anything the child holds: the key is the author's to choose, and keeping it clear of the child's is the author's to do. Where it turns out not to be clear, the child's record is the one that stands and this one is dropped for good -- the opposite of Replace, which displaces whatever the child has.

func (*AmendedSource) Alter

func (a *AmendedSource) Alter(key *Value, members Record)

Alter says SOME of a record changes and the rest is the child's.

The one amendment that is not a record entire, and it is what a cell edit is: a reader changed one member of one row and said nothing whatever about the others. Stating it as a Replace would mean holding the whole record to state it with, and a reader that has drawn five columns of a forty-member record has not got one.

Three things follow from it being partial, and they are the reason it is worth having rather than a convenience:

  • **It cannot move the record.** The child applies the filter and the sort and places the record; this touches what the record HOLDS afterwards. So a row stays where it was until something asks the question again -- which is what a reader editing a cell wants, the row not leaping away under the cursor.
  • **It cannot change how many there are.** The child counted it and still does.
  • **It means nothing for a key the child does not send.** There is no record to alter, so nothing goes out. That is the opposite of Replace, which stands on its own, and it is why an alteration is not a way to add anything.

**Altering accumulates.** Two edits to two columns of one row are two calls, and the second must not lose the first -- so the members are merged into whatever is held: into an alteration, into a replacement's or an addition's own fields (both being records entire, a member of one is well defined), and into nothing at all for a key that was deleted, the record being gone.

func (*AmendedSource) Amended

func (a *AmendedSource) Amended() bool

Amended reports whether anything is held at all, for a caller asking whether there is anything to save.

func (*AmendedSource) Amendment

func (a *AmendedSource) Amendment(key *Value) (Amendment, bool)

Amendment is what is held against ONE key, and false where nothing is.

The per-key form of Amendments, for a reader that has a record in hand and wants to know whether this source has anything to say about it. A caller with a flattening taken before an amendment was made is reading a snapshot that predates it, and this is how the snapshot is corrected without asking the whole sequence again -- the amendment being the authority over anything older than itself.

func (*AmendedSource) Amendments

func (a *AmendedSource) Amendments() []Amendment

Amendments is every statement this source holds.

**It is here because an amendment is meant to outlive the session that made it.** A reader edits a cell, the edit is held here, and somebody eventually has to write it to a file or a database -- which is impossible if the only way to see one is to read the records back and diff them against a child that has meanwhile moved on. So what is held can be asked for.

Ordered by key, which is not the order they were made in and is deliberately not: the map holds one statement per key, so the making order has already been collapsed, and reporting some order would suggest a history that is not kept. A stable one is what a caller writing a file wants.

func (*AmendedSource) Delete

func (a *AmendedSource) Delete(key *Value, known Record)

Delete says a record is gone.

Known is what was last seen of it, and may be nil. With it, the scope that record would have fallen in is known before the child is asked; without it, the shortfall is discovered afterwards and costs a second question -- which is also where the fields to remember are learned.

func (*AmendedSource) Forget

func (a *AmendedSource) Forget(key *Value)

Forget drops an amendment, leaving the child's own record to stand.

func (*AmendedSource) Open

func (a *AmendedSource) Open(descriptor *DataSetDescriptor) (DataSet, error)

Open states a sequence, and opens the same one on the child.

func (*AmendedSource) Replace

func (a *AmendedSource) Replace(key *Value, fields Record)

Replace says a record now carries these fields, whatever the child holds.

These are the record entire, not a correction to some of it: what goes out for this key is exactly what is stated here, and it goes out as a whole record.

func (*AmendedSource) Stale

func (a *AmendedSource) Stale(key *Value)

Stale says the child's own record under this key may have changed, so what was shadowed of it is no longer to be trusted.

This source holds no runs and no values, so it needs no extent and no reason: what it keeps of the child is one record per amended key, and either that is still right or it is not. Told, never decided -- the same rule as everywhere, because a shadow is knowledge like any other and goes stale like any other.

A count that was exact because of a shadow falls back to a floor, and a deletion that had a placement goes back to being ruled out of no scope.

func (*AmendedSource) TreeFields

func (a *AmendedSource) TreeFields() TreeFields

func (*AmendedSource) TreeHint

func (a *AmendedSource) TreeHint() TreeHint

TreeHint: an amendment's own where one was said, and otherwise the child's.

**Amending a record does not reshape it.** A replacement is a record in the same world as the ones around it -- that is what makes it a replacement rather than a different source -- so a child that says its records are a hierarchy is describing the amended ones too.

Its own wins because it is the more specific saying: whoever assembled this layer and described it knows what the layer is, and a bundle is exactly that case.

func (*AmendedSource) WhenArrived

func (a *AmendedSource) WhenArrived(tell func())

WhenArrived passes the notice on to the source this amends. The amendments are here and they do not arrive; the records do.

type Amendment

type Amendment struct {
	// Key is the record this is about.
	Key *Value

	// How is which of the four things has been said about it, in the same words a
	// source uses to ANNOUNCE one: Added, Removed, Replaced, Altered.
	//
	// **They are the same four facts, so they are the same four words.** A
	// `Change` is one of them told to a reader; an amendment is one of them held
	// against a child. Giving the held version four words of its own would mean
	// two vocabularies for one idea, and a reader turning `Delete` into "removed"
	// by hand.
	How Change

	// Fields is the record entire for Replaced and Added, the members that change
	// for Altered, and nil for Removed -- the record being gone, there is nothing
	// of it to state.
	Fields Record

	// Clashed marks an addition whose key turned out to be the child's after all.
	// The child's record stands and this one no longer goes out, so it is here to
	// be seen and dealt with rather than written out as though it were in force.
	Clashed bool
}

An Amendment is one statement this source holds, as a caller reads it back.

A copy, so that amending afterwards does not change one already handed out.

type Arriving

type Arriving interface {
	// WhenArrived adds something to be told once an answer has landed. It ADDS,
	// because several readers may share one source.
	//
	// It is called on whatever thread the records arrived on. A reader that
	// cannot be touched from there is the one that knows so, and arranges
	// accordingly.
	WhenArrived(tell func())
}

An Arriving source can say when records have landed.

Optional, as every one of these is: a source that does not implement it answers at once, and `TellOnArrival` is what a reader asks rather than asserting.

type Branch

type Branch struct {
	// Kind is the node type the children are.
	Kind string

	// When is a predicate on the PARENT. Nil is always, which is what makes the
	// unconditional case free -- Match passes a nil filter.
	//
	// It is a `*Filter` because that is what this library already says a
	// predicate with, down to the collation. `ends name ".zip"` needs nothing
	// new, and the alternative -- a Go function -- could not be written down in
	// a bundle later.
	//
	// **It tests the row's own values and not a field an author added.** The
	// override field exists for a row announcing something about itself, and
	// wanting the data to carry the view's vocabulary is what this avoids: a
	// filesystem listing has an extension and has never heard of a node type.
	When *Filter
}

A Branch is one kind of thing that hangs off a row, and when it does.

func Always

func Always(kinds ...string) []Branch

Always is the unconditional branches, which is the common case said shortly.

type CachedSource

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

A CachedSource answers out of the process's cache wherever it can, and asks the source it wraps wherever it cannot.

func NewCachedSource

func NewCachedSource(child Source) *CachedSource

NewCachedSource wraps a source.

Wrap once and keep the wrapper: it is the name under which everything it learns is filed, so a second wrapper over the same source starts knowing nothing.

func (*CachedSource) Covers

func (c *CachedSource) Covers(descriptor *DataSetDescriptor) []Extent

Covers is what this wrapper currently depends on for one sequence: the stretches of it being held.

**The stated coverage is a superset of what is actually depended on**, which is the rule the whole arrangement stands on. Stating more is safe -- a staleness reported for something already let go of is ignored. Stating less is a missed invalidation, which is silent and permanent. These are taken from what is held, so they are exactly it, which satisfies the rule with nothing to spare and nothing to get wrong.

**In no particular order.** They are stretches of one sequence, but which stretch comes before which is a question about the ORDER, and comparing two identities needs the records they name. Whoever asked holds that order and can sort them; this cannot.

func (*CachedSource) Open

func (c *CachedSource) Open(descriptor *DataSetDescriptor) (DataSet, error)

Open states a sequence, and opens the same one on the child.

The child is opened whether or not anything is ever asked of it. Opening is where a source REFUSES a sequence it cannot produce exactly, and a refusal that only arrived once the cache happened to miss would be a refusal that depended on what was in memory.

func (*CachedSource) Stale

func (c *CachedSource) Stale(descriptor *DataSetDescriptor, n Notice)

Stale tells this wrapper that something it may be holding is no longer true.

The descriptor names the sequence the extent is stated in, and a nil one says the notice is about records and not about anyone's order -- which is what a record known to the source but placed in no live sequence needs, there being no stretch to name it in.

Nothing is fetched. What is no longer true is let go of.

func (*CachedSource) TreeFields

func (c *CachedSource) TreeFields() TreeFields

func (*CachedSource) TreeHint

func (c *CachedSource) TreeHint() TreeHint

TreeHint: a cache holds the same records under the same keys, so whatever the child says about their shape is true of what comes out.

func (*CachedSource) WhenArrived

func (c *CachedSource) WhenArrived(tell func())

WhenArrived passes the notice on to whatever this cache wraps.

A cache does not fire one of its own: what it holds it holds, and an answer landing at the far end is the CHILD's news. Telling the cache its runs are stale is a different saying with a different name -- see invalidate.go.

type Census

type Census struct {
	// Groups, in the order of their values -- which is an order and not an
	// arrangement, so that two censuses of one sequence are the same answer and
	// a test can say what it expects. A caller wanting them by size sorts them,
	// or reads them through CensusSource and says `sort={ count desc }`.
	Groups []Group

	// Total is how many distinct values there are: Exactly(n) meaning these n
	// and no others, AtLeast(n) meaning at least these.
	//
	// It is the second of the three claims, and it is why this is a struct
	// rather than a slice. A slice of exact counts cannot say "and there may be
	// more groups I have not seen", which is precisely what a source that has
	// read part of a sequence has to be able to say.
	Total RecordCount
}

A Census is the answer: the groups, and whether they are all of them.

func CensusOf

func CensusOf(v DataSet, field string) (Census, error)

CensusOf partitions a data set's sequence by a field, and says it cannot for a data set that is not Censusing.

The error is the point. CountOf answers Unknown for a set that cannot count, because a floor of nought is a true statement about every sequence; there is no such harmless answer here, an empty census claiming there are no groups at all. So this refuses instead, and a caller falls back to whatever it had.

func (Census) CountOfGroup

func (t Census) CountOfGroup(v *Value) RecordCount

CountOfGroup is the count for one value, and the honest answer for a value this census has not got: nought exactly where the census is complete, because then there is nothing left for it to be, and Unknown where it is not.

This is the question a tree actually asks -- has this row any children, and how many -- so it is worth answering here rather than at every caller, where the difference between "no children" and "I did not see" would get lost.

func (Census) Group

func (t Census) Group(v *Value) (Group, bool)

Group finds one value's group, and reports false for a value this census has not got -- which, where Total is exact, is a count of nought and not an ignorance.

type Censusing

type Censusing interface {
	Census(field string) (Census, error)
}

A Censusing data set can partition its sequence by a field and count each part.

Optional, and deliberately so, on the model of Counting: the least a data set can do stays one method, and one that cannot take one simply is not one of these. Ask with CensusOf.

**A data set that cannot take one says so rather than walking.** Over records in hand the work is a pass over records already here, which is exact and cheap. Over a source that must be ASKED and cannot, walking the sequence to count it is precisely what the caller was avoiding -- and a census that quietly became a full scan would be fast in testing and ruinous in use.

type Change

type Change int

A Change is why what is held is no longer true.

const (
	// Added: a record appeared. It names no record anyone holds, so the extent
	// names the two it fell BETWEEN -- First the one it falls after, Last the
	// one it falls before -- either of which may be absent where it fell past
	// an end.
	Added Change = iota

	// Removed: a record has left the sequence altogether.
	Removed

	// Replaced: a record is still there and everything about it may be
	// different, which is the same as every field having changed.
	Replaced

	// Altered: the named fields may have changed. MAY, not did -- a source that
	// cannot tell exactly which is welcome to name more than changed, or to say
	// Replaced and be done. Over-stating costs a refill; under-stating is a
	// wrong answer nobody finds out about.
	Altered
)

func (Change) String

func (c Change) String() string

type ColumnKind

type ColumnKind int

A ColumnKind is what a declared column holds, whatever its cells look like. It is the column that says so, so quoting in a declared column is transport and carries no meaning of its own.

const (
	// Inferred is the cell's own business: see Delimited.Infer.
	Inferred ColumnKind = iota
	AsString
	AsSymbol
	AsNumber
	// AsBytes reads the wire's escapes whatever Escapes says, because \xNN is
	// the only way a cell can spell a byte.
	AsBytes
)

type Complaint

type Complaint struct {
	Row    int    // the row it was in, counting data rows from zero
	Column string // the column, as the rows name it
	Cell   string // what was there
	Reason string
}

A Complaint is one thing that was wrong with one cell or one row.

It is a report and not a refusal: the cell is left undefined, the row is kept, and the load carries on. One bad cell in a long file costs that cell.

func (Complaint) String

func (c Complaint) String() string

type Complete

type Complete struct {
	Watermark *Value
	Stop      Stop

	// Total is how many records the whole SEQUENCE has, where the source knows
	// and volunteers it.
	//
	// Optional. A source that cannot count cheaply says nothing, which is
	// Unknown; one that can says it on an answer it was sending anyway rather
	// than waiting to be asked. It is a fact about the ORDER, which is why it
	// rides here beside the watermark and the ending word -- and about the
	// sequence rather than this scope of it, how many came back being something
	// whoever asked can count.
	Total RecordCount

	// First is where in the sequence the answer actually started: the position
	// of the first record it carried, counted in the sequence's own order
	// however the scope walked it.
	//
	// It is the calibration, and it is nearly free wherever it is possible at
	// all -- a source that resolved a start knows what it resolved. A reader
	// that asked After some record it holds learns where that record stands
	// without a question of its own; a reader that asked From a position learns
	// whether it got there. Beside Total, the two say how long the sequence is
	// and where in it this answer sits, which between them are a scroll thumb.
	//
	// **Unknown is an answer and is never inferred.** A source that cannot say
	// says nothing, and a reader told nothing stays where it was rather than
	// believing a number that was not sent. An answer that carried no records
	// has no first record and says Unknown for that reason alone.
	//
	// **Exactly or Unknown, and never a floor.** A count can honestly be a
	// floor -- part of a sequence seen is at least that many -- but a position
	// cannot be reckoned the same way: a source either resolved where it began
	// or it did not, and "at least the six hundredth" is not something a reader
	// can put a thumb on. It shares Total's type for the vocabulary, not for
	// the middle value.
	First RecordCount

	// Error is a refusal, which is an answer: this scope cannot be produced,
	// the records are gone, whatever held them is no longer reachable.
	// Whoever asked carries on with what it has.
	Error string
}

A Complete ends a scope: which of the three ways it ended, and how far the answer is complete.

Watermark says there is nothing between where the scope was asked from and that record that the asker does not now have. StopExhausted carries none, because there is no point past the end to be complete up to.

type ComposedSource

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

A ComposedSource answers out of several sources at once.

func NewComposedSource

func NewComposedSource(includes ...Include) (*ComposedSource, error)

NewComposedSource composes sources under the names their records go out under.

The includes are fixed for the life of the source. A sequence opened over it opens one over each of them, so a set that changed underneath would leave a sequence answering out of children it never opened.

func (*ComposedSource) Includes

func (c *ComposedSource) Includes() []Include

Includes are the sources this one is made of, in the order they were given.

func (*ComposedSource) Open

func (c *ComposedSource) Open(descriptor *DataSetDescriptor) (DataSet, error)

Open states a sequence, and opens it on every include that could hold anything the filter admits.

Each include gets the filter read in its own terms: an identity this source made says which include it came from, so `filter={ id (left/1) }` asks `left` about its own record 1 and never opens `right` at all.

func (*ComposedSource) Stale

func (c *ComposedSource) Stale(key *Value)

Stale says a record this source has placed may no longer be where it was.

It needs no extent and no reason. What this source holds of a record is one note -- where it stood, and where each include stood as it went out -- and either that is still right or it is not. Told, never decided, the same as everywhere else.

**Forgetting a note is safe, which is what makes this cheap.** A note says where this source WAS when it last spoke about a record, so a scope resuming from one it has not got is REFUSED rather than answered wrongly. The whole price of forgetting too much is a refusal, and a refusal is an answer.

It is answered against no sequence, because a record moving is a fact about the record: every order over this source placed it, and every one of them placed it wrongly.

The note of it, and the notes that mention it

Two different things go, and the second is the one that is easy to miss.

The note OF the record is obviously wrong: it says where that record stood and where the includes stood as it went out, and it may have moved.

But a note of ANY OTHER record may name it too. A note's vector holds each include's own cursor -- the last record that include had handed over at that moment -- and this record may be one of them. Resuming from such a note would ask that include to carry on past a record that is no longer where it says, and the include would answer from somewhere else without anyone finding out. So those go as well, and finding them is a walk of a bounded book.

The tuple of where a record STOOD needs no such walk: it is that record's own values and says nothing about anybody else's.

func (*ComposedSource) TreeFields

func (c *ComposedSource) TreeFields() TreeFields

func (*ComposedSource) WhenArrived

func (c *ComposedSource) WhenArrived(tell func())

WhenArrived passes the notice on to every include that can arrive.

**Any one of them is enough**, because a composition's answer is drawn from all of them: a record landing in one changes what the whole says, and a reader that re-reads on it reads the composition rather than the child. So there is no need to know WHICH arrived, and nothing here keeps track.

type Counting

type Counting interface {
	RecordCount() RecordCount
}

A Counting data set knows how many records its sequence has.

Optional, and deliberately so: the least a source can do stays one method, and a source that cannot count simply is not one of these. Ask with CountOf, which answers Unknown for anything that is not.

type Criterion

type Criterion struct {
	// Of is the question for one node: a DataSetDescriptor naming that node's children.
	Of func(of Node) *DataSetDescriptor

	// Over and By are how a whole level is counted in one question: the
	// sequence every one of these children is drawn from, and the field saying
	// which parent each belongs to. Group is the value naming one node's part
	// of that census.
	//
	// **Only a criterion that PARTITIONS can be censused.** A census puts each
	// row in exactly one group, so a criterion that puts a row in several
	// nodes' answers -- a subtree, where every ancestor claims it -- cannot use
	// one, and says so by leaving these out rather than by counting wrongly.
	Over  *DataSetDescriptor
	By    string
	Group func(of Node) *Value
}

A Criterion says which rows are a node's children -- and, where it can, how a whole LEVEL of that question is answered at once.

The three census parts are here rather than on the node type because they have to agree with Of and with each other, and a place where three things must agree is a place to put them together. A criterion that cannot be censused leaves them out, and every node is then counted on its own.

func ChildrenByKey

func ChildrenByKey(field string) Criterion

ChildrenByKey is `eq <field> <the parent's key>`: an adjacency list, and the commonest hierarchy in a database.

It needs no path, no delimiter and no address field. The parent's identity is compared against the child's parent column, and that is the whole of it.

It partitions -- every row has one value under the field, so it belongs to one parent -- so a census of the source by that field answers every node at once.

func Sorted

func Sorted(by Criterion, levels ...SortLevel) Criterion

Sorted puts a sort on whatever a criterion produces.

**A node type may order its children differently from its parents, and this costs nothing** -- a node type produces a whole DataSetDescriptor, and a DataSetDescriptor is a source, a filter and a sort. Windows in z-order under applications in name order needs nothing added. It is written down because a capability nobody notices gets reinvented. The census parts come through untouched, a count belonging to the filter and not to the order: sorting the same records cannot make there be more or fewer.

func (Criterion) Check

func (c Criterion) Check() error

Check refuses a criterion whose census parts do not all agree to be there. Two of the three is a caller who meant to have one and will silently not.

type DataSet

type DataSet interface {
	// Read asks for one scope of the sequence and says where to put it.
	//
	// It does not wait for the answer. Records reach the sink as they are
	// produced -- immediately, for a source whose records are here; as they
	// arrive, for one whose records are somewhere else -- and the sink is told
	// what ended it when it ends. The error is for a request that could not be
	// started at all, never for one that has not finished.
	//
	// A scope names its ends by identity, and an identity means something only
	// to the source that issued it. So a scope handed here was issued here: a
	// source made of several others translates rather than relays.
	Read(s *Scope, out Sink) error

	// Close lets the data set go, and with it whatever it was holding.
	Close()
}

A DataSet is one stated sequence -- this source, this sort, this filter -- prepared once and drawn from until it is let go. It is what a query names, seen from the end that holds the records.

Those three name it, and nothing else does: the fields a query asks for change what a scope carries rather than which records are in the sequence, and the direction belongs to the scope. So two queries naming the same three are reading one data set, and whatever was worked out for either of them -- the ordering, where each record stands -- holds for both.

It does not change. A different sort or a different filter is a different data set, opened alongside this one and taking its place -- which is also what keeps the source in use while the reader moves from one to the other.

type DataSetDescriptor

type DataSetDescriptor struct {
	Source  string
	Fields  Record // the fields asked for; empty means whatever the record has
	Exclude Record // the fields not wanted, valued the same way
	Filter  *Filter
	Sort    []SortLevel
}

A DataSetDescriptor says what sequence a query names: which records, in which order.

Fields and Exclude are what the asker WANTS. What a source carries back may be any superset of that -- sending more than was asked for is always allowed -- but what it CLAIMS to carry must be true, because an answer kept against a later question is answered from what it claims.

func (*DataSetDescriptor) Roles

func (s *DataSetDescriptor) Roles() Roles

Roles groups this descriptor's fields by the part each plays.

The names are in the order they were first met -- sort levels top down, then the filter tree -- and each appears once per group. A stable order matters because this is stated over and over as a sequence is read, and a set that reshuffles itself makes every statement look like a change.

type Delimited

type Delimited struct {
	// Delimiter separates cells: one character, and one per file.
	Delimiter rune

	// Quote begins and ends a quoted cell, and doubled inside one stands for
	// itself. A quoted cell may hold the delimiter and may hold newlines.
	Quote rune

	// Header says the first row names the columns. Without it they are named
	// `.0`, `.1` and so on, which is how a position is named everywhere else.
	Header bool

	// Escapes reads the wire's escape set inside a quoted cell: \\ \" \n \t \r
	// \e and \xNN. It is OURS and not a spreadsheet's -- leave it off for
	// anything Excel or LibreOffice wrote, or a cell holding a Windows path
	// will be read as escapes it never meant.
	Escapes bool

	// Infer reads a bare cell by the rule the wire reads a bare token by: a
	// number where it spells one, a symbol otherwise. Off, every bare cell is
	// text, which is what a spreadsheet meant by it.
	Infer bool

	// Identity says what names a row.
	Identity Identity

	// Value names the column that becomes the record's `value` rather than a
	// member of its own, the way a scalar record carries one.
	Value string

	// Columns declares what a column holds, by the name the row names it.
	Columns map[string]ColumnKind

	// Comments begin a line that is not a row: "#", ";", "--", whatever this
	// file uses. They are prefixes rather than characters because "--" is two.
	//
	// A line counts as a comment only where a ROW could have started, so a
	// prefix inside a quoted cell -- including one spanning several lines -- is
	// part of the cell and not the start of a remark.
	Comments []string
}

Delimited is how to read one file.

func CSV

func CSV() Delimited

CSV is what a spreadsheet writes: commas, quotes where they are needed, no escapes, and every bare cell text.

func TSV

func TSV() Delimited

TSV is the same with tabs.

type Extent

type Extent struct {
	First, Last *Value
}

An Extent is a stretch of one sequence that is currently depended on.

Named by its ends the way a scope is -- identities, never positions -- and both ends INCLUSIVE, both being records that are actually held. A scope's ends say where to start and stop walking, which is a different question and is why they are exclusive; an extent says what is covered.

type Field

type Field struct {
	// Name is what the member is called, or "" for a member that has no name of
	// its own and stands by its position.
	Name string

	// Value is what is under the name. Nil is undefined, which is a value a
	// field can have: a name present with nothing under it.
	Value *Value
}

A Field is one member of a record: a name, and what is under it.

func At

func At(v any) *Field

At is one positional member, which is a field with no name.

func Named

func Named(name string, v any) *Field

Named is one field, built from an ordinary Go value.

type Filter

type Filter struct {
	Op       string
	Field    string    // predicates: the field being tested
	Values   []*Value  // what it is tested against; more than one only for `in`
	Collate  string    // text predicates: the collation, "" for the default
	Children []*Filter // and, or, not
}

A Filter is a tree: a predicate over one field, a test of a record's identity, or an and/or/not over other nodes.

The top of a built filter is an OpAnd, so there is one shape to walk whether the filter held one predicate or twenty.

func (*Filter) String

func (f *Filter) String() string

String is the filter as readable text, in the same shape Record uses: each node as an operator, the field it names and what it is tested against, and a braced block where a node has children. For an error message, a log line, or a test saying what it expected in one line -- never parsed back.

func (*Filter) Value

func (f *Filter) Value() *Value

Value is the single operand of a comparison, and nil where there is none.

type Group

type Group struct {
	// Value is what the field held. Groups are told apart by Key and Equal, so
	// 3 and 3.0 are two groups because they are two numbers, and 3 and "3" are
	// two groups because one is a number and one is text. Nothing else decides
	// this and nothing may -- a census that grouped by a spelling would merge
	// values the rest of the library keeps apart.
	//
	// Nil is `undefined`, which is a group like any other. A field a record has
	// not got reads as undefined rather than being left out, so the records
	// missing the field are countable and nameable rather than a silence to be
	// inferred from an arithmetic that does not add up.
	Value *Value

	// Count is how many records of the sequence hold it: Exactly where the
	// whole sequence was seen, AtLeast where only part of it was.
	Count RecordCount
}

A Group is one distinct value of a census field, and how many records of the sequence hold it.

type HintSaid

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

A HintSaid is what a source embeds to be able to say what its records are.

It is embedded rather than wrapped, and that is the point. A wrapper forwarding `Open` would have to forward every OPTIONAL interface too -- Counting, Censusing, TreeFielded, and whichever is added next -- and the one it forgot would silently degrade the source it wrapped. Embedding adds a method and changes nothing else.

**The hint is said when the source is assembled, before anybody reads it.** So there is no lock: whoever built the source is the only one holding it at that moment. A source whose shape changes after it has been handed out is not a thing this describes.

func (*HintSaid) SetTreeHint

func (h *HintSaid) SetTreeHint(hint TreeHint)

SetTreeHint says what this source's records are.

func (*HintSaid) TreeHint

func (h *HintSaid) TreeHint() TreeHint

TreeHint is what it was told, and the zero hint where nobody said.

type Hinting

type Hinting interface {
	TreeHint() TreeHint
}

A Hinting source says what its own records are.

Optional, as every one of these is: a source that says nothing about its shape is answered for by TreeHintOf, which is what a reader asks.

type Identity

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

An Identity says what identifies a row. The zero value is the row's index, which is what an authored file usually wants: these are versioned documents rather than tables somebody inserts into, so a row's position is its name.

func IdentityAt

func IdentityAt(i int) Identity

IdentityAt identifies a row by a column's value, by where the column stands.

func IdentityColumn

func IdentityColumn(name string) Identity

IdentityColumn identifies a row by a column's value, named as the row names it: a header, or `.2` where there are no headers.

func RowIndex

func RowIndex() Identity

RowIndex identifies a row by where it stands, counting from zero.

type Include

type Include struct {
	Name   string
	Source Source
}

An Include is one of the sources a ComposedSource is made of, under the name its records go out prefixed with.

type Kind

type Kind int

Kind is what a value is.

const (
	NilValue    Kind = iota // nothing, deliberately: a field that has no value
	BoolValue               // true or false
	NumberValue             // an integer or a float; IsInt says which
	SymbolValue             // a name the data uses: an identifier, an enum
	TextValue               // text
	BytesValue              // bytes that are not text
	ListValue               // a record in its own right: a nested thing
)

type Level

type Level struct {
	Descending bool
	Collation  string
}

Level is one level of a sort, as it applies to a pair of values already taken from the two records being ordered.

func Levels

func Levels(levels []SortLevel) []Level

Levels drops the field names, leaving what CompareLevels compares tuples by.

func Reverse

func Reverse(levels []Level) []Level

Reverse turns every level over, which is what walking a sequence from its end amounts to: the same records, in the opposite order, with the level that settles ties turned over as well so that nothing is left facing the way it was.

type ListSource

type ListSource struct {
	// What it says its records ARE, where somebody has said. Embedded rather than
	// wrapped so that nothing about how the list answers changes; see treehint.go.
	HintSaid
	// contains filtered or unexported fields
}

ListSource is a body of records held here, presented as a source.

func CensusSource

func CensusSource(t Census) *ListSource

CensusSource presents a census as a body of records: one per group, carrying the value and the count.

**A census is a sequence, so it is a source**, and then it needs no vocabulary of its own for anything a caller will want. A short list of large counts is `sort={ count desc }` with `count=20`; the biggest group is the same with `count=1`; groups over a threshold are a filter; how many distinct values there are is RecordCount. None of that is written here because none of it is new.

A group's identity is the value itself, which is unique by Key -- so nothing is synthesised, and a scope naming where to carry on from names a value the caller was already holding.

func NewListSource

func NewListSource(rows []Row) *ListSource

NewListSource presents rows already in memory as a source. The rows are the source's from here: it does not copy them and it does not change them. What it does with a different set of them is Restate.

func NewPSLSource

func NewPSLSource(n *pawscript.PSLNode, reading Reading) *ListSource

NewPSLSource presents an already-parsed PSL list as a data source: its items as records keyed by index, its keyed members as records keyed by name.

func NewPSLSourceExcept

func NewPSLSourceExcept(n *pawscript.PSLNode, reading Reading, skip ...string) *ListSource

NewPSLSourceExcept is the same, less the keyed members named.

A flat reading takes every keyed member for a record, `_bundle` included, because only the layer above knows which of them are ABOUT the document rather than in it. That layer says so here, and its records are then the document's own with nothing of the format's mixed in.

func ParsePSLSource

func ParsePSLSource(text string, reading Reading) (*ListSource, error)

A PSLSource is a data source backed by one parsed PSL list.

It is read-only and its records do not move, so everything computed from them stays true: an ordering is built once per descriptor and reused for every scope drawn from it. ParsePSLSource reads PSL text and presents it as a data source.

func (*ListSource) Len

func (l *ListSource) Len() int

Len is how many records the source holds, before any filter.

func (*ListSource) Open

func (l *ListSource) Open(descriptor *DataSetDescriptor) (DataSet, error)

Open states a sequence over the records: one filter, one sort.

func (*ListSource) Restate

func (l *ListSource) Restate(rows []Row)

Restate says the list is now these rows.

**It is told, never decided.** Nothing here watches the slice it was handed, compares a length or a generation, or looks again to see: whoever holds the rows says they have changed, and that saying is this. A list nobody tells holds what it holds, which is the same bargain a cache makes.

What it costs is the orderings: every sequence this source had worked out is forgotten, the records that decided where each one stood no longer being the records.

**A sequence already stated goes on answering the one it stated.** An ordering holds the rows it was built over, so a data set opened before this reads the list as it was -- asked once and answered once, which is the rule everywhere else and wants no exception here. A caller who wants the new rows states a new sequence; one holding a CachedSource in front of this tells it `Stale` so the runs it kept go too, and one holding a TreeSource tells that.

type Mark

type Mark int

A Mark is what a node's state can be.

const (
	// Closed: this node's children do not show. Nothing beneath a closed node
	// is reachable to be marked, which is why closing wipes.
	Closed Mark = iota

	// Open: this node's children show. Theirs follow their own marks, which
	// default to Closed -- so Open governs exactly one level.
	Open

	// OpenAll: this node and everything beneath it, until a deeper mark says
	// otherwise. It is a state and not an iteration: one mark, whatever the
	// size of the subtree.
	OpenAll
)

func (Mark) String

func (m Mark) String() string

type Marks

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

Marks is a tree's expansion: which nodes are open, held sparsely.

It lives on the SOURCE rather than on a data set, so two data sets over one descriptor cannot disagree about what the sequence contains. Two views sharing a tree share its expansion, which is sometimes exactly right; a view wanting its own wraps its own tree, which is cheap because wrapping is all it is.

The zero value is ready, with the top level showing and nothing deeper.

func (*Marks) Clear

func (m *Marks) Clear()

Clear is collapse-all: the top level shows and nothing deeper does.

It is its own verb rather than a coincidence of the root's state. Setting the root Open would be a no-op where it is Open already, which is exactly when a hundred marks beneath it are the thing to be rid of.

func (*Marks) Close

func (m *Marks) Close(chain ...string)

Close hides a node's children, and discards the expansion of everything held beneath it -- so collapsing a node and opening it again shows one level, not whatever was open before.

func (*Marks) Flat

func (m *Marks) Flat(chain ...string) bool

Flat reports whether every node beneath this one is closed: what this node's children inherit does not show, and nothing marked beneath it shows either.

**It is what lets a descent ask a level for a POSITION.** Where every row of a level stands for exactly one row of the flattening, the nth row of the level IS the nth row of that subtree -- so the level can be entered at a position rather than walked to it, and a reader a hundred thousand rows down costs one question instead of a hundred thousand rows. One open node anywhere beneath breaks that: its children stand between its siblings, and the arithmetic no longer holds.

It walks the marks and not the tree. What is held here is bounded by what somebody has TOUCHED, so this is a handful of nodes however large the sequence is -- the same property that makes Showing worth having.

func (*Marks) Held

func (m *Marks) Held() int

Held is how many nodes carry a mark of their OWN -- exceptions, not trie nodes. It is what "bounded by what somebody has touched" means when a test asks it, and the number that must not grow with the size of the tree.

func (*Marks) Mark

func (m *Marks) Mark(chain ...string) Mark

Mark is a node's state: its own if it has one, and what it inherits if it has not.

The chain is the segments from the root down to it, which a descent has in hand -- it got there by walking them.

func (*Marks) Open

func (m *Marks) Open(chain ...string)

Open makes a node's children show. Inside an OpenAll region this drops the node's Closed mark rather than adding an Open one, and the OpenAll resumes beneath it -- which is what somebody re-opening a folder they collapsed inside an expand-all expects to see.

The consequence worth naming: "open exactly one level, here, inside an OpenAll region" is a state this cannot express, because the verb that would say it is spent saying the other thing. It has not been wanted, and the alternative reading costs the interaction above, which is wanted constantly.

func (*Marks) OpenAll

func (m *Marks) OpenAll(chain ...string)

OpenAll expands a node and everything beneath it, in one mark.

func (*Marks) Set

func (m *Marks) Set(state Mark, chain ...string)

Set puts a node into a state, and is the one mutator the three verbs are made of.

**Nothing happens where the state does not change.** A click on an already open node must not wipe what is beneath it, and asking for the state a node is already in is the commonest way that would happen.

**Where it does change, the subtree is wiped and the mark is only kept if it says something.** Those are the two rules, and they are applied in that order: the marks beneath were exceptions to the state being replaced, and a mark equal to what would be inherited is not an exception at all.

func (*Marks) Showing

func (m *Marks) Showing(tell func(chain []string, state Mark))

Showing calls back for every node whose children are VISIBLE, with the chain down to it and the state it is in.

**Only the ones actually reachable.** A mark is an exception to what it inherits, so a node marked Open inside a Closed parent does not show and neither does anything beneath it -- and yielding it would describe a subtree nobody can see. So the descent here is the shape the row descent has: into a node's children only where that node shows.

The root is not yielded. Its children are the top level, which a sequence has whether or not anything is marked; a caller that wants the root's own state asks `Mark()` with no segments.

It is for working out how LONG a flattening is without walking it: the top level counts itself, and every node that shows adds its children. What makes that worth having is the second rule up at the top of this file -- what is held is bounded by what somebody has touched, so this walks a handful of nodes and not a tree.

func (*Marks) Shows

func (m *Marks) Shows(chain ...string) bool

Shows reports whether this node's children are visible, which is the question a tree actually asks of every row it draws.

type Node

type Node struct {
	Key    *Value
	Fields Record
	Path   string
}

A Node is a parent, as a node type sees it: what identifies it, what it holds, and where it stands.

The path is here because two of the three criteria are about it, and it is a STRING rather than a value because that is what a delimiter joins and a prefix bounds. Where a standing produces no path it is empty, and the criteria that use one are not the ones such a tree is built with.

type NodeType

type NodeType struct {
	// Source is where rows of this kind are read from. Nil is the tree's own
	// source, which is the nested case: a hierarchy inside one body of records.
	Source Source

	// Children is how rows of this kind are found under a parent. The zero
	// Criterion makes every node of this type a leaf, which is a legitimate
	// kind for a row to be and is how a tree says "nothing hangs off this".
	Children Criterion

	// Standing is how a row of this kind says where it stands, and it belongs
	// here rather than on the tree because it is a property of the SOURCE.
	Standing Standing

	// Then are the kinds the rows BENEATH this one are, by name. Empty is the
	// default kind, which is what makes a tree of one shape need no names at
	// all.
	//
	// **This is what lets a chain declare itself.** Without it, host ->
	// application -> window needs every host row to carry `applications` and
	// every application row to carry `windows` -- which means the applications
	// SOURCE has to carry the view's type names as data. The kinds are the
	// tree's own business, so the tree says them, and a row speaks up only where
	// it departs from its kind.
	//
	// **It is a LIST because one parent can have two kinds of children.** A host
	// has applications and it has volumes, out of two entirely different
	// sources, and neither is a special case of the other. Each branch is asked
	// for its own children of this parent, and they come back grouped in the
	// order named -- which is predictable, is what a view usually wants (every
	// folder, then every file), and asks nothing of the two sources that they
	// cannot answer. Interleaving them would need a sort field the two sources
	// share, and nothing says they have one.
	//
	// **And each branch may say WHEN it applies**, which is what makes a files
	// list work: a `.zip` takes its children from the archive, a `.ini` from its
	// sections, a folder from the listing, and a plain file has none. All four
	// are rows of one kind, out of one source, with one column mapping -- what
	// differs is decided by the row's own values, so it is a predicate and not a
	// second kind.
	Then []Branch
}

A NodeType is a KIND OF ROW: where rows of that kind come from, how one says where it stands, and what kind the rows beneath it are.

It was called a child type while the only thing it described was somebody's children, and the name was the parent's vantage point rather than the thing's own. A tree's parents are of a kind too -- a host row, an application row, a window row -- and anything a VIEW hangs off a type, a column mapping most of all, is about what a row IS and not about what hangs off it.

type NodeTypes

type NodeTypes struct {
	// Default is what a row that says nothing gets.
	Default *NodeType

	// Named are the others, by the name a row uses to ask for one.
	Named map[string]*NodeType

	// Field is the field a row OVERRIDES its children's kind in. Empty means no
	// row can, and every level takes the kind its parent's type named.
	//
	// It is an override and no longer the only way, which is the difference the
	// rename made: a row says something here when it is unusual -- a mount point
	// under a folder, an alias, a graft -- and says nothing when it is not.
	Field string
}

NodeTypes is the set a tree knows: one default, and any number named.

**That is what lets one tree mix kinds**: a host whose children are applications, an application whose children are windows, read out of three different places.

func (NodeTypes) Beneath

func (c NodeTypes) Beneath(of string, node Node) []string

Beneath are the kinds of the rows under this one: what its own type says comes next, unless the row itself says otherwise.

Four answers, and the middle two are the ones to get right:

  • no type at all above, which is the top level: the DEFAULT.
  • the field is absent, or undefined, which is a row saying nothing: the kinds its parent's type named. A field a record has not got reads as undefined everywhere in this library, so "says nothing" and "has not got it" are one case and must be.
  • the field says `false` or `nil`: no children, whatever its siblings do.
  • the field names a kind: that one alone -- or, where nothing is registered under the name, a leaf.

**An unknown name is a leaf and not a refusal.** One mistyped field must not empty a view, which is the same posture a sort takes towards a field a record has not got.

A row's override names ONE kind, where a type's Then may name several. A row departing from its kind is saying something unusual about itself, and wanting two unusual things at once has not come up; a type declaring the shape of a tree is the place where several is ordinary. It answers in NAMES rather than in types, because the name is what a reader of the flattened sequence needs: the descent knows a row's kind by construction -- whatever the `applications` criterion returned is an application -- and the name is how it says so, in the field the tree writes beside the depth.

func (NodeTypes) Check

func (c NodeTypes) Check() error

func (NodeTypes) Get

func (c NodeTypes) Get(name string) *NodeType

Get is the type of that name, and the default for an empty one.

type Notice

type Notice struct {
	Extent
	Change Change

	// Fields are the ones that may have changed, for Altered. A name that
	// decides the sequence costs the order; one that does not costs those
	// records' values and nothing structural. Naming none says the source
	// cannot tell, which is every field.
	Fields []string
}

A Notice is one thing a source says has happened to what is held.

The extent is by identity, the way everything here is. One record is First with no Last; a stretch is both, and is read in the order of the sequence the notice is answered against. Neither end is the whole of it -- every record the notice is answered against -- which is the widest thing a source can say and the most expensive.

type Placing

type Placing interface {
	Sink

	// Place is one record's position, and what is known of it so far.
	//
	// **Its fields are true and its silence is not.** What is here can be
	// believed, rendered and kept; what is missing is not a claim that the
	// record has not got it. That is the whole difference between this and a
	// subset, which says how many members there are and therefore how much it
	// left out.
	Place(id *Value, fields Record) error

	// Placed says the ORDER is settled: every record of this scope has been
	// named, under Place or under Record or Subset, and here is the same claim
	// Done will carry.
	//
	// It comes after the last place and before Done, and only where it says
	// something Done would not -- an answer with no places in it settles its
	// order at the end like any other, and says so once. Which matters because
	// a reader cannot lay out a sequence, not even one of placeholders, until
	// it knows no further record will turn up between two it already holds.
	Placed(c Complete)
}

A Placing sink takes PLACES as well as records: a record's identity, and whatever is known of it so far with no claim about how much that is.

It is the third degree of knowledge, beside a whole record and a subset that says how much it left out, and it is what lets an answer be started on before it is finished. What a reader needs first is almost never the values -- it is where the rows are. With the order it can lay its rows out and stay reactive under a scrub while the contents arrive behind them.

**Optional, and that is what makes it safe.** Places are ADDITIONAL, never substitutional: a sink that is not one of these is handed every record it would have been handed anyway, because whoever answers falls back to finding the values rather than to sending less. So there is nothing to negotiate -- a sink either has somewhere to put a place or it has not.

type Reading

type Reading int

A Reading is how a record's contents are named. Both are useful and neither is a subset of the other's behaviour, so it is said once, when the source is made, rather than guessed per record.

const (
	// Whole exposes the record entire. `key` is the document's own key for it
	// and `value` is its value, and every member wears a dot: `.size` is the
	// member called size, `.0` is the item at position 0.
	//
	// Nothing can shadow anything, so a member called `key` -- which the bundle
	// format's own `_bundle: (key: "figaro")` has -- is `.key` and is reachable
	// like any other. It is the reading for data whose records are not all the
	// same shape, and for anything that has to survive a round trip.
	//
	// `key` here is a field. It is what the document called this record, handy
	// to sort or show; what this end identifies the record by travels beside
	// the bag and is nobody's field.
	Whole Reading = iota

	// Members exposes the members alone, under their own names: `size`, not
	// `.size`. It is the shorter reading, and it suits a source whose records
	// are all lists of named fields -- which is most of them.
	//
	// It is deliberately not complete: the record's own key and value have no
	// name here, and positions have none either, a bare `0` being the number it
	// spells rather than a name. A member called `key` is a
	// member like any other and is reached and sent as one -- the point of this
	// reading is the document at its face value, and nothing in the bag is an
	// identity for it to collide with.
	Members
)

type Record

type Record []*Field

A Record is a run of fields.

func PSLFields

func PSLFields(key *Value, v any, reading Reading) Record

PSLFields is what one PSL value carries, named the way a reading names them.

It is the record a document's member WOULD be, without a source to hold it: what the layer above needs when it takes a value out of one document and stands it in another's place.

func (Record) Field

func (r Record) Field(name string) *Value

Field makes a Record a Subject, which is the common case: a record already in our own shape is tested as it stands.

func (Record) Get

func (r Record) Get(name string) *Value

Get is the value under a name, or nil where the record does not name it. A field present with no value reads as nil too: a bare name is a name, not a value of its own.

func (Record) Has

func (r Record) Has(name string) bool

Has reports whether the record names something, whatever is under it.

func (Record) Names

func (r Record) Names() []string

Names is the names the record carries, in order, including repeats and including the empty name of a positional member.

func (Record) String

func (r Record) String() string

String is the record as readable text: each member's name, then its value, and nothing at all for a member that has no value under its name. See Value.String -- readable, never parsed back, and nothing turns on it.

type RecordCount

type RecordCount struct {
	// N is how many records there are, or where the count is not Exact, how
	// many there are AT LEAST.
	N int

	// Exact says N is the whole figure rather than a floor.
	Exact bool
}

How many records a sequence has.

Not how many were asked for and not how many came back -- a scope already knows both of those, having stated one and counted the other. This is the figure a reader needs to size a bar it has not scrolled: the whole of the sequence, which is to say the whole of the DATA SET.

It belongs to the filter, not to the order

A data set is a source, a sort and a filter, and only two of those three can change this number. Sorting the same records cannot make there be more or fewer of them, and the fields a query asks for change what a scope carries rather than which records are in it.

So every data set over one source with one filter has the same count, and a reader that clicks a column header -- a new sort, a new order, every placement thrown away -- already knows it.

A floor is worth more than a silence

Some sources know the figure for nothing: one that ordered its records when the sequence was opened has them in a slice, and the count is its length. Some cannot know it at all. And in between is the common case, where part of the sequence has been read and what is known is a LOWER BOUND -- which is most of what a bar wants anyway, and is exactly what an incremental reader has.

So a count is a number and whether that number is the whole story. Nothing known at all is a floor of zero, which is true of every sequence and says nothing, and is how a source that cannot count answers.

func AtLeast

func AtLeast(n int) RecordCount

func CountOf

func CountOf(v DataSet) RecordCount

CountOf is what a data set says its sequence holds, and Unknown for one that does not count.

func Exactly

func Exactly(n int) RecordCount

Exactly and AtLeast are the two things that can be said, and Unknown is saying nothing -- a floor of zero, which holds for every sequence there is.

func Unknown

func Unknown() RecordCount

func (RecordCount) Add

func (c RecordCount) Add(n int) RecordCount

Add and Take are records certainly arriving and certainly leaving, which is what a notice about one identity says. Both keep whatever the figure was: a floor with one more record above it is a floor one higher, and an exact count that gains one is exact.

**This is why counting needs no deltas of its own.** A source reporting an append has already said +1 by saying Added, and one reporting a deletion has said -1; there is no second channel to invent and nothing to resynchronise, because the figure is only ever moved by a statement that was being sent anyway.

func (RecordCount) And

And is two counts added, for a sequence made of several -- exact only where both halves are, because a floor plus an exact figure is still only a floor.

func (RecordCount) Doubt

func (c RecordCount) Doubt(n int) RecordCount

Doubt is n records that MAY no longer be in the sequence -- replaced, or altered in a field the filter tests, either of which can take a record out of it or leave it where it was.

The true figure is then somewhere between N-n and N, so N-n is a floor and nothing here is exact any more. Which is the whole reason a floor is worth carrying: without one, a single doubtful record would cost the entire count, and the next honest thing to say would be nothing at all.

func (RecordCount) Nothing

func (c RecordCount) Nothing() bool

Nothing reports whether this says anything at all.

func (RecordCount) String

func (c RecordCount) String() string

String is the count as readable text, for a message or a test saying what it expected. Never parsed back.

func (RecordCount) Take

func (c RecordCount) Take(n int) RecordCount

type Roles

type Roles struct {
	// Sort and Filter are the fields the SEQUENCE is decided by. A change to
	// one of them can move a record, or take it out of the sequence
	// altogether, so it costs membership or order and not merely a repaint.
	Sort   []string
	Filter []string

	// Detail is the rest of what the query asked for: fields that are carried
	// and shown and decide nothing, so a change to one costs those records'
	// values and nothing structural.
	Detail []string

	// Whole says the query asked for whole records, so its detail fields are
	// every field a record has that is not a sort or a filter field -- which
	// cannot be listed here, the records being the source's to shape. Detail is
	// then what was asked to be LEFT OUT, and the answer to "is this a detail
	// field" is yes for anything else.
	Whole bool
}

Roles is the fields of a sequence grouped by the part each one plays.

A field may play two -- sorted and filtered both -- and appears in both groups when it does, because it costs both. What it may not do is be a detail field as well: a field that decides where a record stands is not decoration, however much it is also displayed.

func (Roles) Decides

func (r Roles) Decides(name string) bool

Decides reports whether a change to this field could move a record or take it out of the sequence.

Which is the SKELETON question: a field that decides where a record stands is what positions, membership, the count and the watermark are built out of, and losing it means the stretch has to be read again from the start. A field that decides nothing is flesh, and re-acquiring it is a fill for the rows somebody is actually looking at.

func (Roles) Filters

func (r Roles) Filters(name string) bool

Filters reports whether a change to this field could take a record OUT of the sequence, or bring one in.

Narrower than Decides, and the difference is exactly the sort. A sort field changing moves a record to another place in the sequence; it cannot make there be more or fewer of them. So this is the question the COUNT asks, where Decides is the one the order asks, and a sequence sorted by `.size` and filtered by `.kind` answers them differently about each.

func (Roles) Shows

func (r Roles) Shows(name string) bool

Shows reports whether this sequence carries the field without being decided by it -- so a change to it repaints those records and moves nothing.

A sequence that asked for whole records shows everything it is not decided by and has not excluded, which is why this is a question rather than a list.

type Row

type Row interface {
	Subject
	Key() *Value
	Fields() Record
}

A Row is one record of a source that holds its records.

Field comes from Subject, which is what a filter tests: a field the row has not got reads as nil, which is `undefined` rather than an error.

Fields is the row entire. A format that computes its fields lazily -- the PSL reading does, reaching into a nested list only when asked -- pays for this one when a scope carries the whole record, and pays for nothing when the query named the fields it wanted.

func NewRow

func NewRow(key *Value, fields Record) Row

NewRow is one record for a source built from rows: an identity, and the fields it carries.

type Scope

type Scope struct {
	// After is the record to start past: the asker holds it already. Nil
	// starts at the first record in walk order.
	After *Value

	// Until is the record to stop before: the asker holds that one too, and
	// everything beyond it, so a walk that reaches it has joined two runs the
	// asker held separately. Nil walks until the count is reached or the
	// records run out.
	Until *Value

	// Count is how many records are wanted.
	Count int

	// From is a position to start NEAR, for a reader that has a place in mind
	// rather than a record: a scroll thumb dragged to the middle of a long
	// sequence knows how far down it is and knows no identity there at all.
	//
	// **Best effort, and never a promise.** A source honours it as well as it
	// can and says where it actually started in Complete.First, which is the
	// truth whatever From asked for. One that holds its records lands exactly;
	// one built on others estimates and may be well out; one that cannot place
	// a position at all starts at the beginning and says so. So a reader that
	// means a particular place asks, reads First, and asks again from what it
	// learned -- which converges, and needs no source to promise anything.
	//
	// It is not a lie about identities. After and Until stay what they always
	// were, and this says something weaker in its own words rather than
	// dressing a position up as one of them.
	//
	// Zero is the beginning, which is where a scope starts anyway, so an unset
	// From asks for nothing. After WINS over it: a reader holding the record it
	// wants to carry on past knows something better than a position, and a
	// scope naming both a record and a place is refused rather than quietly
	// answered from one of them.
	From int

	// Reversed walks the sequence from its end rather than its beginning.
	//
	// Every level turns over, the one the sort does not write included: an
	// identity settles what the named levels leave equal, and a sequence read
	// backwards settles it backwards too. That is what makes this the exact
	// mirror -- `size desc` turns one level over and leaves ties facing the way
	// they were, which is a different sequence again.
	//
	// It belongs to the scope rather than the sequence because it costs
	// nothing: one prepared ordering is read either way, where a reversed
	// *sequence* would be a second ordering of the same records. And it names
	// no field, so it is the one way to turn over a sequence whose records are
	// read in a way that cannot name their identity at all.
	Reversed bool
}

A Scope is the run of records a query asks for: where to start, which way to walk, how many, and where the asker's own knowledge picks up again.

It is not a filter and it names no field. The sequence is already decided by the descriptor, and a scope only says which part of it to read -- so a source prepares one ordering and serves every scope of it cheaply, rather than preparing a new one because the reader scrolled.

After and Until are identities, not positions. An identity means something only to the source that issued it, which is why a source made of several others never passes one down: it hands each of them that one's own.

type Sink

type Sink interface {
	// Ordered says the records about to arrive are in the sequence's order.
	// It comes before the first of them or not at all, which is the only place
	// it is worth anything: a sink that learns it afterwards can no longer act
	// on the records it has already been given. Not being told means not
	// ordered, which is always safe.
	Ordered()

	// Record takes one record entire: its identity, and every field it has.
	//
	// Whole is worth saying because it outlives the scope that asked for it.
	// A record that arrived entire answers any question about that record, so
	// whoever holds it can answer the next query out of it instead of asking
	// again. It states no totals because it IS the totals: what it carries is
	// everything there is.
	//
	// The identity is beside the fields, not among them. A record may well
	// carry a field called `key`, and that field is data: it sorts, it
	// filters, it fills a column. What names the record is this.
	Record(id *Value, fields Record) error

	// Subset takes some of a record: its identity, the fields that were asked
	// for, and how many members the record HAS ALTOGETHER.
	//
	// The totals are what let a later question be answered without asking
	// again. Knowing a record has eight named members and holding eight of them
	// settles every other name at once -- there is nothing left for one to be --
	// and knowing it has three ordered ones settles `3` and everything past it,
	// an ordered member being named by its position. Without them a subset
	// answers the question that asked for it and no other.
	//
	// **A field the record has not got is sent as undefined**, rather than left
	// out. Left out it reads as a field nobody asked about; sent, it is a
	// guarantee that the record has not got it, and it is never asked for
	// again. It is not counted in the totals, being knowledge about the record
	// and not a member of it.
	Subset(id *Value, fields Record, has Totals) error

	Done(c Complete)
}

A Sink takes an answer as it is produced: the records one at a time, and then what ended them.

One at a time because a scope of a million records need not be assembled anywhere before the first of them moves, and because a source whose records are across a connection has them in that shape already.

type SortLevel

type SortLevel struct {
	Field string
	Level
}

A SortLevel is one level of a sort: which field, which way, and -- for text -- under which collation.

type Source

type Source interface {
	// Open states a sequence: one filter, one sort. It is refused where the
	// sequence cannot be produced exactly -- an op that is not implemented, a
	// collation that is not carried -- because an ordering that is quietly a
	// little different corrupts every answer after it and looks like data.
	Open(descriptor *DataSetDescriptor) (DataSet, error)
}

A Source is a body of records that sequences can be read out of.

type Standing

type Standing struct {
	// Location is the field holding the row's CONTAINER -- where it lives, not
	// what it is. With Name, a row's own path is the two joined.
	//
	// This is the field that makes children one equality, and the name is
	// `location` rather than `path` for a reason: `path` can mean where a row
	// lives or the row's own full address, and the two behave completely
	// differently. Whatever the data calls it -- `directory`, `container`,
	// `folder`, `thread` -- is what goes here.
	Location string

	// Name is the field holding this row's own segment. Empty means the
	// record's key, rendered as text.
	Name string

	// Whole is the field holding the row's own full path, materialised. It
	// suits data that already knows its whole address, and it is read as the
	// record spells it.
	Whole string

	// Delimiter goes between segments: `/` for something filesystem-like, `.`
	// for a namespace, `::`, whatever the data uses.
	//
	// **A segment containing it makes a path that means two things**, and that
	// is the caller's to avoid exactly as a duplicate key is. Where the tree
	// DERIVES a path, choosing one the segments do not contain is the whole of
	// what keeps it unambiguous.
	Delimiter string
}

A Standing says how a row of one source tells where it stands.

**It belongs to a CHILD TYPE and not to a tree.** A child type names a source, and how a row says where it stands is a property of that source rather than of whatever is reading it -- so a tree that is addresses at the top and an adjacency list beneath has two standings, and its capability degrades at the boundary rather than for the whole of it.

func (Standing) Check

func (st Standing) Check() error

Check refuses a standing that cannot mean what it says.

Two ways to be wrong, and both are configuration rather than data: naming two standings at once, and asking for a path to be BUILT with nothing to build it out of. A standing that builds no path is not among them -- that is the adjacency list, and it is a shape and not a mistake.

func (Standing) ChildrenByLocation

func (st Standing) ChildrenByLocation(field string) Criterion

ChildrenByLocation is `eq <field> <the parent's own path>`: every row that says it LIVES here.

One equality, no depth field and no prefix arithmetic -- which is what a field holding the CONTAINER buys, and what a field holding a row's own full address cannot, no operator being able to take the parent off the front of one.

The path is compared as TEXT, because `eq` compares by kind and a location field holding a symbol or a number is a different value from the text of the same characters. A source whose location field is not text wants a different criterion, and gets a refusal from the filter rather than a wrong answer. It partitions for the same reason ChildrenByKey does -- a row lives in one place -- so one census of the source by the location field answers every node's twisty.

func (Standing) DescendantsByLocation

func (st Standing) DescendantsByLocation(field string) Criterion

DescendantsByLocation is the whole subtree in one question, deliberately.

**It is TWO predicates and not one, which is not what it looks like.** The obvious form -- `starts <field> <the parent's path, and a delimiter>` -- is not descendants at all: a direct child's location is the parent's path EXACTLY, with no delimiter after it, so a prefix test misses every child and catches only what is deeper. And dropping the delimiter to let them in drags `/usr/locally` into `/usr/local`, which is what appending it prevents.

So:

or { eq location "/usr/local" ; starts location "/usr/local/" }

The first half is the children, and is the same predicate ChildrenByLocation uses; the second is everything below them, bounded. Each half does exactly the job it was described as doing, and descendants are the two TOGETHER rather than either alone.

**It cannot be censused, and that is a property of the question rather than a gap.** A census partitions, putting each row in exactly one group; a subtree criterion puts every row in the group of every one of its ancestors. So there is no field whose values are these answers, and this one leaves the census parts out rather than counting something that is not what was asked.

func (Standing) Join

func (st Standing) Join(container, name string) string

Join puts a container and a name together.

**No trailing delimiter is assumed.** Data writes `/usr/local` and data writes `/usr/local/`, both are common, and one rule reads both: the delimiter goes between them and is not doubled where it is already there. So `/usr` and `local` join as `/usr/local`, `/usr/` and `local` join the same way, and a root spelled `/` does not produce `//usr`.

An empty container is the name alone, which is what the top level of a derived path wants: the first segment stands by itself rather than under a delimiter that leads nowhere.

func (Standing) PathOf

func (st Standing) PathOf(above string, key *Value, r Record) string

PathOf is where this row stands, by whichever of the three readings is configured. `above` is the path of the row it was found beneath, which only the derived standing uses -- and which is exactly why the other two can say where a row belongs without having descended to it.

func (Standing) Paths

func (st Standing) Paths() bool

Paths reports whether this standing produces a path at all. The zero Standing does not, and a tree over one keys its marks by identity instead.

func (Standing) Under

func (st Standing) Under(path string) string

Under is a path with the delimiter on it, for asking what lies beneath.

**This is where the missing trailing delimiter bites**, and it is why the tree appends one rather than hoping the data did. Descendants of `/usr/local` are `starts location "/usr/local/"` and NOT `starts location "/usr/local"`, because the second also matches `/usr/locally` -- a sibling whose name begins with the same letters, dragged into a subtree it has nothing to do with. The delimiter is what makes a prefix a BOUNDARY instead of a spelling.

Equality needs no such care, comparing the whole of the field, which is why only this one has a method.

The empty path stays empty: beneath the root is everything, and every string starts with nothing.

type Stop

type Stop string

A Stop is why a scope ended, which the asker cannot work out for itself.

A scope that filled and one that ran out of records look identical from the far end -- both are a run of records that stopped -- and they mean opposite things about whether there is any point asking again.

const (
	// StopFilled: the count was reached. There is more past the watermark.
	StopFilled Stop = "filled"

	// StopJoined: the walk reached `until`, so what the asker holds on this
	// side and what it holds on the other are now one run.
	StopJoined Stop = "joined"

	// StopExhausted: there are no more records this way. Nothing past the end
	// to be complete up to, so there is no watermark either.
	StopExhausted Stop = "exhausted"
)

type Subject

type Subject interface {
	Field(name string) *Value
}

A Subject is anything a filter can take a field out of: the thing being tested, whatever shape it is held in.

An interface rather than a Record so that a source holding its records in its own form can put one to a filter without copying it into ours first -- which for a filter that rejects it would be a copy made for nothing.

A field the subject has not got reads as nil, which is `undefined`: a value with a rank of its own rather than an error. That is what makes a test against undefined a question about presence.

type Totals

type Totals struct {
	Ordered int
	Named   int
}

Totals is how many members a record has, whether or not they were sent.

Two counts and not one, because they settle different questions. ORDERED members are named by their position, so their count settles them entirely: n of them are the members `0` through `n-1`, and a query naming an index past that is answered without anyone being asked. NAMED members need the count AND the names -- knowing there are three says nothing about which three -- but the count still settles the question the moment three are known, there being nothing left for a fourth name to be.

**An absence does not count.** A member sent as undefined is a name the record has NOT got, said out loud so that nobody asks again. That is knowledge about the record rather than a member of it, and counting it would say the record had a member it has not.

func Tally

func Tally(r Record) Totals

Tally counts what a record carries: how many members stand by position and how many by name. Absences are not counted -- see Totals.

type TreeFielded

type TreeFielded interface {
	TreeFields() TreeFields
}

A TreeFielded source says what names it writes its tree fields under.

Optional, on the model of Counting and Censusing: a source that is not a tree is not one of these, and a reader asking gets told so rather than having to guess from whether a field happens to be there. That difference matters -- "a tree that could not count this row's children" and "not a tree at all" are two answers, and sniffing for a field conflates them.

A wrapper over a tree may forward it, and one that does not simply reads as not a tree, which is a missing capability rather than a wrong answer.

type TreeFields

type TreeFields struct {
	Depth      string // how deep the row stands, 0 at the top
	Path       string // where it stands, empty where the standing builds no path
	Expandable string // how many children: a number, or a floor, or undefined
	State      string // `closed`, `open` or `openAll`, as a symbol

	// Chain is the mark segments from the root down to this row, as a list of
	// positional members -- the walk's own `mine`, written down.
	//
	// **It is what lets a reader hold a WINDOW of a tree and still open a row in
	// it.** Expanding is `Marks.Open(chain...)`, and a reader that holds the whole
	// pre-order can spell the chain for itself: the ancestors of any row precede
	// it, so walking back up the rows it has is enough. A reader holding rows
	// forty to eighty cannot -- the ancestors are above forty, which is exactly
	// what it declined to hold -- so it would have to fetch what it let go
	// in order to click on what it has.
	//
	// `Path` is this same fact for a level with a Standing, and is empty for the
	// two commonest trees: a source that is its own adjacency list, and a flat
	// source read as one generation. Neither configures a standing, so neither has
	// a path, and both still have to be clickable.
	//
	// The descent holds the chain already, a segment appended per level, so the
	// row costs one list and nothing is walked to find it.
	Chain string

	// Kind is the NAME of the node type this row is, as a symbol, and empty for
	// the default kind.
	//
	// **This is how a reader tells one kind of row from another**, and nothing
	// else can: the descent knows a row's kind by construction -- whatever the
	// `applications` criterion returned out of the applications source is an
	// application -- and this is where it says so. A view holding a column
	// mapping per kind needs exactly this to know which mapping a row takes.
	Kind string
}

TreeFields names the things a tree ADDS to every row.

They are the caller's to change because they shadow: a tree over filesystem records whose own field is called `path` would otherwise lose it, and the tree's own must win or a view could not draw. Moving the tree's name is the way out, and it is why these are not constants.

func TreeFieldsOf

func TreeFieldsOf(src Source) (TreeFields, bool)

TreeFieldsOf asks a source what it writes its tree fields under, and reports false for one that is not a tree.

type TreeHint

type TreeHint struct {
	// Parent is the field holding the parent's key. A row with nothing under it
	// stands at the top, `undefined` being what a record lacking a field reads
	// as everywhere else.
	Parent string

	// Location is the field holding the container the row lives in -- where it
	// lives, not what it is. Whatever the data calls it: `directory`,
	// `container`, `folder`, `thread`.
	Location string

	// Root is how the TOP LEVEL's container is spelled, for a Location descent.
	//
	// It is here because only the author knows: data writes a root as `/`, as
	// `.`, as the empty string, and as nothing at all. Left out, the top level is
	// the rows whose container is **absent or empty** -- two predicates, because
	// `undefined` and `""` are two values and both are ordinary ways to say "at
	// the top".
	Root string

	// Name is the field holding the row's own segment. Empty means the record's
	// key, rendered as text.
	Name string

	// Whole is the field holding the row's own full address, where it carries
	// one. A path reading and never a descent -- see above.
	Whole string

	// Delimiter goes between segments, where a path is built or read.
	Delimiter string

	// Children is the field saying whether a row has children and how many:
	// `undefined` or `false` is a leaf, `true` is children of unknown number, a
	// number is that many. Empty means no row says, and every twisty is counted.
	Children string

	// Order is the field a row's place among its SIBLINGS comes from.
	//
	// It earns a field of its own because sibling order is so often not any
	// datum's order: the order a document listed them in, the order a store
	// decided about them. For a document whose records are positional, `key` is
	// that order, the key of a positional record being its index.
	Order string

	// Label is the field holding the row's own name, for whatever draws it.
	//
	// It is about the record -- which of its fields is what this record is
	// CALLED -- rather than about a column, and a reader with no use for it may
	// ignore it.
	Label string
}

A TreeHint is what a source says about the shape of its own records.

Every field is a FIELD NAME, not a value, and the zero hint says nothing.

func TreeHintOf

func TreeHintOf(src Source) (TreeHint, bool)

TreeHintOf is what a source says about the shape of its records, and false for one that says nothing.

**Asking the SOURCE rather than being handed a hint beside it** is the same move TreeFieldsOf makes, and for the same reason: whatever produced the source is what knows, and a reader that has been given only a source should not have to have been given a second thing as well.

func (TreeHint) Check

func (h TreeHint) Check() error

Check refuses a hint that cannot mean what it says.

Every one of these is CONFIGURATION rather than data, which is why it is worth finding when a hint is read rather than when a row is drawn: a document with a contradictory hint is a document to be fixed, and a tree assembled from it would be wrong in a way that looks like the data being wrong.

func (TreeHint) Nothing

func (h TreeHint) Nothing() bool

Nothing reports whether this hint says anything at all.

func (TreeHint) Options

func (h TreeHint) Options(src Source) (TreeOptions, error)

Options is the tree this hint describes, over the records of one source.

**This is the whole payoff.** A hint is worth having because it becomes a configured tree without anybody restating it, so the assembling lives here, next to what a hint means, rather than in each of the readers.

func (TreeHint) Standing

func (h TreeHint) Standing() Standing

Standing is the path reading this hint describes.

type TreeOptions

type TreeOptions struct {
	// Source and DataSetDescriptor are the TOP LEVEL: where its rows come from and which of
	// them, in what order. A nil DataSetDescriptor is all of them, unsorted.
	Source     Source
	Descriptor *DataSetDescriptor

	// Types is what kind each row is. **The top level's rows are the DEFAULT
	// kind**, so where the top level's rows come from is here and what they ARE
	// is there -- including how one says where it stands, which used to be said
	// twice and is now said once, by the type.
	Types NodeTypes

	// SaysChildren is the field a row uses to say whether it has children and
	// how many: `undefined` or `false` is a leaf, `true` is children of unknown
	// number, and a number is that many. Empty means no row says, and every
	// twisty is a count.
	SaysChildren string

	// Revisits is how many times a node may be EXPANDED again beneath itself on
	// one root-to-leaf path: 0 refuses, which is the default, and 3 is the most
	// that can be asked for. It is a setting because a tree that refuses says
	// the data is a tree and one that allows says it is a graph, and seeing
	// round the loop is how somebody understands it. See `docs/trees.md`.
	//
	// **It governs expansion and never emission.** A row the criterion says is
	// a child IS a child, so a node standing on its own path is still drawn --
	// with a dead twisty. Hiding it would make the tree lie about what is under
	// a node, which is worse than showing a loop, and the budget exists to bound
	// the walk rather than to censor the data.
	Revisits int

	// ByPath identifies rows by their PATH rather than by the record's own key.
	//
	// Not the default, and a choice a caller makes knowing why. A record's
	// identity is unique across a tree nested inside ONE source, and a path is
	// longer than a key. Where a tree grafts a second source under rows of the
	// first, or allows a node to stand beneath itself, two rows can carry one
	// key and the path is what tells them apart.
	ByPath bool

	// Fields names what the tree adds. Empty names take TreeFieldsDefault.
	Fields TreeFields
}

TreeOptions is everything a tree is made of.

type TreeSource

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

A TreeSource presents the visible rows of a hierarchy as one flat sequence.

func NewTreeSource

func NewTreeSource(o TreeOptions) (*TreeSource, error)

NewTreeSource makes one, and refuses what cannot be used.

The checks are configuration and are worth finding here rather than when a row is drawn: a standing that names two readings, named node types with no field to name them in, and a sort on the top level, which the next paragraph is about.

func (*TreeSource) Collapse

func (t *TreeSource) Collapse(chain ...string)

func (*TreeSource) CollapseAll

func (t *TreeSource) CollapseAll()

func (*TreeSource) Expand

func (t *TreeSource) Expand(chain ...string)

Expand, ExpandAll, Collapse and CollapseAll are the mark verbs, and each tells every live sequence afterwards.

**Invalidation is told, never decided.** Nothing here polls, nothing expires and no generation is compared: a mark moved, so the source says so, and the sequences drawing on it rebuild. That the saying costs a rebuild is this version's price for an eager flattening, and it is bounded by the rows that are visible rather than by the size of the tree.

func (*TreeSource) ExpandAll

func (t *TreeSource) ExpandAll(chain ...string)

func (*TreeSource) MarkedByPath

func (t *TreeSource) MarkedByPath(kind string) bool

MarkedByPath reports whether one KIND of row's mark segment is its path rather than its identity.

**A chain handed to Expand has to be spelled the way the walk spells it**, and the walk spells a level with a standing by path -- see `descent.level`, and `Standing.Paths`, which is where the two answers are decided. A caller reaching in from the side has neither in hand, so it asks.

It is per kind because a standing is: a tree of applications under hosts descends by key at one level and might descend by address at the next, and there is one right answer per level rather than one per tree.

Without this a reader guesses, and the guess is wrong exactly where it is hard to see: a location-descended tree drew its twisties from the child counts, took an Expand keyed by identity, matched nothing, and opened nothing -- with no error anywhere, because a chain naming a node that is not there is an ordinary thing to ask about.

func (*TreeSource) Marks

func (t *TreeSource) Marks() *Marks

Marks is the expansion, for a caller that wants to set it directly. The tree's own four verbs are the usual way, because they also tell the sequences that what they hold has changed.

func (*TreeSource) Open

func (t *TreeSource) Open(descriptor *DataSetDescriptor) (DataSet, error)

Open states a sequence over the visible rows.

**A sort is refused**, because the order is the tree's own and is BUILT rather than sorted. It does not fall out of CompareLevels either: that stops where the shorter run ends and returns 0, so `[a]` and `[a, b]` compare EQUAL rather than parent-before-child, and a tuple per level cannot express a varying-depth path. Each LEVEL is sorted, by its own descriptor, and the pre-order is laid over that.

A filter is the SHALLOW one: it is asked of the rows at every level, and a parent that does not match is gone and its children with it. Deep filtering -- keeping a parent that does not match where a descendant does -- is eager by nature and is not here yet.

func (*TreeSource) SortBy

func (t *TreeSource) SortBy(byKind map[string][]SortLevel)

SortBy restates how each KIND of row is ordered among its siblings, and tells every sequence stated over this tree.

**A tree sorts LEVEL by level, which is why this is per kind and not one order.** The pre-order is built rather than sorted -- `Open` refuses a sort for that reason -- so "sort by size" over a tree is a sort of each level's own sequence, laid out in the order the walk visits them. A kind spelling its size `bytes` where another spells it `size` is exactly the case, and the caller who knows both is the one holding the columns.

A kind not named here is ordered as its configuration says. A kind named with no levels is ordered the way its source answers, which is how a sort is turned OFF -- so the map's keys are the question and an empty slice is an answer.

The empty name is the default kind, which is the top level's.

func (*TreeSource) Stale

func (t *TreeSource) Stale()

Stale says that what this tree flattens has changed: a level's source was restated, a record was altered, one arrived or one went.

**Told, never decided.** A tree reads several sources at several levels and holds the flattening; nothing here asks any of them whether they still say what they said. Whoever changed one says so, and every sequence stated over this tree is built again.

**It takes no Notice, because a tree's flattening is total.** The pre-order is one walk over every level, so a record altered anywhere can change which rows are visible, how deep they stand and what their twisties say -- there is no run of one level kept apart from the rest for a reason to narrow. Taking the fields would be taking a parameter this version does not read. Narrowing the walk is what a later one would want a Notice for, and it can have one then.

func (*TreeSource) TreeFields

func (t *TreeSource) TreeFields() TreeFields

TreeFields is what this source writes its added fields under.

func (*TreeSource) WhenArrived

func (t *TreeSource) WhenArrived(tell func())

WhenArrived adds a reader to be told once a flattening has completed (serval.Arriving).

What a reader of a tree wants to know is that the ROWS are there, which is a different moment from a level answering: one level of several has said its piece and the walk goes on. So this fires when the walk is done, and nothing before.

type Value

type Value struct {
	Kind Kind

	Str   string  // SymbolValue, TextValue, BytesValue
	Int   int64   // NumberValue, when IsInt
	Num   float64 // NumberValue, otherwise
	IsInt bool    // NumberValue: written with no fractional part, and it fits
	Bool  bool    // BoolValue
	List  Record  // ListValue
}

A Value is one value of a record.

The fields a kind does not use are not read and carry no meaning. Str serves the three kinds that are a run of characters -- a symbol's name, text, and bytes as they stand -- because they differ in what they MEAN and not in how they are held.

func NewBool

func NewBool(b bool) *Value

func NewBytes

func NewBytes(b []byte) *Value

func NewFloat

func NewFloat(f float64) *Value

func NewInt

func NewInt(n int64) *Value

func NewList

func NewList(r Record) *Value

func NewNil

func NewNil() *Value

The constructors. A value is built, never parsed: whatever read the text owns the reading of it.

func NewSymbol

func NewSymbol(s string) *Value

func NewText

func NewText(s string) *Value

func Val

func Val(v any) *Value

Val is a Go value as a Value, for the callers that have one of a handful of ordinary types and do not want to say which. Anything it does not know becomes undefined rather than a guess.

func (*Value) String

func (v *Value) String() string

String is a value as readable text, for an error message, a log line or a test that wants to say what it expected in one line.

Readable and not canonical: it is for a person, it is never parsed back, and nothing turns on it. Key is what tables are keyed by and Equal is what decides whether two values are the same one. The shape -- names and values separated by spaces inside braces -- is shared with Record and Filter so that one glance reads all three.

Jump to

Keyboard shortcuts

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