Declare preferences with a type and a predefined key, and lay mesh and node config between default and node (hq issue 630)
A preference may fall back to one reviewed mesh-wide or node-wide value instead of a module layer for the whole mesh; like a default, config fills ${setting:} only.
This commit is contained in:
@@ -530,6 +530,12 @@ type Manifest struct {
|
|||||||
// stays refused by name until a layer sets it. The mesh's layer, then the node's, override it.
|
// stays refused by name until a layer sets it. The mesh's layer, then the node's, override it.
|
||||||
Settings map[string]SettingDeclaration `json:"settings,omitempty"`
|
Settings map[string]SettingDeclaration `json:"settings,omitempty"`
|
||||||
|
|
||||||
|
// Preferences are the values this module reads that a machine may tune (novox/hq issue 630): each
|
||||||
|
// key's type, default and why, and the predefined key it falls back to, if any. The value resolves
|
||||||
|
// from the default, then mesh config and node config for the predefined key, then this module's own
|
||||||
|
// value on the node. `settings` entries of kind preference are read as preferences while they move.
|
||||||
|
Preferences map[string]Preference `json:"preferences,omitempty"`
|
||||||
|
|
||||||
// unknown is the first key this manifest has that this controller does not know, at any depth, when
|
// unknown is the first key this manifest has that this controller does not know, at any depth, when
|
||||||
// it was read from the store (novox/hq ADR 0262): a manifest a newer controller registered.
|
// it was read from the store (novox/hq ADR 0262): a manifest a newer controller registered.
|
||||||
// ParseManifest refuses it; the store's catalogue still loads, and the module is left out of every
|
// ParseManifest refuses it; the store's catalogue still loads, and the module is left out of every
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
package catalogue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A config value resolves from four sources, the last set winning (novox/hq issue 630):
|
||||||
|
//
|
||||||
|
// 1. the default in the module's manifest;
|
||||||
|
// 2. mesh config: a mesh-wide value for a predefined key, such as `font-size`;
|
||||||
|
// 3. node config: a value for a predefined key for one node;
|
||||||
|
// 4. module-assignment config: that module's own value on that node, the most specific.
|
||||||
|
//
|
||||||
|
// An assignment is one module on one node: there is no module layer for the whole mesh. Mesh and node
|
||||||
|
// config hold values only for the keys on a fixed, reviewable list — the catalogue's `predefined.json` —
|
||||||
|
// that any module may fall back to, by naming one as its preference's `predefined`. A preference without
|
||||||
|
// `predefined` reads only its default and its module-assignment config. A preference is never trusted.
|
||||||
|
|
||||||
|
// Preference is one value a module reads that a machine may tune, as the manifest declares it under
|
||||||
|
// `preferences`.
|
||||||
|
type Preference struct {
|
||||||
|
// Type is "number", "string" or "boolean".
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Default is the value when no source sets it, of Type.
|
||||||
|
Default any `json:"default"`
|
||||||
|
// Predefined names the key on the catalogue's list this preference falls back to: mesh config,
|
||||||
|
// then node config, for that key. Empty reads only the default and the module-assignment config.
|
||||||
|
Predefined string `json:"predefined,omitempty"`
|
||||||
|
// Why this default: one sentence for whoever wonders whether to change it.
|
||||||
|
Why string `json:"why"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// The types a preference and a predefined key may have.
|
||||||
|
const (
|
||||||
|
TypeNumber = "number"
|
||||||
|
TypeString = "string"
|
||||||
|
TypeBoolean = "boolean"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PredefinedFile is the catalogue repository's root file holding the predefined keys.
|
||||||
|
const PredefinedFile = "predefined.json"
|
||||||
|
|
||||||
|
// PredefinedKey is one key mesh and node config may hold a value for.
|
||||||
|
type PredefinedKey struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Why string `json:"why"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predefined is the list of predefined keys, by key.
|
||||||
|
type Predefined map[string]PredefinedKey
|
||||||
|
|
||||||
|
// Keys is the list's keys, sorted.
|
||||||
|
func (p Predefined) Keys() []string {
|
||||||
|
keys := make([]string, 0, len(p))
|
||||||
|
for k := range p {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// predefinedName is what a predefined key looks like: the shape of a setting key.
|
||||||
|
var predefinedName = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]*$`)
|
||||||
|
|
||||||
|
// ParsePredefined reads `predefined.json` strictly, and refuses a key whose name, type or why is wrong.
|
||||||
|
func ParsePredefined(raw []byte) (Predefined, error) {
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
var list Predefined
|
||||||
|
if err := decoder.Decode(&list); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s is not a list of predefined keys ({\"<key>\": {\"type\": …, \"why\": …}}): %w",
|
||||||
|
PredefinedFile, err)
|
||||||
|
}
|
||||||
|
var problems []string
|
||||||
|
for _, key := range list.Keys() {
|
||||||
|
k := list[key]
|
||||||
|
if !predefinedName.MatchString(key) {
|
||||||
|
problems = append(problems, fmt.Sprintf("%q is not a usable key: lower-case letters, digits, dots, dashes and underscores", key))
|
||||||
|
}
|
||||||
|
if meshWords[key] {
|
||||||
|
problems = append(problems, fmt.Sprintf("%q is a key the mesh reads itself", key))
|
||||||
|
}
|
||||||
|
if !knownType(k.Type) {
|
||||||
|
problems = append(problems, fmt.Sprintf("%q has the type %q; a type is %q, %q or %q", key, k.Type,
|
||||||
|
TypeNumber, TypeString, TypeBoolean))
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(k.Why) == "" {
|
||||||
|
problems = append(problems, fmt.Sprintf("%q has no why: say in one sentence what the key is for", key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(problems) > 0 {
|
||||||
|
return nil, fmt.Errorf("%s: %s", PredefinedFile, strings.Join(problems, "; "))
|
||||||
|
}
|
||||||
|
if list == nil {
|
||||||
|
list = Predefined{}
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func knownType(t string) bool {
|
||||||
|
return t == TypeNumber || t == TypeString || t == TypeBoolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeOf is the type a JSON value has, as a preference names it, or "" for one that is none of them.
|
||||||
|
func TypeOf(v any) string {
|
||||||
|
switch v.(type) {
|
||||||
|
case float64, int, int64, json.Number:
|
||||||
|
return TypeNumber
|
||||||
|
case string:
|
||||||
|
return TypeString
|
||||||
|
case bool:
|
||||||
|
return TypeBoolean
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreferencesOf is every preference a module declares: those under `preferences`, and — while the
|
||||||
|
// catalogue moves to them — every `settings` entry of kind preference, its type read from its default.
|
||||||
|
func PreferencesOf(m Manifest) map[string]Preference {
|
||||||
|
if len(m.Preferences) == 0 && len(m.Settings) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make(map[string]Preference, len(m.Preferences)+len(m.Settings))
|
||||||
|
for key, d := range m.Settings {
|
||||||
|
out[key] = Preference{Type: TypeOf(d.Default), Default: d.Default, Why: d.Why}
|
||||||
|
}
|
||||||
|
for key, p := range m.Preferences {
|
||||||
|
out[key] = p
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeclaresPreference is whether a module declares a preference by this key.
|
||||||
|
func DeclaresPreference(m Manifest, key string) bool {
|
||||||
|
_, has := m.Preferences[key]
|
||||||
|
if !has {
|
||||||
|
_, has = m.Settings[key]
|
||||||
|
}
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
// PredefinedProblems is every way a module's preferences disagree with the list of predefined keys: a
|
||||||
|
// `predefined` not on it, or a type other than the list's.
|
||||||
|
func PredefinedProblems(m Manifest, list Predefined) []string {
|
||||||
|
var problems []string
|
||||||
|
keys := make([]string, 0, len(m.Preferences))
|
||||||
|
for k := range m.Preferences {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
p := m.Preferences[key]
|
||||||
|
if p.Predefined == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k, on := list[p.Predefined]
|
||||||
|
if !on {
|
||||||
|
listed := "the list holds none"
|
||||||
|
if len(list) > 0 {
|
||||||
|
listed = "the list holds " + strings.Join(list.Keys(), ", ")
|
||||||
|
}
|
||||||
|
problems = append(problems, fmt.Sprintf("%s's preference %q falls back to the predefined key %q, which "+
|
||||||
|
"is not on the catalogue's list of predefined keys (%s; %s)", m.Module, key, p.Predefined, PredefinedFile,
|
||||||
|
listed))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if k.Type != p.Type {
|
||||||
|
problems = append(problems, fmt.Sprintf("%s's preference %q is a %s, and the predefined key %q it falls "+
|
||||||
|
"back to is a %s", m.Module, key, p.Type, p.Predefined, k.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where a value from mesh or node config is said to come from.
|
||||||
|
const MeshConfigLayer = "mesh config"
|
||||||
|
|
||||||
|
// NodeConfigLayer is what the node config of one node is called where a value's source is said.
|
||||||
|
func NodeConfigLayer(node string) string { return "node config (" + node + ")" }
|
||||||
|
|
||||||
|
// ConfigLayers are the layers mesh config and node config make for one module: each of its preferences
|
||||||
|
// that names a predefined key, given that key's value. Laid between the module's defaults and its
|
||||||
|
// module-assignment config; like the defaults they fill `${setting:<key>}` only.
|
||||||
|
func ConfigLayers(m Manifest, mesh, node map[string]any, nodeName string) []Layer {
|
||||||
|
var out []Layer
|
||||||
|
for _, source := range []struct {
|
||||||
|
from string
|
||||||
|
values map[string]any
|
||||||
|
}{{MeshConfigLayer, mesh}, {NodeConfigLayer(nodeName), node}} {
|
||||||
|
values := map[string]any{}
|
||||||
|
for key, p := range m.Preferences {
|
||||||
|
if p.Predefined == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, set := source.values[p.Predefined]; set {
|
||||||
|
values[key] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(values) > 0 {
|
||||||
|
out = append(out, Layer{From: source.from, Values: values, Config: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigValueProblem refuses a value of the wrong type for a predefined key or a preference.
|
||||||
|
func ConfigValueProblem(what string, want string, v any) error {
|
||||||
|
if got := TypeOf(v); got != want {
|
||||||
|
shown, _ := json.Marshal(v)
|
||||||
|
return fmt.Errorf("%s is a %s, and %s is %s", what, want, shown, aOrNothing(got))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aOrNothing(t string) string {
|
||||||
|
if t == "" {
|
||||||
|
return "none of a number, a string or a boolean"
|
||||||
|
}
|
||||||
|
return "a " + t
|
||||||
|
}
|
||||||
|
|
||||||
|
// preferenceProblems is every way a module's `preferences` are wrong in themselves.
|
||||||
|
func preferenceProblems(m Manifest) []string {
|
||||||
|
var problems []string
|
||||||
|
keys := make([]string, 0, len(m.Preferences))
|
||||||
|
for k := range m.Preferences {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
p := m.Preferences[key]
|
||||||
|
say := func(format string, args ...any) {
|
||||||
|
problems = append(problems, fmt.Sprintf("%s's preference %q: ", m.Module, key)+fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
if _, twice := m.Settings[key]; twice {
|
||||||
|
say("declared under settings too; declare it once, under preferences")
|
||||||
|
}
|
||||||
|
if !knownType(p.Type) {
|
||||||
|
say("the type is %q; a type is %q, %q or %q", p.Type, TypeNumber, TypeString, TypeBoolean)
|
||||||
|
} else if p.Default == nil {
|
||||||
|
say("no default: a preference has one")
|
||||||
|
} else if got := TypeOf(p.Default); got != p.Type {
|
||||||
|
shown, _ := json.Marshal(p.Default)
|
||||||
|
say("it is a %s, and its default %s is %s", p.Type, shown, aOrNothing(got))
|
||||||
|
}
|
||||||
|
if p.Predefined != "" && !predefinedName.MatchString(p.Predefined) {
|
||||||
|
say("%q is not a predefined key's name", p.Predefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems
|
||||||
|
}
|
||||||
|
|
||||||
|
// PredefinedNamed is every predefined key a module's preferences fall back to, sorted.
|
||||||
|
func PredefinedNamed(m Manifest) []string {
|
||||||
|
seen := map[string]bool{}
|
||||||
|
var out []string
|
||||||
|
for _, p := range m.Preferences {
|
||||||
|
if p.Predefined != "" && !seen[p.Predefined] {
|
||||||
|
seen[p.Predefined] = true
|
||||||
|
out = append(out, p.Predefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -114,15 +114,23 @@ func operatorsWord(key string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingProblems is every way a definition's setting defaults are wrong, in its own words.
|
// SettingProblems is every way a definition's setting defaults and preferences are wrong, in its own
|
||||||
|
// words. The list of predefined keys is not needed here; PredefinedProblems judges against it.
|
||||||
func SettingProblems(m Manifest) []string {
|
func SettingProblems(m Manifest) []string {
|
||||||
if len(m.Settings) == 0 {
|
declared := PreferencesOf(m)
|
||||||
|
if len(declared) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
used := settingKeysUsedBy(m)
|
used := settingKeysUsedBy(m)
|
||||||
var problems []string
|
problems := preferenceProblems(m)
|
||||||
for _, key := range sortedSettingKeys(m.Settings) {
|
keys := make([]string, 0, len(declared))
|
||||||
d := m.Settings[key]
|
for k := range declared {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
d := declared[key]
|
||||||
|
_, old := m.Settings[key]
|
||||||
say := func(format string, args ...any) {
|
say := func(format string, args ...any) {
|
||||||
problems = append(problems, fmt.Sprintf("%s's setting %q: ", m.Module, key)+fmt.Sprintf(format, args...))
|
problems = append(problems, fmt.Sprintf("%s's setting %q: ", m.Module, key)+fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
@@ -134,23 +142,27 @@ func SettingProblems(m Manifest) []string {
|
|||||||
say("the mesh reads %q itself, and a module gives it no default", key)
|
say("the mesh reads %q itself, and a module gives it no default", key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.Kind != KindPreference {
|
if old && m.Settings[key].Kind != KindPreference {
|
||||||
say("kind is %q, and only a %q has a default (novox/hq ADR 0262)", d.Kind, KindPreference)
|
say("kind is %q, and only a %q has a default (novox/hq ADR 0262)", m.Settings[key].Kind, KindPreference)
|
||||||
}
|
}
|
||||||
if word := operatorsWord(key); word != "" {
|
if word := operatorsWord(key); word != "" {
|
||||||
say("a key naming %q is the operator's value, and has no default — it is the "+
|
say("a key naming %q is the operator's value, and has no default — it is the "+
|
||||||
"assignment's, never the definition's (novox/hq ADR 0112, ADR 0262)", word)
|
"assignment's, never the definition's (novox/hq ADR 0112, ADR 0262)", word)
|
||||||
}
|
}
|
||||||
switch v := d.Default.(type) {
|
if old {
|
||||||
case string:
|
switch v := d.Default.(type) {
|
||||||
if strings.TrimSpace(v) == "" {
|
case string:
|
||||||
say("an empty default is no default; give the value, or declare nothing")
|
if strings.TrimSpace(v) == "" {
|
||||||
|
say("an empty default is no default; give the value, or declare nothing")
|
||||||
|
}
|
||||||
|
case float64, bool:
|
||||||
|
case nil:
|
||||||
|
say("no default: a key without one is the operator's, and is not declared here")
|
||||||
|
default:
|
||||||
|
say("a default is a string, a number or a boolean, and this is %T", d.Default)
|
||||||
}
|
}
|
||||||
case float64, bool:
|
} else if v, isString := d.Default.(string); isString && strings.TrimSpace(v) == "" {
|
||||||
case nil:
|
say("an empty default is no default; give the value, or declare nothing")
|
||||||
say("no default: a key without one is the operator's, and is not declared here")
|
|
||||||
default:
|
|
||||||
say("a default is a string, a number or a boolean, and this is %T", d.Default)
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(d.Why) == "" {
|
if strings.TrimSpace(d.Why) == "" {
|
||||||
say("no why: say in one sentence why this default")
|
say("no why: say in one sentence why this default")
|
||||||
@@ -164,11 +176,12 @@ func SettingProblems(m Manifest) []string {
|
|||||||
|
|
||||||
// Defaults is the layer a module's own defaults make, or nothing when it gives none.
|
// Defaults is the layer a module's own defaults make, or nothing when it gives none.
|
||||||
func Defaults(m Manifest) (Layer, bool) {
|
func Defaults(m Manifest) (Layer, bool) {
|
||||||
if len(m.Settings) == 0 {
|
declared := PreferencesOf(m)
|
||||||
|
if len(declared) == 0 {
|
||||||
return Layer{}, false
|
return Layer{}, false
|
||||||
}
|
}
|
||||||
values := map[string]any{}
|
values := map[string]any{}
|
||||||
for key, d := range m.Settings {
|
for key, d := range declared {
|
||||||
if d.Default != nil {
|
if d.Default != nil {
|
||||||
values[key] = d.Default
|
values[key] = d.Default
|
||||||
}
|
}
|
||||||
@@ -189,7 +202,7 @@ func WithDefaults(m Manifest, layers []Layer) []Layer {
|
|||||||
type SettingSource struct {
|
type SettingSource struct {
|
||||||
Key string
|
Key string
|
||||||
Value any
|
Value any
|
||||||
// From is DefaultLayer, MeshWideLayer or the node's name.
|
// From is DefaultLayer, MeshConfigLayer, NodeConfigLayer(node), MeshWideLayer or the node's name.
|
||||||
From string
|
From string
|
||||||
// FromDefault is whether the value is the module's default, whatever From reads.
|
// FromDefault is whether the value is the module's default, whatever From reads.
|
||||||
FromDefault bool
|
FromDefault bool
|
||||||
@@ -199,10 +212,11 @@ type SettingSource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Effective is every key a module gives a default or a layer sets, with its value and where it came
|
// Effective is every key a module gives a default or a layer sets, with its value and where it came
|
||||||
// from: the default, then the mesh's layer, then the node's — later wins.
|
// from: the default, then mesh config, then node config, then the module's own layer on the node —
|
||||||
|
// later wins (novox/hq issue 630).
|
||||||
func Effective(m Manifest, layers []Layer) []SettingSource {
|
func Effective(m Manifest, layers []Layer) []SettingSource {
|
||||||
byKey := map[string]*SettingSource{}
|
byKey := map[string]*SettingSource{}
|
||||||
for key, d := range m.Settings {
|
for key, d := range PreferencesOf(m) {
|
||||||
byKey[key] = &SettingSource{Key: key, Value: d.Default, From: DefaultLayer, FromDefault: true,
|
byKey[key] = &SettingSource{Key: key, Value: d.Default, From: DefaultLayer, FromDefault: true,
|
||||||
Default: d.Default, HasDefault: true}
|
Default: d.Default, HasDefault: true}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ func wholeID(v string) (uint64, bool) {
|
|||||||
func OwnerSettingProblems(m Manifest) []string {
|
func OwnerSettingProblems(m Manifest) []string {
|
||||||
var problems []string
|
var problems []string
|
||||||
asked := settingKeysAskedOutsideOwners(m)
|
asked := settingKeysAskedOutsideOwners(m)
|
||||||
for k := range m.Settings {
|
for k := range PreferencesOf(m) {
|
||||||
asked[k] = true
|
asked[k] = true
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources {
|
for _, r := range m.Resources {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func onlyTaken(layers []Layer, taken map[string]bool) []Layer {
|
|||||||
values[key] = value
|
values[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kept = append(kept, Layer{From: layer.From, Values: values, Default: layer.Default})
|
kept = append(kept, Layer{From: layer.From, Values: values, Default: layer.Default, Config: layer.Config})
|
||||||
}
|
}
|
||||||
return kept
|
return kept
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ func TakenSettingsProblems(m Manifest) []string {
|
|||||||
// Said only when none of the module's mergeable files names its keys: when one does, §2 already
|
// Said only when none of the module's mergeable files names its keys: when one does, §2 already
|
||||||
// names every file that does not, and a file is not reported twice.
|
// names every file that does not, and a file is not reported twice.
|
||||||
func WrittenSettingsProblems(m Manifest) []string {
|
func WrittenSettingsProblems(m Manifest) []string {
|
||||||
if len(m.Settings) == 0 {
|
if len(PreferencesOf(m)) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var silent []string
|
var silent []string
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ type Layer struct {
|
|||||||
// Default marks the layer a module's own defaults make (novox/hq ADR 0262). Marked rather than
|
// Default marks the layer a module's own defaults make (novox/hq ADR 0262). Marked rather than
|
||||||
// recognised by From, which is a node's name for a node's layer, and a node may be called anything.
|
// recognised by From, which is a node's name for a node's layer, and a node may be called anything.
|
||||||
Default bool
|
Default bool
|
||||||
|
// Config marks a layer of mesh config or node config (novox/hq issue 630): values for predefined keys,
|
||||||
|
// given to the preferences that fall back to them. Like the defaults, it fills `${setting:<key>}` only.
|
||||||
|
Config bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplySettings produces a resource's final content from the module's own and the layers over it.
|
// ApplySettings produces a resource's final content from the module's own and the layers over it.
|
||||||
@@ -136,7 +139,7 @@ func overridden(base map[string]any, layers []Layer, what string) (map[string]an
|
|||||||
values[key] = value
|
values[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kept = append(kept, Layer{From: layer.From, Values: values, Default: layer.Default})
|
kept = append(kept, Layer{From: layer.From, Values: values, Default: layer.Default, Config: layer.Config})
|
||||||
}
|
}
|
||||||
merged, err := settle(base, kept, nil, what)
|
merged, err := settle(base, kept, nil, what)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -173,7 +176,7 @@ func settle(base map[string]any, layers []Layer, protected map[string]bool, what
|
|||||||
map[string]any, error) {
|
map[string]any, error) {
|
||||||
merged := deepCopy(base)
|
merged := deepCopy(base)
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
if layer.Default {
|
if layer.Default || layer.Config {
|
||||||
// A module's own defaults fill ${setting:<key>} only (novox/hq ADR 0262). A mergeable
|
// A module's own defaults fill ${setting:<key>} only (novox/hq ADR 0262). A mergeable
|
||||||
// file's content already is its defaults, and a contribution or served fact declares its
|
// file's content already is its defaults, and a contribution or served fact declares its
|
||||||
// own; laid on here, a default would reach every mergeable file of its module (issue 168).
|
// own; laid on here, a default would reach every mergeable file of its module (issue 168).
|
||||||
@@ -363,7 +366,7 @@ func UnusedSettings(m Manifest, layers []Layer) []string {
|
|||||||
}
|
}
|
||||||
// A key the module gives a default reaches what asks for it (novox/hq ADR 0262): setting it
|
// A key the module gives a default reaches what asks for it (novox/hq ADR 0262): setting it
|
||||||
// overrides the default, which is how a running module gains a setting without a gap between.
|
// overrides the default, which is how a running module gains a setting without a gap between.
|
||||||
for key := range m.Settings {
|
for key := range PreferencesOf(m) {
|
||||||
lands[key] = true
|
lands[key] = true
|
||||||
}
|
}
|
||||||
for _, values := range m.Contributes {
|
for _, values := range m.Contributes {
|
||||||
|
|||||||
Reference in New Issue
Block a user