edit: keep YAML top-level strings as strings and unescape single quotes (#431)
This commit is contained in:
1 parent
99b1b15d98
commit
563dc83eef
2 files changed
+65
-3
No files matched your search
@@ -5,6 +5,8 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Top-level YAML keys are edited line by line. That keeps every comment and
|
||||
@@ -44,7 +46,9 @@ func SetYAMLTop(path string, kvs ...KV) error {
|
||||
lines := splitLines(string(raw))
|
||||
for _, kv := range kvs {
|
||||
v := toString(kv.Value)
|
||||
if !yamlPlain.MatchString(v) {
|
||||
var decoded any
|
||||
_, isString := kv.Value.(string)
|
||||
if !yamlPlain.MatchString(v) || (isString && (yaml.Unmarshal([]byte(v), &decoded) != nil || decoded != v)) {
|
||||
v = strconv.Quote(v)
|
||||
}
|
||||
lines = setLine(lines, kv.Path, kv.Path+": "+v, nil, func(line string) (string, bool) {
|
||||
@@ -139,8 +143,9 @@ func tomlValue(v string) string {
|
||||
}
|
||||
|
||||
func yamlValue(v string) string {
|
||||
if s, ok := unquote(v); ok {
|
||||
return s
|
||||
var n yaml.Node
|
||||
if err := yaml.Unmarshal([]byte(v), &n); err == nil && len(n.Content) > 0 && n.Content[0].Kind == yaml.ScalarNode {
|
||||
return n.Content[0].Value
|
||||
}
|
||||
if i := strings.Index(v, " #"); i >= 0 {
|
||||
v = strings.TrimSpace(v[:i])
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestYAMLTopStringScalars(t *testing.T) {
|
||||
for _, v := range []string{"null", "true", "123", "a\t\nb"} {
|
||||
p := filepath.Join(t.TempDir(), "goose.yaml")
|
||||
if e := SetYAMLTop(p, KV{"GOOSE_MODEL", v}); e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m map[string]any
|
||||
if e := yaml.Unmarshal(b, &m); e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
if got, ok := m["GOOSE_MODEL"].(string); !ok || got != v {
|
||||
t.Errorf("value %q serialized %q reads %T %v", v, b, m["GOOSE_MODEL"], m["GOOSE_MODEL"])
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestYAMLTopSingleQuotedEscape(t *testing.T) {
|
||||
p := tmpFile(t, "goose.yaml", "GOOSE_MODEL: 'owner''s/model'\n")
|
||||
v, ok := GetYAMLTop(p, "GOOSE_MODEL")
|
||||
if !ok {
|
||||
t.Fatal("model not found")
|
||||
}
|
||||
if v != "owner's/model" {
|
||||
t.Fatalf("got %q", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLTopTypedValues(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "config.yaml")
|
||||
if err := SetYAMLTop(p, KV{"enabled", true}, KV{"count", 123}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m map[string]any
|
||||
if err := yaml.Unmarshal(b, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m["enabled"] != true || m["count"] != 123 {
|
||||
t.Fatalf("types changed: %#v", m)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user