feat: add TOML codec and envc format support

New toml package (pelletier/go-toml/v2) matching yaml/json options and merge flow.
envc convert/get/merge accept --from/--to toml; fixture and CLI tests; docs refresh.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-03 10:42:21 +01:00
co-authored by Cursor
parent 2d7b97f5ae
commit 474cd88739
16 changed files with 307 additions and 16 deletions
+7 -2
View File
@@ -11,6 +11,7 @@ import (
"github.com/eslider/go-config/ini"
"github.com/eslider/go-config/internal/bytesutil"
libjson "github.com/eslider/go-config/json"
"github.com/eslider/go-config/toml"
"github.com/eslider/go-config/yaml"
yaml3 "gopkg.in/yaml.v3"
)
@@ -19,8 +20,8 @@ import (
func RunConvert(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("convert", flag.ContinueOnError)
fs.SetOutput(stderr)
from := fs.String("from", "", "source format: yaml|json|ini|env")
to := fs.String("to", "", "target format: yaml|json|ini|env")
from := fs.String("from", "", "source format: yaml|json|toml|ini|env")
to := fs.String("to", "", "target format: yaml|json|toml|ini|env")
input := fs.String("input", "-", "input path, URL, or - for stdin")
output := fs.String("output", "-", "output path or - for stdout")
if err := fs.Parse(args); err != nil {
@@ -75,6 +76,8 @@ func loadMapFromBytes(ctx context.Context, format string, b []byte) (map[string]
return ini.New(ini.WithBytes(b)).Map(ctx)
case "env":
return env.New(env.WithBytes(b)).Map(ctx)
case "toml":
return toml.New(toml.WithBytes(b)).Map(ctx)
default:
return nil, fmt.Errorf("unknown format %q", format)
}
@@ -90,6 +93,8 @@ func marshalMap(format string, m map[string]any) ([]byte, error) {
return ini.New().Marshal(m)
case "env":
return env.New().Marshal(m)
case "toml":
return toml.New().Marshal(m)
default:
return nil, fmt.Errorf("unknown format %q", format)
}
+12
View File
@@ -8,6 +8,18 @@ import (
"testing"
)
func TestRunConvert_TOMLToJSON(t *testing.T) {
var stdout, stderr bytes.Buffer
stdin := strings.NewReader("[app]\nport = 8080\n")
code := RunConvert([]string{"--from", "toml", "--to", "json", "--input", "-"}, stdin, &stdout, &stderr)
if code != 0 {
t.Fatalf("stderr: %s", stderr.String())
}
if !strings.Contains(stdout.String(), `"port"`) || !strings.Contains(stdout.String(), "8080") {
t.Fatalf("out: %s", stdout.String())
}
}
func TestRunConvert_YAMLToJSON(t *testing.T) {
var stdout, stderr bytes.Buffer
stdin := strings.NewReader("a: 1\n")
+1 -1
View File
@@ -1,3 +1,3 @@
// Command envc converts, queries, and merges configuration between env, YAML,
// JSON, and INI formats.
// JSON, TOML, and INI formats.
package main
+1 -1
View File
@@ -16,7 +16,7 @@ import (
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")
from := fs.String("from", "", "format: yaml|json|toml|ini|env")
path := fs.String("path", "", "dot-separated path (e.g. service.subservice.name)")
if err := fs.Parse(args); err != nil {
return 2
+1 -1
View File
@@ -16,7 +16,7 @@ var (
// printUsage writes the root help text to w.
func printUsage(w io.Writer) {
_, _ = io.WriteString(w, `envc — convert, query, and merge configuration across env, YAML, JSON, and INI.
_, _ = io.WriteString(w, `envc — convert, query, and merge configuration across env, YAML, JSON, TOML, and INI.
Usage:
envc <command> [arguments]
+2 -2
View File
@@ -15,8 +15,8 @@ import (
func RunMerge(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("merge", flag.ContinueOnError)
fs.SetOutput(stderr)
from := fs.String("from", "", "input format: yaml|json|ini|env")
to := fs.String("to", "", "output format: yaml|json|ini|env")
from := fs.String("from", "", "input format: yaml|json|toml|ini|env")
to := fs.String("to", "", "output format: yaml|json|toml|ini|env")
output := fs.String("output", "-", "output path or - for stdout")
if err := fs.Parse(args); err != nil {
return 2