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
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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user