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
}