Every seat constant of mesh-sdk is mesh-contracts' generated name; the four whole seats' (BrokerSeat, CheckerSeat, asks.Seat, buscompose.VaultSeat) are marked deprecated for their importers. The package walk of mesh-sdk's own check refuses a string literal naming a defined seat or its subjects, and an exported name given to one, beyond testdata/seat-names.txt, which lists what stands before the move (module names that equal a seat's among them) and only shrinks. No new test function. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
119 lines
4.5 KiB
Go
119 lines
4.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
contractseats "git.novox.be/novox/mesh-contracts/go/seats"
|
|
)
|
|
|
|
// The graphical session's seats (novox/hq ADR 0208): one module per piece of software, each piece's
|
|
// role a node seat in the mesh's own set, so i3 and sway, xterm and foot, rofi and dmenu compete for
|
|
// a role rather than each inventing one — and a machine running two of one role is refused at
|
|
// assignment instead of found by two bars on one screen.
|
|
const (
|
|
LoginManagerSeat = contractseats.NodeLoginManager
|
|
DisplayServerSeat = contractseats.NodeDisplayServer
|
|
DisplaySessionSeat = contractseats.NodeDisplaySession
|
|
TerminalEmulatorSeat = contractseats.NodeTerminalEmulator
|
|
LauncherSeat = contractseats.NodeLauncher
|
|
NotifierSeat = contractseats.NodeNotifier
|
|
LockScreenSeat = contractseats.NodeLockScreen
|
|
ClipboardSeat = contractseats.NodeClipboard
|
|
BarSeat = contractseats.NodeBar
|
|
CompositorSeat = contractseats.NodeCompositor
|
|
SecretServiceSeat = contractseats.NodeSecretService
|
|
)
|
|
|
|
// BarKindBlock is the bar's one received kind: a block (novox/hq ADR 0255).
|
|
const BarKindBlock = "block"
|
|
|
|
// barShows is what a contributed block may show, and the options each takes — the bar-neutral
|
|
// vocabulary every holder of node-bar renders. A new entry is a decision, and every holder's template
|
|
// renders it (the shape's examples) before it is accepted.
|
|
var barShows = map[string]map[string]barOption{
|
|
// The machine's battery: its charge, and how long it lasts or takes to fill.
|
|
"battery": {"device": {kind: "text"}},
|
|
// One line a command prints, run every interval seconds: what the bar cannot know itself.
|
|
"command": {"command": {kind: "text", required: true}, "interval": {kind: "seconds", required: true}},
|
|
// A value the contributor keeps on the bus (novox/hq ADR 0255): its state `<module>.<name>` and the
|
|
// key, this machine's name when absent. The value is an object whose `text` is what is shown. The
|
|
// holder is granted to read it and shows it however its bar shows a changing value; no file and
|
|
// no command passes between the two modules.
|
|
"state": {"state": {kind: "text", required: true}, "key": {kind: "text"}},
|
|
}
|
|
|
|
// barBlockReads is the state and key a block shows, if any; an empty key is the machine's own.
|
|
func barBlockReads(data map[string]any) []StateRead {
|
|
if data["shows"] != "state" {
|
|
return nil
|
|
}
|
|
options, _ := data["options"].(map[string]any)
|
|
s, ok := options["state"].(string)
|
|
if !ok || s == "" {
|
|
return nil
|
|
}
|
|
key, _ := options["key"].(string)
|
|
return []StateRead{{Read: s, Key: key}}
|
|
}
|
|
|
|
type barOption struct {
|
|
kind string // text, or seconds (a whole number from 1 to 86400)
|
|
required bool
|
|
}
|
|
|
|
func barBlockProblems(data map[string]any) []string {
|
|
var problems []string
|
|
for field := range data {
|
|
if !oneOf([]string{"bar", "place", "order", "shows", "options"}, field) {
|
|
problems = append(problems, fmt.Sprintf("a block has no field %q; it has bar, place, order, shows and options", field))
|
|
}
|
|
}
|
|
shows, _ := data["shows"].(string)
|
|
takes, known := barShows[shows]
|
|
if !known {
|
|
return append(problems, fmt.Sprintf("a block shows %v; it shows one of %s", data["shows"],
|
|
strings.Join(sortedKeys(barShows), ", ")))
|
|
}
|
|
options := map[string]any{}
|
|
if raw, has := data["options"]; has {
|
|
o, ok := raw.(map[string]any)
|
|
if !ok {
|
|
return append(problems, "a block's options are an object")
|
|
}
|
|
options = o
|
|
}
|
|
for name, v := range options {
|
|
opt, ok := takes[name]
|
|
if !ok {
|
|
problems = append(problems, fmt.Sprintf("a %s block has no option %q; it takes %s", shows, name,
|
|
orNothing(sortedKeys(takes))))
|
|
continue
|
|
}
|
|
switch opt.kind {
|
|
case "text":
|
|
if t, isText := v.(string); !isText || strings.TrimSpace(t) == "" {
|
|
problems = append(problems, fmt.Sprintf("a %s block's %s is text", shows, name))
|
|
}
|
|
case "seconds":
|
|
if _, whole := wholeIn(v, 1, 86400); !whole {
|
|
problems = append(problems, fmt.Sprintf("a %s block's %s is a whole number of seconds from 1 to 86400", shows, name))
|
|
}
|
|
}
|
|
}
|
|
for _, name := range sortedKeys(takes) {
|
|
if _, has := options[name]; takes[name].required && !has {
|
|
problems = append(problems, fmt.Sprintf("a %s block needs the option %s", shows, name))
|
|
}
|
|
}
|
|
return problems
|
|
}
|
|
|
|
// withShapeCode gives a data-shaped kind of the generated seat list the checks its definition cannot carry, being code:
|
|
// the bar's block (novox/hq ADR 0255, ADR 0260).
|
|
func withShapeCode(seat, kind string, sh *Shape) {
|
|
if seat == BarSeat && kind == BarKindBlock {
|
|
sh.Check, sh.Reads = barBlockProblems, barBlockReads
|
|
}
|
|
}
|