Skip to content

Commit 8e93a68

Browse files
author
Chris McDonnell
committed
Migrate to only doing marshalling twice, and compare via deep copy
1 parent 19d3162 commit 8e93a68

File tree

3 files changed

+134
-141
lines changed

3 files changed

+134
-141
lines changed

pkg/config/app_config.go

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"log"
66
"os"
77
"path/filepath"
8+
"reflect"
89
"strings"
910
"time"
1011

@@ -237,7 +238,17 @@ func migrateUserConfig(path string, content []byte) ([]byte, error) {
237238

238239
// A pure function helper for testing purposes
239240
func computeMigratedConfig(path string, content []byte) ([]byte, error) {
240-
changedContent := content
241+
var err error
242+
var rootNode yaml.Node
243+
err = yaml.Unmarshal(content, &rootNode)
244+
if err != nil {
245+
return nil, fmt.Errorf("failed to parse YAML: %w", err)
246+
}
247+
var originalCopy yaml.Node
248+
err = yaml.Unmarshal(content, &originalCopy)
249+
if err != nil {
250+
return nil, fmt.Errorf("failed to parse YAML, but only the second time!?!? How did that happen: %w", err)
251+
}
241252

242253
pathsToReplace := []struct {
243254
oldPath []string
@@ -248,46 +259,53 @@ func computeMigratedConfig(path string, content []byte) ([]byte, error) {
248259
{[]string{"gui", "windowSize"}, "screenMode"},
249260
}
250261

251-
var err error
252262
for _, pathToReplace := range pathsToReplace {
253-
changedContent, err = yaml_utils.RenameYamlKey(changedContent, pathToReplace.oldPath, pathToReplace.newName)
263+
_, err := yaml_utils.RenameYamlKey(&rootNode, pathToReplace.oldPath, pathToReplace.newName)
254264
if err != nil {
255265
return nil, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %s", path, strings.Join(pathToReplace.oldPath, "."), err)
256266
}
257267
}
258268

259-
changedContent, err = changeNullKeybindingsToDisabled(changedContent)
269+
err = changeNullKeybindingsToDisabled(&rootNode)
260270
if err != nil {
261271
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
262272
}
263273

264-
changedContent, err = changeElementToSequence(changedContent, []string{"git", "commitPrefix"})
274+
err = changeElementToSequence(&rootNode, []string{"git", "commitPrefix"})
265275
if err != nil {
266276
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
267277
}
268278

269-
changedContent, err = changeCommitPrefixesMap(changedContent)
279+
err = changeCommitPrefixesMap(&rootNode)
270280
if err != nil {
271281
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
272282
}
283+
273284
// Add more migrations here...
274285

275-
return changedContent, nil
286+
if !reflect.DeepEqual(rootNode, originalCopy) {
287+
newContent, err := yaml_utils.YamlMarshal(&rootNode)
288+
if err != nil {
289+
return nil, fmt.Errorf("Failed to remarsall!")
290+
}
291+
return newContent, nil
292+
} else {
293+
return content, nil
294+
}
276295
}
277296

278-
func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
279-
return yaml_utils.Walk(changedContent, func(node *yaml.Node, path string) bool {
297+
func changeNullKeybindingsToDisabled(rootNode *yaml.Node) error {
298+
return yaml_utils.Walk(rootNode, func(node *yaml.Node, path string) {
280299
if strings.HasPrefix(path, "keybinding.") && node.Kind == yaml.ScalarNode && node.Tag == "!!null" {
281300
node.Value = "<disabled>"
282301
node.Tag = "!!str"
283-
return true
284302
}
285-
return false
303+
return
286304
})
287305
}
288306

289-
func changeElementToSequence(changedContent []byte, path []string) ([]byte, error) {
290-
return yaml_utils.TransformNode(changedContent, path, func(node *yaml.Node) (bool, error) {
307+
func changeElementToSequence(rootNode *yaml.Node, path []string) error {
308+
return yaml_utils.TransformNode(rootNode, path, func(node *yaml.Node) error {
291309
if node.Kind == yaml.MappingNode {
292310
nodeContentCopy := node.Content
293311
node.Kind = yaml.SequenceNode
@@ -298,15 +316,14 @@ func changeElementToSequence(changedContent []byte, path []string) ([]byte, erro
298316
Content: nodeContentCopy,
299317
}}
300318

301-
return true, nil
319+
return nil
302320
}
303-
return false, nil
321+
return nil
304322
})
305323
}
306324

307-
func changeCommitPrefixesMap(changedContent []byte) ([]byte, error) {
308-
return yaml_utils.TransformNode(changedContent, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) (bool, error) {
309-
changedAnyNodes := false
325+
func changeCommitPrefixesMap(rootNode *yaml.Node) error {
326+
return yaml_utils.TransformNode(rootNode, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) error {
310327
if prefixesNode.Kind == yaml.MappingNode {
311328
for _, contentNode := range prefixesNode.Content {
312329
if contentNode.Kind == yaml.MappingNode {
@@ -318,11 +335,10 @@ func changeCommitPrefixesMap(changedContent []byte) ([]byte, error) {
318335
Kind: yaml.MappingNode,
319336
Content: nodeContentCopy,
320337
}}
321-
changedAnyNodes = true
322338
}
323339
}
324340
}
325-
return changedAnyNodes, nil
341+
return nil
326342
})
327343
}
328344

pkg/utils/yaml_utils/yaml_utils.go

Lines changed: 38 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func UpdateYamlValue(yamlBytes []byte, path []string, value string) ([]byte, err
3535
}
3636

3737
// Convert the updated YAML node back to YAML bytes.
38-
updatedYAMLBytes, err := yamlMarshal(body)
38+
updatedYAMLBytes, err := YamlMarshal(body)
3939
if err != nil {
4040
return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
4141
}
@@ -100,83 +100,57 @@ func lookupKey(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) {
100100
return nil, nil
101101
}
102102

103-
// Walks a yaml document to the specified path, and then applies the transformation to that node.
103+
// Walks a yaml document from the root node to the specified path, and then applies the transformation to that node.
104104
//
105105
// The transform must return true if it made changes to the node.
106106
// If the requested path is not defined in the document, no changes are made to the document.
107107
//
108-
// If no changes are made, the original document is returned.
109-
// If changes are made, a newly marshalled document is returned. (This may result in different indentation for all nodes)
110-
func TransformNode(yamlBytes []byte, path []string, transform func(node *yaml.Node) (bool, error)) ([]byte, error) {
111-
// Parse the YAML file.
112-
var node yaml.Node
113-
err := yaml.Unmarshal(yamlBytes, &node)
114-
if err != nil {
115-
return nil, fmt.Errorf("failed to parse YAML: %w", err)
116-
}
117-
108+
// Returns true if the transform function made changes to any node
109+
func TransformNode(rootNode *yaml.Node, path []string, transform func(node *yaml.Node) error) error {
118110
// Empty document: nothing to do.
119-
if len(node.Content) == 0 {
120-
return yamlBytes, nil
111+
if len(rootNode.Content) == 0 {
112+
return nil
121113
}
122114

123-
body := node.Content[0]
115+
body := rootNode.Content[0]
124116

125-
if didTransform, err := transformNode(body, path, transform); err != nil || !didTransform {
126-
return yamlBytes, err
117+
if err := transformNode(body, path, transform); err != nil {
118+
return err
127119
}
128120

129-
// Convert the updated YAML node back to YAML bytes.
130-
updatedYAMLBytes, err := yamlMarshal(body)
131-
if err != nil {
132-
return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
133-
}
134-
135-
return updatedYAMLBytes, nil
121+
return nil
136122
}
137123

138124
// A recursive function to walk down the tree. See TransformNode for more details.
139-
func transformNode(node *yaml.Node, path []string, transform func(node *yaml.Node) (bool, error)) (bool, error) {
125+
func transformNode(node *yaml.Node, path []string, transform func(node *yaml.Node) error) error {
140126
if len(path) == 0 {
141127
return transform(node)
142128
}
143129

144130
keyNode, valueNode := lookupKey(node, path[0])
145131
if keyNode == nil {
146-
return false, nil
132+
return nil
147133
}
148134

149135
return transformNode(valueNode, path[1:], transform)
150136
}
151137

152-
// takes a yaml document in bytes, a path to a key, and a new name for the key.
138+
// Takes the root node of a yaml document, a path to a key, and a new name for the key.
153139
// Will rename the key to the new name if it exists, and do nothing otherwise.
154-
func RenameYamlKey(yamlBytes []byte, path []string, newKey string) ([]byte, error) {
155-
// Parse the YAML file.
156-
var node yaml.Node
157-
err := yaml.Unmarshal(yamlBytes, &node)
158-
if err != nil {
159-
return nil, fmt.Errorf("failed to parse YAML: %w for bytes %s", err, string(yamlBytes))
160-
}
161-
140+
// Returns true if it found the old path and renamed the key
141+
func RenameYamlKey(rootNode *yaml.Node, path []string, newKey string) (bool, error) {
162142
// Empty document: nothing to do.
163-
if len(node.Content) == 0 {
164-
return yamlBytes, nil
143+
if len(rootNode.Content) == 0 {
144+
return false, nil
165145
}
166146

167-
body := node.Content[0]
147+
body := rootNode.Content[0]
168148

169149
if didRename, err := renameYamlKey(body, path, newKey); err != nil || !didRename {
170-
return yamlBytes, err
171-
}
172-
173-
// Convert the updated YAML node back to YAML bytes.
174-
updatedYAMLBytes, err := yamlMarshal(body)
175-
if err != nil {
176-
return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
150+
return false, err
177151
}
178152

179-
return updatedYAMLBytes, nil
153+
return true, nil
180154
}
181155

182156
// Recursive function to rename the YAML key.
@@ -205,42 +179,27 @@ func renameYamlKey(node *yaml.Node, path []string, newKey string) (bool, error)
205179
}
206180

207181
// Traverses a yaml document, calling the callback function for each node. The
208-
// callback is allowed to modify the node in place, in which case it should
209-
// return true. The function returns the original yaml document if none of the
210-
// callbacks returned true, and the modified document otherwise.
211-
func Walk(yamlBytes []byte, callback func(node *yaml.Node, path string) bool) ([]byte, error) {
212-
// Parse the YAML file.
213-
var node yaml.Node
214-
err := yaml.Unmarshal(yamlBytes, &node)
215-
if err != nil {
216-
return nil, fmt.Errorf("failed to parse YAML: %w", err)
217-
}
218-
182+
// callback is expected to modify the node in place
183+
func Walk(rootNode *yaml.Node, callback func(node *yaml.Node, path string)) error {
219184
// Empty document: nothing to do.
220-
if len(node.Content) == 0 {
221-
return yamlBytes, nil
185+
if len(rootNode.Content) == 0 {
186+
return nil
222187
}
223188

224-
body := node.Content[0]
225-
226-
if didChange, err := walk(body, "", callback); err != nil || !didChange {
227-
return yamlBytes, err
228-
}
189+
body := rootNode.Content[0]
229190

230-
// Convert the updated YAML node back to YAML bytes.
231-
updatedYAMLBytes, err := yamlMarshal(body)
232-
if err != nil {
233-
return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
191+
if err := walk(body, "", callback); err != nil {
192+
return err
234193
}
235194

236-
return updatedYAMLBytes, nil
195+
return nil
237196
}
238197

239-
func walk(node *yaml.Node, path string, callback func(*yaml.Node, string) bool) (bool, error) {
240-
didChange := callback(node, path)
198+
func walk(node *yaml.Node, path string, callback func(*yaml.Node, string)) error {
199+
callback(node, path)
241200
switch node.Kind {
242201
case yaml.DocumentNode:
243-
return false, errors.New("Unexpected document node in the middle of a yaml tree")
202+
return errors.New("Unexpected document node in the middle of a yaml tree")
244203
case yaml.MappingNode:
245204
for i := 0; i < len(node.Content); i += 2 {
246205
name := node.Content[i].Value
@@ -251,31 +210,29 @@ func walk(node *yaml.Node, path string, callback func(*yaml.Node, string) bool)
251210
} else {
252211
childPath = fmt.Sprintf("%s.%s", path, name)
253212
}
254-
didChangeChild, err := walk(childNode, childPath, callback)
213+
err := walk(childNode, childPath, callback)
255214
if err != nil {
256-
return false, err
215+
return err
257216
}
258-
didChange = didChange || didChangeChild
259217
}
260218
case yaml.SequenceNode:
261219
for i := 0; i < len(node.Content); i++ {
262220
childPath := fmt.Sprintf("%s[%d]", path, i)
263-
didChangeChild, err := walk(node.Content[i], childPath, callback)
221+
err := walk(node.Content[i], childPath, callback)
264222
if err != nil {
265-
return false, err
223+
return err
266224
}
267-
didChange = didChange || didChangeChild
268225
}
269226
case yaml.ScalarNode:
270227
// nothing to do
271228
case yaml.AliasNode:
272-
return false, errors.New("Alias nodes are not supported")
229+
return errors.New("Alias nodes are not supported")
273230
}
274231

275-
return didChange, nil
232+
return nil
276233
}
277234

278-
func yamlMarshal(node *yaml.Node) ([]byte, error) {
235+
func YamlMarshal(node *yaml.Node) ([]byte, error) {
279236
var buffer bytes.Buffer
280237
encoder := yaml.NewEncoder(&buffer)
281238
encoder.SetIndent(2)

0 commit comments

Comments
 (0)