Documentation
¶
Overview ¶
Package rows is PayCLI's local editor for an array of objects: the rows of a Payload `blocks` field, or of a plain `array` field.
It exists because the one edit a content agent makes most often — "move the CTA above the media block", "drop the third block" — has no API of its own. Payload's REST surface can only replace the whole array, so the edit is a read-modify-write round trip that every caller re-invents with jq and a temp file, and gets wrong in the same two ways: the anchor index shifts once the moved row is lifted out, and a row is addressed by a position the caller read from a stale listing.
Nothing here performs I/O, reads the clock or knows what a Payload document is. A Selector is parsed from a string and matched against a []any; every mutation returns a NEW slice and never aliases the input. That makes each rule below a table test, and it keeps the index arithmetic — the part that is actually hard — in one place instead of in six command files.
Index ¶
- Constants
- Variables
- func Copy(list []any, src int, a Anchor) ([]any, int, error)
- func Insert(list []any, row any, a Anchor) ([]any, int, error)
- func LooksLikeBlocks(list []any) bool
- func Move(list []any, src int, a Anchor) ([]any, int, error)
- func Remove(list []any, idx []int) []any
- func ResolveMany(list []any, sel Selector, field string) ([]int, error)
- func ResolveOne(list []any, sel Selector, field string) (int, error)
- func Set(list []any, idx int, patch map[string]any, unset []string) ([]any, error)
- func StripIDs(v any) any
- func Types(list []any) []string
- type AmbiguousError
- type Anchor
- type Kind
- type Mode
- type NoMatchError
- type Selector
- type Summary
Constants ¶
const ( KeyID = "id" KeyBlockType = "blockType" KeyBlockName = "blockName" )
Payload's own keys on a blocks row. They are protocol, not content: `blockType` decides which block the row IS, `id` is server-generated, and `blockName` is the admin-UI label. They are named here because the selector grammar addresses rows BY them.
const SelectorGrammar = `N | -N | first | last | id:VALUE | name:VALUE | type:SLUG | type:SLUG[N]`
SelectorGrammar is the one-paragraph description of the grammar, shared by every command's help so the five forms are documented identically in all of them.
Variables ¶
var SelectorForms = []string{
"3 the row at index 3 (0-based)",
"-1 the last row; -2 the second to last",
"first,last sugar for 0 and -1",
"id:67f3a1 the row whose `id` is 67f3a1 — the only stable handle across edits",
"name:Hero the row whose `blockName` is exactly Hero (never a substring)",
"type:cta EVERY row whose `blockType` is cta",
"type:cta[1] the second cta row (0-based), which is always exactly one row",
}
SelectorForms is the per-form explanation for help output.
Functions ¶
func Copy ¶
Copy duplicates the row at src into the destination a names, stripping every server-generated `id` on the way — at the top level and at every depth.
The strip is not optional and not a flag. Payload treats a row whose `id` matches an existing row as THAT row: a duplicate that kept its ids does not add a block, it silently rewrites the original and drops one of the two.
func Insert ¶
Insert puts row at the destination a names and returns the new array plus the index it landed at.
The destination space has len+1 slots (0 .. len), not len: appending to a 3-row array is index 3. A negative `--at` counts back through those slots, so `--at -1` is the last position — consistent with a negative selector meaning the last row.
func LooksLikeBlocks ¶
LooksLikeBlocks reports whether every object row carries a `blockType`, which is what distinguishes a blocks field from a plain `array` field in a document PayCLI has no schema for. An empty array is not blocks: there is nothing to read it from, and guessing would name a field the caller never asked for.
func Move ¶
Move relocates the row at src to the destination a names and returns the new array plus the index the row ended at.
The anchor is resolved against the array WITH the moved row still in it, and the destination is then recomputed against the array WITHOUT it. That is the whole reason this function exists: "move row 0 after row 3" naively becomes insert-at-4 on a 3-row remainder and lands the row past its anchor. Every hand-written version of this loop gets it wrong once.
func Remove ¶
Remove deletes the rows at idx and returns the new array. Indices may arrive in any order and may repeat.
func ResolveMany ¶
ResolveMany matches sel and insists on at least one row. Several is fine.
func ResolveOne ¶
ResolveOne matches sel and insists on exactly one row.
func Set ¶
Set applies a field patch to the row at idx and returns the new array. Keys are dotted paths into the row; a nil value in patch deletes the key.
Types ¶
type AmbiguousError ¶
AmbiguousError is returned when a selector addressed several rows but the operation acts on exactly one.
func (*AmbiguousError) Error ¶
func (e *AmbiguousError) Error() string
type Anchor ¶
type Anchor struct {
Mode Mode
Index int
Sel Selector
// Label is how the caller SPELLED the destination, when that is clearer
// than the mode it compiled to. `--last` compiles to index -1, and an op
// log reading "(index -1)" makes a reader work out what the caller wrote;
// "(last)" does not. Empty means "describe the mode".
Label string
}
Anchor is a destination. Before/After are expressed relative to another ROW rather than to an index on purpose: an index read out of a listing is stale the moment anything else in the pipeline edits the array, while "after the media block" stays true.
type Kind ¶
type Kind string
Kind is the form a Selector took. The grammar is closed — five forms, no escapes, no wildcards — for the same reason `--path` is not jq: an open grammar guarantees a caller sends something plausible that this package does not implement, and gets an unspecified failure instead of a named one.
const ( // KindIndex is a bare integer: `0`, `3`, `-1`. Negative counts from the // end, so `-1` is the last row. KindIndex Kind = "index" // KindID is `id:<value>` — the row's server-generated `id`. KindID Kind = "id" // KindName is `name:<value>` — an exact `blockName` match. KindName Kind = "name" // KindType is `type:<slug>` — every row with that `blockType`, or the // n-th one with `type:<slug>[n]`. KindType Kind = "type" )
type Mode ¶
type Mode string
Mode is how an Anchor names a destination.
const ( // ModeAppend puts the row after every existing row. It is the default for // an insert, and the only mode that needs no argument. ModeAppend Mode = "append" // ModeIndex is `--at N` / `--to N`, a literal destination index. ModeIndex Mode = "index" // ModeBefore is `--before SEL`: end up immediately above that row. ModeBefore Mode = "before" // ModeAfter is `--after SEL`: end up immediately below that row. ModeAfter Mode = "after" )
type NoMatchError ¶
NoMatchError is returned when a selector that had to address a row addressed none. It carries the rows' summary so the caller can print what DOES exist instead of only what does not.
func (*NoMatchError) Error ¶
func (e *NoMatchError) Error() string
type Selector ¶
type Selector struct {
// Raw is the string the caller wrote, kept verbatim for error messages.
Raw string
// Kind is which of the five forms was used.
Kind Kind
// Index is set for KindIndex and may be negative.
Index int
// Value is the id, blockName or blockType being matched.
Value string
// Nth is the `[n]` subscript of `type:cta[1]`, nil when absent. It is
// separate from Index so that "the second cta" and "row 2" cannot be
// confused by a reader of this struct.
Nth *int
}
Selector addresses one or more rows. The zero value is unusable; build one with ParseSelector.
func ParseSelector ¶
ParseSelector parses one selector. It never consults the rows, so a selector can be validated before a document has been read.
type Summary ¶
type Summary struct {
Index int `json:"index"`
ID string `json:"id,omitempty"`
BlockType string `json:"block_type,omitempty"`
BlockName string `json:"block_name,omitempty"`
// Selector is the shortest selector that addresses THIS row and no other.
// It is emitted rather than left to the caller because the shortest one is
// not the obvious one: an index is stale after the next edit in the pipe,
// so an `id:` is preferred whenever the row has an id.
Selector string `json:"selector"`
// Fields is the row's own keys, minus Payload's plumbing, so a caller can
// see what `pay blocks set` could address without printing the whole row.
Fields []string `json:"fields,omitempty"`
// Row is the complete row. It is only filled in for `--long`.
Row any `json:"row,omitempty"`
}
Summary is one row as `pay blocks ls` reports it: enough to choose a row and to write a selector for it, and nothing else.