Documentation
¶
Overview ¶
Package kvconf reads, writes and live-reloads flat `key = value` config files with the standard library only.
# a comment lock.after = 5m border.color = #ff8800 # a value may start with # title = "quoted # kept" # quotes are removed bind.cmd+return = spawn foot
Parse turns a file into entries, Decode maps entries onto a struct with `kv` tags, Watch reports file changes with the native OS API (inotify, kqueue, ReadDirectoryChangesW, or polling), and Live ties them together into a config that reloads itself.
Index ¶
- Constants
- Variables
- func Changed(old, cur map[string]string) []string
- func DefaultPath(app, name string) (string, error)
- func ExpandPath(s string) (string, error)
- func Marshal(v any) ([]byte, error)
- func Open[T any](path string, opts LiveOptions[T]) (*Live[T], []Warning, error)
- func Parse(r io.Reader, opts Options) ([]Entry, []Warning, error)
- func ParseBool(s string) (bool, error)
- func ParseBytes(data []byte, opts Options) ([]Entry, []Warning, error)
- func Quote(v string) string
- func ReadFile(path string, opts Options) ([]byte, error)
- func Set(data []byte, values map[string]*string) ([]byte, error)
- func SetFile(path string, values map[string]*string, perm os.FileMode) error
- func Values(entries []Entry) map[string]string
- func Watch(ctx context.Context, path string, opts WatchOptions, out chan<- struct{}) error
- func WriteFile(path string, data []byte, perm os.FileMode) error
- type Change
- type CommentStyle
- type Defaulter
- type Entry
- type Live
- type LiveOptions
- type Options
- type Path
- type Validator
- type ValueError
- type Warning
- type WatchOptions
Constants ¶
const ( DefaultMaxBytes = 1 << 20 DefaultMaxLine = 64 << 10 )
Default limits, used when the matching Options field is 0.
const DefaultDebounce = 100 * time.Millisecond
DefaultDebounce is the quiet time Watch waits for after a change.
Variables ¶
var ( // ErrTooLarge is returned when the input exceeds Options.MaxBytes. ErrTooLarge = errors.New("kvconf: file too large") // ErrNotUTF8 is returned when the input is not valid UTF-8. ErrNotUTF8 = errors.New("kvconf: file is not valid UTF-8") )
var ErrNotRegular = errors.New("not a regular file")
ErrNotRegular is returned by ReadFile for a directory, FIFO or device.
Functions ¶
func DefaultPath ¶
DefaultPath returns <config dir>/<app>/<name>. The config dir is $XDG_CONFIG_HOME when it is an absolute path; otherwise ~/.config on Unix, ~/Library/Application Support on macOS and %AppData% on Windows.
func ExpandPath ¶
ExpandPath expands a leading ~/ and requires an absolute, NUL-free path.
func Marshal ¶
Marshal writes the tagged fields of v (a struct or a pointer to one) as `key = value` lines in declaration order; map entries are sorted by name.
func Open ¶
Open loads the config file at path and returns it with the warnings of the first load. Call Run to follow its changes.
func Parse ¶
Parse reads r and returns its entries in file order: a repeated key appears once per assignment. A line that is not `key = value` is skipped with a warning, or fails the parse in strict mode.
func ParseBytes ¶
ParseBytes is Parse for data already in memory. The entries do not keep a reference to data.
func Quote ¶
Quote returns v as it must be written so Parse reads it back unchanged: as is when possible, else as a Go "..." string.
func ReadFile ¶
ReadFile reads a config file safely: it must be a regular file (a FIFO or device is rejected without blocking) no larger than opts.MaxBytes. A missing file returns an error wrapping fs.ErrNotExist.
func Set ¶
Set returns data with each key of values assigned, keeping every other line, comment and the spacing of edited lines. An existing assignment (the last one) is replaced; otherwise a commented-out `# key = ...` is enabled; otherwise the key goes after the last key sharing its longest dotted parent, or at the end. A nil value removes every assignment of the key. Keys are applied in sorted order, so the output is deterministic. A key that cannot be read back as written (empty, with '=' or a line break, starting with '#') is an error and nothing is changed.
func SetFile ¶
SetFile applies Set to the file at path and writes it back atomically. A missing file is created with perm.
func Watch ¶
func Watch(ctx context.Context, path string, opts WatchOptions, out chan<- struct{}) error
Watch reports on out each time the file at path changes, is created, replaced or removed, once it has been quiet for the debounce time. It watches the parent directory, so editors that save by renaming a new file over the old one are seen. It follows symlinks and watches each link of the chain, so a dotfile-managed config is seen at its target and a retargeted link is followed. A missing directory is waited for.
The send never blocks: give out a buffer of 1, and a pending value already means "changed". Watch returns nil when ctx ends, or an error when the OS refuses to watch.
Types ¶
type Change ¶
type Change[T any] struct { Old, New *T // Keys lists, sorted, the keys added, removed or changed. Keys []string Warnings []Warning }
Change describes a reload.
type CommentStyle ¶
type CommentStyle uint8
CommentStyle selects which '#' start a comment. A line whose first non-blank character is '#' is always a comment, and a '#' inside a quoted value never starts one.
const ( // CommentSpaced: a '#' preceded by a space or a tab ends the value, so // a value may start with '#' (colors) or contain one (a#b). CommentSpaced CommentStyle = iota // CommentLine: only whole-line comments; a value may contain any '#'. CommentLine // CommentAny: any '#' ends the value. CommentAny )
type Defaulter ¶
type Defaulter interface{ SetDefaults() }
Defaulter is implemented by a config, or by the element of a map of structs, that fills in its defaults. Load calls it before decoding; a new map element calls it before its first key is set.
type Entry ¶
type Entry struct {
Key string
Value string // trimmed, comment removed, unquoted unless Options.RawValues
Line int // 1-based
}
Entry is one `key = value` assignment.
type Live ¶
type Live[T any] struct { // contains filtered or unexported fields }
Live is a config that reloads itself when its file changes. Get is safe for concurrent use; a config it returned is never modified, a reload replaces it.
func (*Live[T]) Run ¶
Run watches the file and reloads it until ctx ends. A reload that fails, or a removed file, keeps the current config. A save that changes no value (a comment, spacing, the same file written again) reloads nothing. Run returns nil when ctx ends, or the error of the watcher. Call it once.
type LiveOptions ¶
type LiveOptions[T any] struct { Options Watch WatchOptions // OnChange is called from Run after each reload that changed a value. OnChange func(Change[T]) // OnError is called from Run when a reload fails; the current config // stays in place. A removed file reports an error wrapping // fs.ErrNotExist. OnError func(error) }
LiveOptions controls a Live config.
type Options ¶
type Options struct {
// Strict rejects the whole file on the first problem.
Strict bool
// Comments selects which '#' start a comment.
Comments CommentStyle
// AllowDuplicates lets a repeated key override the earlier one
// silently. Otherwise the last one still wins, with a warning.
AllowDuplicates bool
// AllowUnknown ignores keys the target struct does not declare.
AllowUnknown bool
// RawValues keeps the quotes of "..." and '...' values.
RawValues bool
// RequireFile makes a missing file an error for Load and Live. By
// default a missing file gives the defaults.
RequireFile bool
// MaxBytes bounds the file size: 0 means DefaultMaxBytes, a negative
// value means no limit. A larger file is an error, even when lenient.
MaxBytes int64
// MaxLine bounds the length of a line: 0 means DefaultMaxLine, a
// negative value means no limit.
MaxLine int
}
Options controls parsing and decoding. The zero value is lenient: a problem becomes a warning and the rest of the file still applies.
type Path ¶
type Path string
Path is a file system path value: it accepts an absolute path or one starting with ~/ (the home directory), and stores it cleaned. Use it as a field type to validate paths while decoding.
func (Path) MarshalText ¶
MarshalText implements encoding.TextMarshaler.
func (*Path) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler.
type Validator ¶
type Validator interface{ Validate() error }
Validator is implemented by a config that checks itself after decoding, for rules that span several keys. Its error always fails the load.
type ValueError ¶
type ValueError string
ValueError is an error message that is safe to show in a Warning: it does not contain the value. Return one from UnmarshalText to explain a rejected value; any other error is reported as "invalid <type>".
func (ValueError) Error ¶
func (e ValueError) Error() string
type Warning ¶
type Warning struct {
Line int // 0 when the problem is not tied to a line
Key string // empty when there is no key
Msg string
}
Warning is a problem found in a file. It never contains a value, so it is safe to log even when the file holds secrets. In strict mode the first warning is returned as the error.
func Decode ¶
Decode sets the fields of v, a pointer to a struct, from entries in order, so a repeated key ends with its last valid value. An invalid value is reported and leaves its field unchanged, or fails the decode in strict mode. An unknown key is reported unless Options.AllowUnknown is set or a kv:"*" field collects it. Validate runs last when v implements Validator.
func Load ¶
Load reads the config file at path into a new T: it starts from the defaults (SetDefaults when *T implements Defaulter), then applies the file. A missing file gives the defaults, unless opts.RequireFile is set.
type WatchOptions ¶
type WatchOptions struct {
// Debounce is the quiet time after a change before it is reported:
// every write restarts it, so a file is never read half written. 0
// means DefaultDebounce; a negative value reports each change at once.
Debounce time.Duration
// Dir watches every file directly inside the path, a directory, instead
// of the path itself. Hidden files (".swp") and backups ("~") are
// ignored and subdirectories are not watched.
Dir bool
// Poll, when positive, checks the files at this interval instead of
// using the native OS notifications. Native watching also falls back to
// polling every second where it is not available.
Poll time.Duration
// Ready, when not nil, is closed once the watches are installed: a
// change after that point is reported.
Ready chan<- struct{}
}
WatchOptions controls Watch.