mesh/merge-gate pass: builds new: modules/betterbird, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A calendar reminder showed as a transparent, empty window on shanks and no tool could say which window it was or which preferences Betterbird ran with. The module owns one file, the user.js its setting prefs fills, which its tools place whole into the profile Betterbird uses and never over an operator's own; it reads named preferences without credentials or accounts, restarts Betterbird under the user manager so its output reaches the journal, checks what it promises, and shows a reminder on demand from a throw-away profile so a fix can be proved without the operator's calendar. No package: betterbird-bin is the AUR's.
98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
// Reading a tool's arguments: JSON numbers arrive as float64, and a missing argument is its default.
|
|
// The same in every desktop module that carries it.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// text is a string argument, trimmed; required says an empty one is refused.
|
|
func text(args map[string]any, key string, required bool) (string, error) {
|
|
v, present := args[key]
|
|
if !present || v == nil {
|
|
if required {
|
|
return "", fmt.Errorf("%s is required", key)
|
|
}
|
|
return "", nil
|
|
}
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("%s is a string, not %T", key, v)
|
|
}
|
|
s = strings.TrimSpace(s)
|
|
if s == "" && required {
|
|
return "", fmt.Errorf("%s is required", key)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// whole is a whole-number argument within [least, most], or def when absent.
|
|
func whole(args map[string]any, key string, def, least, most int) (int, error) {
|
|
v, present := args[key]
|
|
if !present || v == nil {
|
|
return def, nil
|
|
}
|
|
f, ok := v.(float64)
|
|
if !ok {
|
|
if i, isInt := v.(int); isInt {
|
|
f = float64(i)
|
|
} else {
|
|
return 0, fmt.Errorf("%s is a number, not %T", key, v)
|
|
}
|
|
}
|
|
if f != math.Trunc(f) {
|
|
return 0, fmt.Errorf("%s is a whole number, not %v", key, f)
|
|
}
|
|
n := int(f)
|
|
if n < least || n > most {
|
|
return 0, fmt.Errorf("%s is %d; it is between %d and %d", key, n, least, most)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// flag is a boolean argument, or def when absent.
|
|
func flag(args map[string]any, key string, def bool) (bool, error) {
|
|
v, present := args[key]
|
|
if !present || v == nil {
|
|
return def, nil
|
|
}
|
|
b, ok := v.(bool)
|
|
if !ok {
|
|
return false, fmt.Errorf("%s is true or false, not %T", key, v)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// texts is a list-of-strings argument.
|
|
func texts(args map[string]any, key string) ([]string, error) {
|
|
v, present := args[key]
|
|
if !present || v == nil {
|
|
return nil, nil
|
|
}
|
|
list, ok := v.([]any)
|
|
if !ok {
|
|
if ss, isStrings := v.([]string); isStrings {
|
|
return ss, nil
|
|
}
|
|
return nil, fmt.Errorf("%s is a list of strings, not %T", key, v)
|
|
}
|
|
out := make([]string, 0, len(list))
|
|
for i, item := range list {
|
|
s, ok := item.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s[%d] is a string, not %T", key, i, item)
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// seconds is a timeout argument in seconds, defaulted and bounded below the runtime's call limit.
|
|
func seconds(args map[string]any, key string, def, most int) (time.Duration, error) {
|
|
n, err := whole(args, key, def, 1, most)
|
|
return time.Duration(n) * time.Second, err
|
|
}
|