Initial commit
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s

Assisted-by: GLM 5.3
This commit is contained in:
2026-09-29 10:03:32 +02:00
commit f8ed33df83
206 changed files with 44165 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Package tomlfile writes TOML files atomically with owner-only
// permissions, shared by the users, tokens and template stores.
package tomlfile
import (
"fmt"
"os"
"path/filepath"
"sourcedock.dev/petrbalvin/interpres/v2"
)
// Write persists rows as the array of tables name, and replaces path
// atomically at mode 0600.
//
// The rows are []map[string]any rather than []any on purpose: interpres
// renders an array of tables as one [[name]] block per row, which is the
// form these files are documented in and the form a reader can edit, while
// a []any of tables renders as a single inline array.
func Write(path, name string, rows []map[string]any) error {
payload, err := interpres.Marshal(map[string]any{name: rows})
if err != nil {
return fmt.Errorf("encode toml: %w", err)
}
parent := filepath.Dir(path)
if parent == "" {
parent = "."
}
if err := os.MkdirAll(parent, 0o755); err != nil {
return fmt.Errorf("create directory: %w", err)
}
tmp, err := os.CreateTemp(parent, ".toml-*.tmp")
if err != nil {
return fmt.Errorf("create temp file: %w", err)
}
tmpPath := tmp.Name()
if _, err := tmp.Write(payload); err != nil {
tmp.Close()
os.Remove(tmpPath)
return fmt.Errorf("write: %w", err)
}
if err := tmp.Sync(); err != nil {
tmp.Close()
os.Remove(tmpPath)
return fmt.Errorf("sync: %w", err)
}
if err := tmp.Close(); err != nil {
os.Remove(tmpPath)
return fmt.Errorf("close: %w", err)
}
if err := os.Rename(tmpPath, path); err != nil {
os.Remove(tmpPath)
return fmt.Errorf("replace: %w", err)
}
if err := os.Chmod(path, 0o600); err != nil {
return fmt.Errorf("chmod: %w", err)
}
syncDir(parent)
return nil
}
func syncDir(dir string) {
d, err := os.Open(dir)
if err != nil {
return
}
defer d.Close()
_ = d.Sync()
}
// Tables normalises a TOML array of tables to a slice of maps.
// interpres decodes [[section]] arrays as []map[string]any, while
// inline arrays decode to []any; both are accepted.
func Tables(value any) []map[string]any {
switch list := value.(type) {
case []map[string]any:
return list
case []any:
out := make([]map[string]any, 0, len(list))
for _, item := range list {
if table, ok := item.(map[string]any); ok {
out = append(out, table)
}
}
return out
}
return nil
}
// Strings normalises a TOML array of strings.
func Strings(value any) []string {
var list []any
switch v := value.(type) {
case []string:
return v
case []any:
list = v
default:
return nil
}
out := make([]string, 0, len(list))
for _, item := range list {
if s, ok := item.(string); ok {
out = append(out, s)
} else {
out = append(out, "")
}
}
return out
}
// String returns the value of a decoded TOML string field, and "" for a
// missing or non-string value. It is the shared accessor for the files
// that read hand-edited TOML.
func String(value any) string {
s, _ := value.(string)
return s
}
// Bool returns the value of a decoded TOML boolean field, and fallback
// for a missing or non-boolean value, so an omitted key can mean the
// value the reader wants.
func Bool(value any, fallback bool) bool {
b, ok := value.(bool)
if !ok {
return fallback
}
return b
}
// Int64 returns the value of a decoded TOML integer field, and fallback
// for a missing or non-integer value.
func Int64(value any, fallback int64) int64 {
if i, ok := value.(int64); ok {
return i
}
return fallback
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
package tomlfile
import (
"os"
"path/filepath"
"strings"
"testing"
"sourcedock.dev/petrbalvin/interpres/v2"
)
func TestWriteAndReload(t *testing.T) {
path := filepath.Join(t.TempDir(), "sub", "data.toml")
if err := Write(path, "users", []map[string]any{
{"username": "admin", "role": "admin"},
}); err != nil {
t.Fatalf("Write: %v", err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("stat: %v", err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("mode = %v", info.Mode().Perm())
}
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read: %v", err)
}
if !strings.Contains(string(raw), "[[users]]") {
t.Fatalf("content = %s", raw)
}
parsed, err := interpres.ParseMap(raw)
if err != nil {
t.Fatalf("parse: %v", err)
}
tables := Tables(parsed["users"])
if len(tables) != 1 || tables[0]["username"] != "admin" {
t.Fatalf("tables = %v", tables)
}
}
func TestTablesShapes(t *testing.T) {
if Tables(nil) != nil {
t.Fatal("nil input should yield nil")
}
if got := Tables("nope"); got != nil {
t.Fatalf("got = %v", got)
}
fromAny := Tables([]any{map[string]any{"a": 1}, "skip"})
if len(fromAny) != 1 || fromAny[0]["a"] != 1 {
t.Fatalf("fromAny = %v", fromAny)
}
fromMaps := Tables([]map[string]any{{"b": 2}})
if len(fromMaps) != 1 || fromMaps[0]["b"] != 2 {
t.Fatalf("fromMaps = %v", fromMaps)
}
}
func TestStringsShapes(t *testing.T) {
if Strings(nil) != nil {
t.Fatal("nil input should yield nil")
}
if Strings(42) != nil {
t.Fatal("non-array input should yield nil")
}
got := Strings([]any{"a", 1, "b"})
if len(got) != 3 || got[0] != "a" || got[2] != "b" {
t.Fatalf("got = %v", got)
}
if direct := Strings([]string{"x"}); len(direct) != 1 || direct[0] != "x" {
t.Fatalf("direct = %v", direct)
}
}