sxcli.devSimple Extensible CLI

sxcli.dev/conf/engine

go
go
import "sxcli.dev/conf/engine"

Package engine implements the configuration machinery of the sxcli framework: schema extraction from tagged config structs, the lenient and strict argument parsers, environment lookup, config file discovery and transcoding, and source merging with in-place struct filling. Like the other internal packages it is framework-ignorant: config structs arrive as named Sections built by the caller, and format providers arrive through a structural interface that the root package's ConfigFormatProvider satisfies implicitly.

Index#

Constants#

CoreID is the reserved service id of the framework core: the name of its config section, of the virtual root the resolver expands from, and of the synthesized introspection entry. The root package derives its reserved-id constant from this one.

go
go
const CoreID = "core"

DefaultMaxSize is the config file size cap applied when Sources does not set one: 1 MiB covers any sane configuration.

go
go
const DefaultMaxSize = 1 << 20

func HasPositionalssrc#

go
go
func HasPositionals(cfgPtr reflect.Type) bool

HasPositionals reports whether a config struct TYPE declares any pos-tagged field — the commit-time probe behind "positionals are applet-only".

func OpenPinnedsrc#

go
go
func OpenPinned(path string) (io.ReadCloser, error)

OpenPinned opens a binary-companion config file refusing a symlink at the final path component (O_NOFOLLOW, enforced atomically by the kernel — no check-then-open race): the companion must be a regular file that really lives next to the real binary.

func ParseFromVersionssrc#

go
go
func ParseFromVersions(c *fail.Collector, entries []string) (map[string]uint32, *uint32)

ParseFromVersions parses --from-version entries: repeatable "section=N" pairs plus at most one bare "N" (the single-section sugar). Violations land in c; both doors share this grammar.

func PeekCoresrc#

go
go
func PeekCore(c *fail.Collector, appletID string, src Sources, core []Contribution)

PeekCore is the first pipeline pass: it leniently fills the composite core from environment and arguments only — no file can be located before --config is known. File-sourced core values arrive later via Files.ApplyCore. The caller owns the contribution structs and must hand over pristine ones.

func ProbeFieldssrc#

go
go
func ProbeFields(cfgPtr any) map[string]ProbedField

ProbeFields returns the settable fields of a config struct keyed by go field name ("A.B" for nested), for registration-time metadata validation. Extraction violations are ignored here; ValidateConfigType reports them.

func ProbeTypesrc#

go
go
func ProbeType(cfgPtr reflect.Type) map[string]ProbedField

ProbeType is ProbeFields without an instance: the settable fields of a config struct TYPE (a pointer-to-struct type), for type-level metadata validation at the registration commit, where no instance exists yet. Probed Values are zero; the value-level default-in-domain check runs later, at Build, over ProbeFields of the real instance.

func StatRegularsrc#

go
go
func StatRegular(path string) (int64, error)

StatRegular probes one config source: a config file must resolve to a regular file. os.Stat follows symlinks, so a symlink to a regular file passes (symlink-overlay distros keep working) — it is the resolved target that must be regular. FIFOs are refused here, before any open could block on them; devices and directories get a clean startup error instead of downstream read weirdness.

func ValidateConfigTypesrc#

go
go
func ValidateConfigType(id string, cfgPtr reflect.Type) error

ValidateConfigType validates the tags and field types of a config struct TYPE (a pointer-to-struct type) at the registration commit, where no instance exists yet — including the Version mandate: the version key must exist in files BEFORE any migration needs it, so it is required from a schema's first release. (The factory default, an instance-level fact, is checked when the schema is built.)

type Contributionsrc#

Contribution is one flat struct claiming keys under the composite core section. The section name is fixed — that absence of a name is what distinguishes it from a Section. Contributors share the "core" namespace in every source; a json key claimed twice is a violation.

go
go
type Contribution struct {
    Ptr  any   // pointer to the struct; nil contributes nothing
    Meta *Meta // its own field metadata, nil when none declared
}

func CoreContribsrc#

go
go
func CoreContrib(core *Core) Contribution

CoreContrib pairs the engine's own Core with its metadata: the first contribution of every composite core, by convention — first so its short forms win.

type Coresrc#

Core is the engine's own configuration — the machinery knobs only, living under the reserved section name "core". All three are run-scoped (dump:"-"): excluded from --write-config output AND refused loudly from config files — a file setting them would be self-triggering (every run becoming help output, or a config write to an attacker-chosen path). writeConfig and help additionally carry env:"-": an inherited APPLETID_HELP=true would be the same persistent denial — they are argument-only. config keeps its env door (a legitimate deployment pattern; the pointed-at file still passes every gate). Anything else under "core" — the framework's service controls, say — arrives as a further Contribution.

go
go
type Core struct {
    Config         string `json:"config" conf:"config,c" dump:"-" usage:"path of the configuration file, replaces the location search"`
    WriteConfig    bool   `json:"writeConfig" conf:"write-config" dump:"-" env:"-" usage:"write the merged configuration to the --config target (or stdout) and exit"`
    Help           bool   `json:"help" conf:"help,h" dump:"-" env:"-" usage:"print the applet's argument schema and exit"`
    ValidateConfig bool   `json:"validateConfig" conf:"validate-config" dump:"-" env:"-" usage:"run every configuration check, report the violations and exit"`
}

type Fieldsrc#

Field is one settable config struct field.

go
go
type Field struct {
    ServiceID string
    Name      string   // go field name path for error messages, e.g. "Rotation.MaxAge"
    Path      []int    // reflect index path into the config struct
    JSONPath  []string // json object path inside the service's section
    Long      string   // long argument name; "" = file-only
    Short     string   // single-character short form; "" = none
    EnvName   string   // resolved environment variable name; "" = not env-settable
    NoEnv     bool     // env:"-": no environment variable, not even derived
    Usage     string
    Type      reflect.Type
    IsSlice   bool
    Transient bool  // dump:"-": run-scoped — excluded from --write-config output AND refused from config files
    Allowed   []any // closed value domain from registration metadata; values are of the field's type (element type for slices)
    Doc       string
    Hint      ValueHint // advisory value denotation from registration metadata
    // contains filtered or unexported fields
}

type FieldMetasrc#

FieldMeta annotates one config field. Allowed values are already converted to the field's own type.

go
go
type FieldMeta struct {
    Allowed []any
    Doc     string
    Hint    ValueHint
}

type Filessrc#

Files is the parsed content of every loaded config file: one service-id → raw section map per file, in merge order (later files override earlier ones), plus the providers that transcoded them.

go
go
type Files struct {
    Used []Provider
    // contains filtered or unexported fields
}

func LoadFilessrc#

go
go
func LoadFiles(c *fail.Collector, src Sources, explicit string) *Files

LoadFiles discovers, transcodes and parses the configuration files of one invocation. explicit is the resolved --config path: when non-empty it replaces the location search entirely and must exist. Otherwise every base path in src.Locations is probed with ".json" plus every registered provider extension; more than one existing candidate at the same location is ambiguous and a startup violation, as is a file whose extension no provider handles.

Existence and size are probed via Stat before any file is opened: an oversized config is never opened, read or parsed.

func (*Files) ApplyCoresrc#

go
go
func (f *Files) ApplyCore(c *fail.Collector, appletID string, src Sources, core []Contribution)

ApplyCore fills the composite core in full precedence order once the files are loaded: file sections, then environment, then arguments. These are the values the closure resolution must use — a control list in a config file is only visible here. The caller owns the contribution structs and must hand over pristine ones (slice values already filled by a peek would double up).

type HelpSectionsrc#

HelpSection is one service's schema for help rendering.

go
go
type HelpSection struct {
    ID     string
    Fields []*Field
}

type Loadedsrc#

Loaded is the outcome of a strict Schema.Apply.

go
go
type Loaded struct {
    // Positionals holds the raw trailing tokens ONLY when the active
    // config declares no pos fields; any declaration makes the schema
    // own the tail entirely (assignment, counting, violations).
    Positionals []string
}

type Locationsrc#

Location is one config file search location: a base path without extension. A pinned location is security-sensitive — the binary companion — and its candidates are opened through Sources.OpenPinned, which must refuse symlinks so the file really lives at Base's directory.

go
go
type Location struct {
    Base   string
    Pinned bool
}

func CompanionLocationsrc#

go
go
func CompanionLocation(name string) (Location, bool)

CompanionLocation returns the pinned binary-companion location, or false when the real binary path cannot be resolved.

func ProductionLocationssrc#

go
go
func ProductionLocations(name string) []Location

ProductionLocations returns the full config search of one name: companion, system, user, in merge order. Callers with tier policy (the front door's Suppress) compose the tier constructors instead.

func SystemLocationsrc#

go
go
func SystemLocation(name string) Location

SystemLocation returns the system-wide location (/etc on unix, %ProgramData% on windows).

func UserLocationsrc#

go
go
func UserLocation(name string) (Location, bool)

UserLocation returns the per-user location (the XDG config dir), or false when it cannot be resolved.

type Metasrc#

Meta is the internal, normalized form of a service's registration metadata (the root package's Metadata, validated and converted by its metadata check).

go
go
type Meta struct {
    Description string
    Fields      map[string]FieldMeta // keyed by go field name, "A.B" for nested
}

type ProbedFieldsrc#

ProbedField describes one settable config field for registration-time metadata validation: its type (element type for slices), slice-ness and current (default) value.

go
go
type ProbedField struct {
    Type    reflect.Type
    IsSlice bool
    Value   reflect.Value
}

type Providersrc#

Provider is the structural twin of the root package's ConfigFormatProvider, redeclared here to avoid an import cycle; root provider instances satisfy it without adaptation.

go
go
type Provider interface {
    Extensions() []string
    ToJSON(in io.Reader) (io.Reader, error)
    FromJSON(in io.Reader) (io.Reader, error)
}

type Schemasrc#

Schema is the full argument/env/file schema of one invocation: the core plus every closure member owning a config struct.

go
go
type Schema struct {
    // contains filtered or unexported fields
}

func NewSchemasrc#

go
go
func NewSchema(c *fail.Collector, appletID string, core []Contribution, sections []Section, suppress []string) *Schema

NewSchema builds the full schema of one invocation: the core config first (so its short forms win), then every member owning a config struct. Core fields whose long name appears in suppress are removed from the schema entirely: the argument becomes unknown, the env var is never consulted, and the file key turns into an unknown-key violation. Duplicate long argument names and duplicate explicit env names across the schema are violations; short-form collisions are resolved first-come-first-served.

func (*Schema) Applysrc#

go
go
func (s *Schema) Apply(c *fail.Collector, files *Files, src Sources) Loaded

Apply is the strict pipeline pass over the full schema: files, then environment, then arguments, unknown argument = violation. It fills every member's config struct in place and returns the trailing positionals.

func (*Schema) HelpSectionssrc#

go
go
func (s *Schema) HelpSections() []HelpSection

HelpSections returns the schema's services and their fields for help rendering, the core first.

func (*Schema) MarshalIndentsrc#

go
go
func (s *Schema) MarshalIndent() ([]byte, error)

MarshalIndent serializes the merged configuration of every schema member — the exact values the config structs hold — as the core's native JSON, service sections keyed by id. Empty values (zero scalars, empty slices) are skipped, and sections or nested objects they would leave empty are omitted entirely, so a default-heavy configuration dumps small. Consequence, documented: a field explicitly set to its zero value is indistinguishable from an unset one and falls back to its default when the dump is loaded.

func (*Schema) PositionalFieldssrc#

go
go
func (s *Schema) PositionalFields() ([]*Field, *Field)

PositionalFields exposes the active config's declared positionals: the indexed fields in order, and the trailing collector (nil when absent) — what help renderers and tooling enumerate.

func (*Schema) UpgradeFilesrc#

go
go
func (s *Schema) UpgradeFile(c *fail.Collector, path string, from map[string]uint32, bare *uint32, src Sources)

UpgradeFile is the pure file transform behind --upgrade-config: it reads ONE file, migrates every schema-owned section to its current version, and writes the file back in its own format. No merge, no other sources, no defaults injection. Sections the schema does not own — a shared file serves other tools — pass through verbatim.

A section carrying a version key migrates by it; a contradicting from entry is an error. A versionless section requires its version asserted via from ("section" → N) or the bare assertion (legal only when exactly one versionless owned section exists). The assertion declares the section a COMPLETE version-N document — the tool warns which top-level keys were absent from the input, since the chain materializes them.

func (*Schema) Valuesrc#

go
go
func (s *Schema) Value(f *Field) any

Value returns one field's current (merged) value, rendered like the --write-config output: durations as unit-suffixed strings.

func (*Schema) WriteHelpsrc#

go
go
func (s *Schema) WriteHelp(w io.Writer)

WriteHelp renders the schema grouped by section: every argument with its short form, usage and environment name against the current (merged) values. Plain text — the framework renders its own help through its translation seam; this is the engine's untranslated canonical form.

func (*Schema) WriteMergedsrc#

go
go
func (s *Schema) WriteMerged(stdout io.Writer, target string, src Sources) error

WriteMerged serves --write-config: the merged configuration goes to stdout as json when target is empty, else to the target file in the format its extension names (a registered format provider transcodes the native json).

type Sectionsrc#

Section is one named contributor to a schema: a config struct under its operator-facing section name. The framework maps its accepted services to sections; a standalone caller builds them directly. The engine never sees anything richer — this is the whole seam.

go
go
type Section struct {
    Name  string // section name: config-file key, env prefix
    Ptr   any    // pointer to the config struct; nil contributes nothing
    Meta  *Meta  // field metadata, nil when none declared
    Steps []Step // migration chain, oldest first; empty = never evolved
}

type Sourcessrc#

Sources carries every external input of configuration loading, all injectable for hermetic tests.

go
go
type Sources struct {
    Args         []string                            // argv without the binary name and applet selector
    LookupEnv    func(string) (string, bool)         // os.LookupEnv in production
    Locations    []Location                          // search locations in merge order
    Stat         func(string) (int64, error)         // file size probe; missing files must report fs.ErrNotExist
    Lstat        func(string) error                  // pinned-location cross-check: nil when something occupies the path ITSELF (e.g. a dangling symlink Stat cannot see)
    Open         func(string) (io.ReadCloser, error) // os.Open in production; missing files must report fs.ErrNotExist
    OpenPinned   func(string) (io.ReadCloser, error) // symlink-refusing opener (O_NOFOLLOW-style) for pinned locations
    Providers    []Provider                          // registered format providers, registration order
    SuppressCore []string                            // long names of core fields the binary suppressed (fw.Suppress)
    MaxSize      int64                               // config file size cap in bytes; <=0 means the 1 MiB default
}

func ProductionSourcessrc#

go
go
func ProductionSources(name string) Sources

ProductionSources assembles the real-world Sources of one name: os argument vector and environment, the standard search locations with the hardening stack (regular files only, pinned symlink refusal).

type Stepsrc#

Step is one erased link of a section's migration chain: it converts the config document of one schema version to the next. Build steps with NewStep (or the front door's Step) — the generic constructor is what keeps the conversion typed.

go
go
type Step struct {
    // contains filtered or unexported fields
}

func NewStepsrc#

go
go
func NewStep[From, To any](from uint32, fn func(From) To) Step

NewStep declares the conversion from schema version `from` to the next: fn receives the strictly-parsed old document and returns its successor. The chain's link types are verified when the schema is built; the erased call can never mismatch at migration time.

type ValueHintsrc#

ValueHint is the advisory declaration of what a field's value denotes. Unlike Allowed it is never enforced — a hinted file may not exist yet (--config with --write-config creates it); it travels the schema so tooling (completion, documentation) can act on it. The root package re-exports the constants under the same names.

go
go
type ValueHint int
go
go
const (
    HintNone ValueHint = iota
    HintFile
    HintDirectory
    HintServiceID
)