package catalogue import ( "math" "regexp" "sort" ) // Shape is what a kind received as data looks like. type Shape struct { // Places are the fields a holder narrows its placeholders by, each with every value it takes. Places []PlaceField // Order is the whole-number field that orders the pieces within one placeholder (lowest first, // then module order); absent in a piece means OrderDefault. Order string // Check is what else is wrong with one piece's data, beyond its placing fields and its order. Check func(data map[string]any) []string `json:"-"` // Examples are pieces every holder's template must render, one for each variant the shape has, // so a holder that cannot render one is refused at registration and not found on a machine. Examples []map[string]any // Reads is the state on the bus a piece shows, each a state and the one key of it, or nothing // (novox/hq ADR 0260): a contributor offers only state it keeps itself, and the holder on the same // machine is granted to read that key, so the holder's manifest names no contributor. Reads func(data map[string]any) []StateRead `json:"-"` // Text says every piece of this kind is, rendered, text that a collector may place as a collection of items // (design 57 L3): a collector then names the kind as `${collected:contribution:::=,…}`. Text bool } // PlaceField is one field a holder places by, and the values it takes. type PlaceField struct { Field string Values []string } // OrderDefault is a piece's order when it gives none: the middle of 0–99. const OrderDefault = 50 // KnownCapabilities is every capability the node-engine detects (mesh-host internal/profile), the // names an `if-capability` may give (novox/hq ADR 0255). A name nothing detects would leave its // contribution out on every machine, silently, so it is refused. A detector added there is added here. var KnownCapabilities = []string{ "battery", "container-groups", "container-runtime", "file-references", "firewall", "graphical-session", "overlay", "package-manager", "power-meter", "privileged", "seat", "service-manager", "uplink-dhcpcd", "uplink-networkmanager", "uplink-systemd-networkd", "user-namespaces", "virtualisation", } // StateRead is one key of another module's state that a holder reads: Read is `.`, // and Key the one key, empty for the holder's own machine until ReadsGranted names it. type StateRead struct { Read, Key string } // stateKeyName is a key a piece may name: a machine's name is one. var stateKeyName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) // ReadsGranted is the state a holder reads because modules on its machine offer it in pieces of a // kind its seat receives as data (novox/hq ADR 0260), each with the one key the piece shows — the // machine's own when it names none — sorted and once each. A module's items of such a kind (design 57 L3) grant // what they show too, always at the machine's own key; an item whose capability the machine lacks grants a key // that is never written, which is no wider than its own machine's. The bus grants the holder these keys and // no others: the holder names no contributor, and reads only what is offered to it where it runs. func ReadsGranted(holder Manifest, onMachine []Manifest, machine string) []StateRead { seen := map[StateRead]bool{} for _, claim := range holder.Claims { s, known := SeatNamed(claim.Name) if !known { continue } for _, r := range s.Receives { if r.Shape == nil || r.Shape.Reads == nil { continue } for _, m := range onMachine { for _, c := range m.allContributions() { if cs, ok := SeatNamed(c.Seat); !ok || cs.Name != s.Name || c.Kind != r.Kind { continue } for _, sr := range r.Shape.Reads(c.Data) { if sr.Key == "" { sr.Key = machine } if module, _, err := ReadState(sr.Read); err == nil && module == m.Module && module != holder.Module && stateKeyName.MatchString(sr.Key) { seen[sr] = true } } } // A moved contributor's items of the kind (design 57 L3): what each shows, read from its attributes as a // contribution's data, and always this machine's own key (ADR 0260), never another's (issue 774: // power's draw block became an item and i3status-rust was issued nothing). for _, it := range m.Items { if it.Collection != "contribution:"+s.Name+":"+r.Kind { continue } for _, sr := range r.Shape.Reads(itemData(it)) { sr.Key = machine if module, _, err := ReadState(sr.Read); err == nil && module == m.Module && module != holder.Module && stateKeyName.MatchString(sr.Key) { seen[sr] = true } } } } } } out := make([]StateRead, 0, len(seen)) for sr := range seen { out = append(out, sr) } sort.Slice(out, func(i, j int) bool { if out[i].Read != out[j].Read { return out[i].Read < out[j].Read } return out[i].Key < out[j].Key }) return out } // itemData is an item's attributes in the shape of a contribution's data: each at the top and among the options, where // a shape's Reads looks for what a piece shows (barBlockReads: shows, options.state). func itemData(it Item) map[string]any { data, options := map[string]any{}, map[string]any{} for k, v := range it.Attributes { data[k], options[k] = v, v } data["options"] = options return data } // wholeIn is v as a whole number within [lo, hi]; JSON gives every number as a float. func wholeIn(v any, lo, hi int) (int, bool) { f, ok := v.(float64) if !ok { if i, isInt := v.(int); isInt { f, ok = float64(i), true } } if !ok || f != math.Trunc(f) || f < float64(lo) || f > float64(hi) { return 0, false } return int(f), true }