Getting started
Install#
Add the framework to your module:
go get sxcli.dev/fw
Your first applet#
An applet is a service implementing Configured() error and Run() int,
plus a config struct whose field values are its defaults. fw.Solo is the
single-applet front door: no subcommands, no ceremony — the binary is
the applet.
package main import ( "fmt" "sxcli.dev/fw" ) // ID is the service's public handle: import-path-shaped, and a // constant so compositions and inject tags can name it. const ID = "example.com/myapp/hello" type Config struct { Version uint32 `json:"version"` Name string `json:"name" conf:"name,n" usage:"who to greet"` } type Hello struct{ cfg Config } func (h *Hello) Configured() error { return nil } func (h *Hello) Run() int { fmt.Println("hello,", h.cfg.Name) return 0 } func main() { fw.Solo(fw.NewRegistration(ID, func() *Hello { return &Hello{cfg: Config{Version: 1, Name: "world"}} }, func(h *Hello) *Config { return &h.cfg }, ).Alias("hello")) }
Three things in that registration carry weight:
- The id —
example.com/myapp/hello— is import-path-shaped and unique by construction. It is what code refers to: inject tags, compositions,Order. - The alias —
hello— is the operator's name for the service. It prefixes the env vars and names the config-file section. Versionis not decoration. Config schemas are versioned so that files written by an older build keep working; see Config migrations.
Run it#
With a single applet, the whole argument vector belongs to it:
$ hello # hello, world $ hello --name gopher # hello, gopher $ hello -n gopher # the short name from conf:"name,n" $ HELLO__NAME=go hello # derived: alias, two underscores, conf name $ hello --help # full argument schema, current effective values
The same field is settable from a config file, the environment, or the
command line — precedence is
defaults < config files < environment < arguments. In a config file the
service's keys live in a section named after its alias, alongside the
schema version:
{"hello": {"version": 1, "name": "gopher"}}
Growing past one applet#
When the binary needs more than one applet, packages catalog their
services in init() and export an ID for others to name:
package hello import ( "fmt" "sxcli.dev/fw" ) const ID = "example.com/myapp/hello" type Config struct { Version uint32 `json:"version"` Name string `json:"name" conf:"name,n" usage:"who to greet"` } type Hello struct{ cfg Config } func (h *Hello) Configured() error { return nil } func (h *Hello) Run() int { fmt.Println("hello,", h.cfg.Name); return 0 } func init() { fw.NewRegistration(ID, func() *Hello { return &Hello{cfg: Config{Version: 1, Name: "world"}} }, func(h *Hello) *Config { return &h.cfg }, ).Alias("hello").Register() }
main then names what the binary takes:
package main import ( "sxcli.dev/fw" "sxcli.dev/fw/sink/console" "example.com/myapp/hello" ) func main() { fw.Builder(). Accept(hello.ID, console.ID). Main() }
Note what is not happening: no blank imports. A package is imported
because the composition names something it exports, so an import that no
longer matters fails to compile instead of quietly changing behaviour.
fw.Main() is the take-everything shorthand when you genuinely want the
whole catalog.
JSON is the native config format — always enabled, nothing to accept.
Adding configfmt/yaml to the composition teaches the binary .yaml and
.yml on top of it; drop it and JSON configs keep working.
Next: Your config struct — every supported type and tag, and how each value is set from arguments, environment and files.