mesh/delivery merged: source.main moved to novox/mesh-host@f8acfaeed79d
mesh/delivery-group group feature/1979-zfs-owns-zfs merged: every member is merged
mesh/check being carried to 96a869063977 on f8acfaeed79d | from 0193949efe9f, mesh-catalog 117f933dacf0, mesh-delivery 7221a1e5cd7f, mesh-sdk e42b9491…
A size of a million MiB or more reached the file in exponent form and was refused. Builds: design 42 zfs-owns-zfs §3, A-24
117 lines
4.2 KiB
Go
117 lines
4.2 KiB
Go
package resolution
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// configLinesRef is `${config-lines:<key>:<prefix>}` and `${config-lines:<key>:<prefix>:mib-bytes}` in a file's
|
|
// content (design 42 zfs-owns-zfs §3, ADR 0679 §16 (c)): one line per entry of a list value, `<prefix><entry>`. The
|
|
// prefix is literal text with no `}`, `$` or newline. A placeholder that holds a whole line takes its newline with it.
|
|
var configLinesRef = regexp.MustCompile(`\$\{config-lines:([a-z0-9][a-z0-9_.-]*):([^}$\n]*?)(:mib-bytes)?\}(\n?)`)
|
|
|
|
// configJSONRef is `${config-json}` in a file's content (design 42 zfs-owns-zfs §9, answer A-15): the module's set
|
|
// config values as one JSON object.
|
|
var configJSONRef = regexp.MustCompile(`\$\{config-json\}`)
|
|
|
|
// configLinesInto writes each `${config-lines:…}` of a file's content from the layers: a line `<prefix><entry>` for each
|
|
// entry of a list, one for a set scalar, nothing for an unset value or an empty list. With `mib-bytes` an entry given in
|
|
// MiB is written in bytes. An entry holding a newline is refused, and the content is left as it was.
|
|
func configLinesInto(content string, layers []mcat.Layer, module string) (string, error) {
|
|
var refused error
|
|
filled := configLinesRef.ReplaceAllStringFunc(content, func(token string) string {
|
|
m := configLinesRef.FindStringSubmatch(token)
|
|
key, prefix, mib, newline := m[1], m[2], m[3] != "", m[4]
|
|
value, set := settingValue(layers, key)
|
|
if !set {
|
|
return ""
|
|
}
|
|
entries, isList := value.([]any)
|
|
if !isList {
|
|
entries = []any{value}
|
|
}
|
|
var lines []string
|
|
for _, e := range entries {
|
|
text := plainly(e)
|
|
if strings.ContainsAny(text, "\r\n") {
|
|
refused = fmt.Errorf("%s: an entry of the config value %q holds a newline, which would begin a line "+
|
|
"of its own in the file it is written into (design 42 §3)", module, key)
|
|
return token
|
|
}
|
|
if mib {
|
|
n, err := mibWhole(text)
|
|
if err != nil || n > 1<<44 {
|
|
refused = fmt.Errorf("%s: the config value %q holds %q, and mib-bytes writes a whole number of MiB "+
|
|
"in bytes (design 42 §3)", module, key, text)
|
|
return token
|
|
}
|
|
text = strconv.FormatUint(n<<20, 10)
|
|
}
|
|
lines = append(lines, prefix+text)
|
|
}
|
|
if len(lines) == 0 {
|
|
return ""
|
|
}
|
|
return strings.Join(lines, "\n") + newline
|
|
})
|
|
if refused != nil {
|
|
return content, refused
|
|
}
|
|
return filled, nil
|
|
}
|
|
|
|
// configJSONInto writes `${config-json}` in a file's content as the module's set config values in one JSON object
|
|
// (design 42 zfs-owns-zfs §9, answer A-15): only keys the module declares under `config` are written, and what no layer sets is absent, and no unset value refuses the module; a
|
|
// list and an object are written as JSON, a number, a boolean and a text as JSON values of their own type.
|
|
func configJSONInto(content string, layers []mcat.Layer, declared []string) (string, error) {
|
|
if !configJSONRef.MatchString(content) {
|
|
return content, nil
|
|
}
|
|
set := map[string]any{}
|
|
for _, layer := range layers {
|
|
for k, v := range layer.Values {
|
|
if slices.Contains(declared, k) {
|
|
set[k] = v
|
|
}
|
|
}
|
|
}
|
|
var out strings.Builder
|
|
enc := json.NewEncoder(&out)
|
|
enc.SetEscapeHTML(false)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(set); err != nil {
|
|
return content, fmt.Errorf("the config values cannot be written as JSON: %w", err)
|
|
}
|
|
object := strings.TrimSuffix(out.String(), "\n")
|
|
return configJSONRef.ReplaceAllLiteralString(content, object), nil
|
|
}
|
|
|
|
// mibWhole is a size in MiB as a whole number: digits, or a whole number a program wrote in exponent form (`1e+06`),
|
|
// which is how a float of a million or more prints (design 42 §3).
|
|
func mibWhole(text string) (uint64, error) {
|
|
if n, err := strconv.ParseUint(text, 10, 64); err == nil {
|
|
return n, nil
|
|
}
|
|
f, err := strconv.ParseFloat(text, 64)
|
|
if err != nil || f < 0 || f != math.Trunc(f) || f > 1<<44 {
|
|
return 0, fmt.Errorf("not a whole number of MiB")
|
|
}
|
|
return uint64(f), nil
|
|
}
|
|
|
|
// declaredConfig is the keys a module's manifest declares under `config`.
|
|
func declaredConfig(m mcat.Manifest) []string {
|
|
keys := make([]string, 0, len(m.Settings))
|
|
for k := range m.Settings {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|