Skip to content

Commit 4c2163f

Browse files
committed
Use goccy's go-yaml library for yaml editing
1 parent 55f86b5 commit 4c2163f

File tree

5 files changed

+58
-63
lines changed

5 files changed

+58
-63
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/djherbis/nio/v3 v3.0.1
2222
github.com/fatih/color v1.18.0
2323
github.com/go-git/go-git/v5 v5.16.0
24+
github.com/goccy/go-yaml v1.18.0
2425
github.com/gofrs/uuid/v5 v5.3.2
2526
github.com/leonelquinteros/gotext v1.7.1
2627
github.com/mailru/easyjson v0.7.7

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
7575
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
7676
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
7777
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
78+
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
79+
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
7880
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
7981
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
8082
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=

internal/arduino/sketch/testdata/SketchWithProfiles/sketch.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# all the profiles
12
profiles:
23
nanorp:
34
fqbn: arduino:mbed_nano:nanorp2040connect

internal/arduino/sketch/yaml.go

Lines changed: 31 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,78 +16,52 @@
1616
package sketch
1717

1818
import (
19-
"errors"
20-
"fmt"
21-
"strings"
22-
23-
"github.com/arduino/arduino-cli/internal/i18n"
2419
"github.com/arduino/go-paths-helper"
25-
"gopkg.in/yaml.v3"
20+
"github.com/goccy/go-yaml"
21+
"github.com/goccy/go-yaml/parser"
22+
"go.bug.st/f"
2623
)
2724

2825
// updateOrAddYamlRootEntry updates or adds a new entry to the root of the yaml file.
2926
// If the value is empty the entry is removed.
30-
func updateOrAddYamlRootEntry(path *paths.Path, key, newValue string) error {
31-
var srcYaml []string
32-
if path.Exist() {
33-
src, err := path.ReadFileAsLines()
34-
if err != nil {
35-
return err
36-
}
37-
lastLine := len(src) - 1
38-
if lastLine > 0 && src[lastLine] == "" {
39-
srcYaml = src[:lastLine]
40-
} else {
41-
srcYaml = src
42-
}
43-
}
44-
45-
// Generate the new yaml key/value pair
46-
v, err := yaml.Marshal(newValue)
27+
func updateOrAddYamlRootEntry(srcPath *paths.Path, key, newValue string) error {
28+
// First encode the new value as YAML and parse it to an AST
29+
newValueYaml, err := yaml.Marshal(map[string]string{key: newValue})
4730
if err != nil {
4831
return err
4932
}
50-
updatedLine := key + ": " + strings.TrimSpace(string(v))
33+
newValueAst := f.Must(parser.ParseBytes(newValueYaml, parser.ParseComments))
5134

52-
// Update or add the key/value pair into the original yaml
53-
addMissing := (newValue != "")
54-
for i, line := range srcYaml {
55-
if strings.HasPrefix(line, key+": ") {
56-
if newValue == "" {
57-
// Remove the key/value pair
58-
srcYaml = append(srcYaml[:i], srcYaml[i+1:]...)
59-
} else {
60-
// Update the key/value pair
61-
srcYaml[i] = updatedLine
62-
}
63-
addMissing = false
64-
break
65-
}
35+
// If the src file does not exist, we can just write the new value
36+
if !srcPath.Exist() {
37+
return srcPath.WriteFile(newValueYaml)
38+
}
39+
40+
// Read the source YAML file and parse it to an AST
41+
srcYaml, err := srcPath.ReadFile()
42+
if err != nil {
43+
return err
6644
}
67-
if addMissing {
68-
lastLine := len(srcYaml) - 1
69-
if lastLine >= 0 && srcYaml[lastLine] == "" {
70-
srcYaml[lastLine] = updatedLine
71-
} else {
72-
srcYaml = append(srcYaml, updatedLine)
73-
}
45+
srcAst, err := parser.ParseBytes(srcYaml, parser.ParseComments)
46+
if err != nil {
47+
return err
7448
}
7549

76-
// Validate the new yaml
77-
dstYaml := []byte(strings.Join(srcYaml, fmt.Sprintln()) + fmt.Sprintln())
78-
var dst interface{}
79-
if err := yaml.Unmarshal(dstYaml, &dst); err != nil {
80-
return fmt.Errorf("%s: %w", i18n.Tr("could not update sketch project file"), err)
50+
// Perform the merge operation
51+
keyYmlPath, err := yaml.PathString("$")
52+
if err != nil {
53+
return err
8154
}
82-
dstMap, ok := dst.(map[string]interface{})
83-
if !ok {
84-
return errors.New(i18n.Tr("could not update sketch project file"))
55+
if n, _ := keyYmlPath.FilterFile(srcAst); n == nil {
56+
// In this case the file is empty, we can just write the new value at the bottom
57+
srcYaml = append(srcYaml, '\n')
58+
srcYaml = append(srcYaml, newValueYaml...)
59+
return srcPath.WriteFile(srcYaml)
8560
}
86-
writtenValue, notRemoved := dstMap[key]
87-
if (newValue == "" && notRemoved) || (newValue != "" && newValue != writtenValue) {
88-
return errors.New(i18n.Tr("could not update sketch project file"))
61+
if err := keyYmlPath.MergeFromFile(srcAst, newValueAst); err != nil {
62+
return err
8963
}
9064

9165
// Write back the updated YAML
92-
return path.WriteFile(dstYaml)
66+
return srcPath.WriteFile([]byte(srcAst.String()))
9367
}

internal/arduino/sketch/yaml_test.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
)
2626

2727
func TestYamlUpdate(t *testing.T) {
28-
{
28+
t.Run("UpdateComplexYamlFile/1", func(t *testing.T) {
2929
sample, err := paths.New("testdata", "SketchWithProfiles", "sketch.yml").ReadFile()
3030
require.NoError(t, err)
3131
tmp, err := paths.WriteToTempFile(sample, nil, "")
@@ -43,8 +43,8 @@ func TestYamlUpdate(t *testing.T) {
4343
expected += fmt.Sprintln("default_fqbn: arduino:avr:uno")
4444
expected += fmt.Sprintln("default_port: /dev/ttyACM0")
4545
require.Equal(t, expected, string(updated))
46-
}
47-
{
46+
})
47+
t.Run("UpdateComplexYamlFile/2", func(t *testing.T) {
4848
sample, err := paths.New("testdata", "SketchWithDefaultFQBNAndPort", "sketch.yml").ReadFile()
4949
require.NoError(t, err)
5050
tmp, err := paths.WriteToTempFile(sample, nil, "")
@@ -62,8 +62,19 @@ func TestYamlUpdate(t *testing.T) {
6262
expected := strings.Replace(string(sample), "arduino:avr:uno", "TEST1", 1)
6363
expected = strings.Replace(expected, "/dev/ttyACM0", "TEST2", 1)
6464
require.Equal(t, expected, string(updated))
65-
}
66-
{
65+
})
66+
t.Run("UpdateAnEmptyYamlFile", func(t *testing.T) {
67+
tmp, err := paths.WriteToTempFile([]byte("\n\n"), nil, "")
68+
require.NoError(t, err)
69+
err = updateOrAddYamlRootEntry(tmp, "default_fqbn", "TEST1")
70+
require.NoError(t, err)
71+
72+
updated, err := tmp.ReadFile()
73+
require.NoError(t, err)
74+
expected := "\n\n\ndefault_fqbn: TEST1\n"
75+
require.Equal(t, expected, string(updated))
76+
})
77+
t.Run("UpdateAMissingYamlFile", func(t *testing.T) {
6778
tmp, err := paths.WriteToTempFile([]byte{}, nil, "")
6879
require.NoError(t, err)
6980
require.NoError(t, tmp.Remove())
@@ -74,5 +85,11 @@ func TestYamlUpdate(t *testing.T) {
7485
require.NoError(t, err)
7586
expected := "default_fqbn: TEST1\n"
7687
require.Equal(t, expected, string(updated))
77-
}
88+
})
89+
t.Run("UpdateAYamlFileWithADifferentStructure", func(t *testing.T) {
90+
tmp, err := paths.WriteToTempFile([]byte("- 123\n"), nil, "")
91+
require.NoError(t, err)
92+
err = updateOrAddYamlRootEntry(tmp, "default_fqbn", "TEST1")
93+
require.Error(t, err)
94+
})
7895
}

0 commit comments

Comments
 (0)