chore(main): release 0.2.1
go-config
Convert env, YAML, JSON, and INI to and from Go map[string]any and structs. Multi-source inputs merge with deep map merge: nested maps combine, scalar leaves are last-write-wins, and slices default to replace (opt-in concat via WithSliceMerge). Keys are normalized with a configurable lower+alnum rule so sub-service, SUB_SERVICE, and SubService line up across formats. Built on go-viper/mapstructure/v2.
Architecture
flowchart TB
Sources["Sources\nbytes reader file URL process env"]
Parser["Parser\ngodotenv yaml ini json"]
Norm["keymap.Walk\nNormalizer"]
MergeOp["merge.DeepMerge"]
Map["map string any"]
MS["structconv\nmapstructure v2"]
Struct["Go struct"]
Sources --> Parser --> Norm --> MergeOp --> Map
Map -->|Unmarshal| MS --> Struct
Struct -->|Marshal| MS --> Map
Map -->|WriteTo Marshal| Parser
Hero example
yamlCfg := yaml.New(yaml.WithURL("https://raw.githubusercontent.com/eSlider/mail-archive/refs/heads/master/docker-compose.yml"))
envCfg := env.New(
env.WithFile(".default.env"), // lowest priority
env.WithFile(".env"),
env.WithCurrentEnvironment(), // highest priority — process env wins
)
var svc MyService
_ = yamlCfg.Unmarshal(&svc)
_ = envCfg.Unmarshal(&svc) // later sources override earlier scalar leaves; maps recurse
- Maps merge recursively (sub-trees are combined, not replaced wholesale).
- Scalar leaves: last-write-wins when you list sources lowest → highest priority.
- Slices:
merge.Replaceby default; useWithSliceMerge(merge.Concat)to append.
Runnable offline variant: see Example_hero_offline in example_hero_test.go.
Install
go get github.com/eslider/go-config
go install github.com/eslider/go-config/cmd/envc@latest
Quick start
1. Single YAML file
c := yaml.New(yaml.WithFile("config.yaml"))
var cfg AppConfig
if err := c.Unmarshal(&cfg); err != nil { /* ... */ }
2. JSON over HTTPS with a header
c := json.New(
json.WithURL("https://api.example.com/v1/config.json"),
json.WithHTTPHeader("Authorization", "Bearer "+token),
)
var cfg AppConfig
_ = c.Unmarshal(&cfg)
3. Cross-format conversion (YAML → JSON)
ctx := context.Background()
m, _ := yaml.New(yaml.WithFile("in.yaml")).Map(ctx)
b, _ := json.New().Marshal(m)
os.WriteFile("out.json", b, 0o644)
CLI: envc
| Command | Purpose |
|---|---|
envc convert --from yaml --to env --input - --output - |
Convert stdin YAML to dotenv on stdout |
envc get --from yaml --path service.name config.yaml |
Print one path (dot-separated; segments normalized like codecs) |
envc merge --from yaml --to json --output out.json a.yaml b.yaml |
Deep-merge multiple YAML files, emit JSON |
API (all codecs)
| Method | Description |
|---|---|
New(opts...) |
Construct codec |
Map(ctx) |
Merged map[string]any |
Unmarshal(dst) / UnmarshalContext(ctx, dst) |
Decode into struct (or map) |
Marshal(src) / WriteTo(w, src) |
Encode struct or map[string]any |
Shared options (each subpackage): WithBytes, WithReader, WithFile, WithURL, WithHTTPHeader, WithHTTPClient, WithKeyNormalizer, WithSliceMerge, WithTrim, WithWeaklyTyped, WithTagName, WithDecodeHook.
env adds: WithCurrentEnvironment, WithPrefix.
Cross-format mapping
| Go | YAML | ENV |
|---|---|---|
Service.SubService.Name |
service.sub-service.name |
SERVICE_SUBSERVICE_NAME |
INI uses dotted sections, e.g. [service.subservice] with name=....
Related libraries
| Module | Role |
|---|---|
| go-matrix-bot | Matrix bots |
| go-onlyoffice | OnlyOffice API |
| go-ollama | Ollama client |
Release flow
Versions are computed from git history, never hardcoded in source. Commit with
Conventional Commits (feat:, fix:, feat!: …)
and the pipeline takes care of the rest:
- Every push / PR runs
test.yml(matrix: Go 1.22, 1.23, stable × linux/macOS/windows,-race -shuffle=on) andlint.yml(go vet+golangci-lint). - On merges to
main,release-please.ymlparses commits since the last tag and opens a "release PR" that bumpsCHANGELOG.mdand.release-please-manifest.jsonto the next SemVer. - Merging that PR creates the git tag
vX.Y.Zand a GitHub Release. - The tag triggers
release.yml, which runs GoReleaser to cross-compileenvc(linux/darwin/windows × amd64/arm64) with-X main.version={{.Version}}injected at link time and attach archives +checksums.txtto the release.
No version string lives in Go source — envc version prints the value baked in by the
release build, or dev for local go install builds.
Decisions
Repo-local ASRs: docs/asr/README.md.
License
MIT © Andriy Oblivantsev