feat(go-config): multi-format codecs, envc CLI, fixtures, ASRs

- Module github.com/eslider/go-config with env/yaml/json/ini packages
- internal: source, keymap, merge, structconv, bytesutil
- cmd/envc: convert, get, merge
- docs/asr ASR-0001..0008, README/CHANGELOG/AGENTS refresh
- golangci-lint v2 config; tests + fixtures

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-02 14:04:01 +01:00
co-authored by Cursor
parent 288a5b600c
commit c575341178
75 changed files with 2810 additions and 528 deletions
+83
View File
@@ -0,0 +1,83 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"strings"
"github.com/eslider/go-config/internal/bytesutil"
"github.com/eslider/go-config/internal/keymap"
)
// RunGet implements: envc get --from F --path p [input].
func RunGet(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("get", flag.ContinueOnError)
fs.SetOutput(stderr)
from := fs.String("from", "", "format: yaml|json|ini|env")
path := fs.String("path", "", "dot-separated path (e.g. service.subservice.name)")
if err := fs.Parse(args); err != nil {
return 2
}
if *from == "" || *path == "" {
_, _ = fmt.Fprintln(stderr, "get: --from and --path are required")
return 2
}
pos := fs.Args()
input := "-"
if len(pos) > 0 {
input = pos[0]
}
ctx := context.Background()
src := openSource(input, stdin)
b, err := bytesutil.ReadAll(ctx, src)
if err != nil {
_, _ = fmt.Fprintf(stderr, "get: read: %v\n", err)
return 1
}
m, err := loadMapFromBytes(ctx, *from, b)
if err != nil {
_, _ = fmt.Fprintf(stderr, "get: parse: %v\n", err)
return 1
}
v, err := walkPath(m, strings.Split(*path, "."))
if err != nil {
_, _ = fmt.Fprintf(stderr, "get: %v\n", err)
return 1
}
switch val := v.(type) {
case string, bool, float64, int, int64, nil:
_, _ = fmt.Fprintln(stdout, val)
default:
enc := json.NewEncoder(stdout)
enc.SetIndent("", " ")
if err := enc.Encode(val); err != nil {
_, _ = fmt.Fprintf(stderr, "get: encode: %v\n", err)
return 1
}
}
return 0
}
func walkPath(m map[string]any, parts []string) (any, error) {
var cur any = m
for i, p := range parts {
if p == "" {
continue
}
pk := keymap.LowerAlnum(p)
switch t := cur.(type) {
case map[string]any:
nx, ok := t[pk]
if !ok {
return nil, fmt.Errorf("missing key %q (normalized %q) at segment %d", p, pk, i)
}
cur = nx
default:
return nil, fmt.Errorf("not a map at segment %d", i)
}
}
return cur, nil
}