`${config-lines:<key>:<prefix>[:mib-bytes]}` in a file's content writes one line
per entry (nothing for an unset value or empty list; a newline in an entry
refuses the composition); `${config-json}` writes the module's set config values
as one JSON object. Both join the sweep's table of fillers. Their rows join the
config table test (A-22).
Builds: design 42 zfs-owns-zfs §3, §9; answers A-15, A-24, A-22
91 lines
3.3 KiB
Go
91 lines
3.3 KiB
Go
package resolution
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"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 := strconv.ParseUint(text, 10, 64)
|
|
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): 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) (string, error) {
|
|
if !configJSONRef.MatchString(content) {
|
|
return content, nil
|
|
}
|
|
set := map[string]any{}
|
|
for _, layer := range layers {
|
|
for k, v := range layer.Values {
|
|
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
|
|
}
|