Skip to content

Commit d39f883

Browse files
author
Chris McDonnell
committed
Migrate to only doing marshalling twice, and compare via deep copy
1 parent a01ca19 commit d39f883

File tree

3 files changed

+137
-150
lines changed

3 files changed

+137
-150
lines changed

pkg/config/app_config.go

Lines changed: 35 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,52 @@ 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 remarsal!\n %w", err)
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
286303
})
287304
}
288305

289-
func changeElementToSequence(changedContent []byte, path []string) ([]byte, error) {
290-
return yaml_utils.TransformNode(changedContent, path, func(node *yaml.Node) (bool, error) {
306+
func changeElementToSequence(rootNode *yaml.Node, path []string) error {
307+
return yaml_utils.TransformNode(rootNode, path, func(node *yaml.Node) error {
291308
if node.Kind == yaml.MappingNode {
292309
nodeContentCopy := node.Content
293310
node.Kind = yaml.SequenceNode
@@ -298,15 +315,14 @@ func changeElementToSequence(changedContent []byte, path []string) ([]byte, erro
298315
Content: nodeContentCopy,
299316
}}
300317

301-
return true, nil
318+
return nil
302319
}
303-
return false, nil
320+
return nil
304321
})
305322
}
306323

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
324+
func changeCommitPrefixesMap(rootNode *yaml.Node) error {
325+
return yaml_utils.TransformNode(rootNode, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) error {
310326
if prefixesNode.Kind == yaml.MappingNode {
311327
for _, contentNode := range prefixesNode.Content {
312328
if contentNode.Kind == yaml.MappingNode {
@@ -318,11 +334,10 @@ func changeCommitPrefixesMap(changedContent []byte) ([]byte, error) {
318334
Kind: yaml.MappingNode,
319335
Content: nodeContentCopy,
320336
}}
321-
changedAnyNodes = true
322337
}
323338
}
324339
}
325-
return changedAnyNodes, nil
340+
return nil
326341
})
327342
}
328343

pkg/utils/yaml_utils/yaml_utils.go

Lines changed: 42 additions & 90 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,147 +100,101 @@ 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.
104-
//
105-
// The transform must return true if it made changes to the node.
103+
// Walks a yaml document from the root node to the specified path, and then applies the transformation to that node.
106104
// If the requested path is not defined in the document, no changes are made to the document.
107-
//
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-
105+
func TransformNode(rootNode *yaml.Node, path []string, transform func(node *yaml.Node) error) error {
118106
// Empty document: nothing to do.
119-
if len(node.Content) == 0 {
120-
return yamlBytes, nil
107+
if len(rootNode.Content) == 0 {
108+
return nil
121109
}
122110

123-
body := node.Content[0]
124-
125-
if didTransform, err := transformNode(body, path, transform); err != nil || !didTransform {
126-
return yamlBytes, err
127-
}
111+
body := rootNode.Content[0]
128112

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)
113+
if err := transformNode(body, path, transform); err != nil {
114+
return err
133115
}
134116

135-
return updatedYAMLBytes, nil
117+
return nil
136118
}
137119

138120
// 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) {
121+
func transformNode(node *yaml.Node, path []string, transform func(node *yaml.Node) error) error {
140122
if len(path) == 0 {
141123
return transform(node)
142124
}
143125

144126
keyNode, valueNode := lookupKey(node, path[0])
145127
if keyNode == nil {
146-
return false, nil
128+
return nil
147129
}
148130

149131
return transformNode(valueNode, path[1:], transform)
150132
}
151133

152-
// takes a yaml document in bytes, a path to a key, and a new name for the key.
134+
// Takes the root node of a yaml document, a path to a key, and a new name for the key.
153135
// 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-
136+
func RenameYamlKey(rootNode *yaml.Node, path []string, newKey string) error {
162137
// Empty document: nothing to do.
163-
if len(node.Content) == 0 {
164-
return yamlBytes, nil
138+
if len(rootNode.Content) == 0 {
139+
return nil
165140
}
166141

167-
body := node.Content[0]
142+
body := rootNode.Content[0]
168143

169-
if didRename, err := renameYamlKey(body, path, newKey); err != nil || !didRename {
170-
return yamlBytes, err
144+
if err := renameYamlKey(body, path, newKey); err != nil {
145+
return err
171146
}
172147

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)
177-
}
178-
179-
return updatedYAMLBytes, nil
148+
return nil
180149
}
181150

182151
// Recursive function to rename the YAML key.
183-
func renameYamlKey(node *yaml.Node, path []string, newKey string) (bool, error) {
152+
func renameYamlKey(node *yaml.Node, path []string, newKey string) error {
184153
if node.Kind != yaml.MappingNode {
185-
return false, errors.New("yaml node in path is not a dictionary")
154+
return errors.New("yaml node in path is not a dictionary")
186155
}
187156

188157
keyNode, valueNode := lookupKey(node, path[0])
189158
if keyNode == nil {
190-
return false, nil
159+
return nil
191160
}
192161

193162
// end of path reached: rename key
194163
if len(path) == 1 {
195164
// Check that new key doesn't exist yet
196165
if newKeyNode, _ := lookupKey(node, newKey); newKeyNode != nil {
197-
return false, fmt.Errorf("new key `%s' already exists", newKey)
166+
return fmt.Errorf("new key `%s' already exists", newKey)
198167
}
199168

200169
keyNode.Value = newKey
201-
return true, nil
170+
return nil
202171
}
203172

204173
return renameYamlKey(valueNode, path[1:], newKey)
205174
}
206175

207176
// 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-
177+
// callback is expected to modify the node in place
178+
func Walk(rootNode *yaml.Node, callback func(node *yaml.Node, path string)) error {
219179
// Empty document: nothing to do.
220-
if len(node.Content) == 0 {
221-
return yamlBytes, nil
180+
if len(rootNode.Content) == 0 {
181+
return nil
222182
}
223183

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

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)
186+
if err := walk(body, "", callback); err != nil {
187+
return err
234188
}
235189

236-
return updatedYAMLBytes, nil
190+
return nil
237191
}
238192

239-
func walk(node *yaml.Node, path string, callback func(*yaml.Node, string) bool) (bool, error) {
240-
didChange := callback(node, path)
193+
func walk(node *yaml.Node, path string, callback func(*yaml.Node, string)) error {
194+
callback(node, path)
241195
switch node.Kind {
242196
case yaml.DocumentNode:
243-
return false, errors.New("Unexpected document node in the middle of a yaml tree")
197+
return errors.New("Unexpected document node in the middle of a yaml tree")
244198
case yaml.MappingNode:
245199
for i := 0; i < len(node.Content); i += 2 {
246200
name := node.Content[i].Value
@@ -251,31 +205,29 @@ func walk(node *yaml.Node, path string, callback func(*yaml.Node, string) bool)
251205
} else {
252206
childPath = fmt.Sprintf("%s.%s", path, name)
253207
}
254-
didChangeChild, err := walk(childNode, childPath, callback)
208+
err := walk(childNode, childPath, callback)
255209
if err != nil {
256-
return false, err
210+
return err
257211
}
258-
didChange = didChange || didChangeChild
259212
}
260213
case yaml.SequenceNode:
261214
for i := 0; i < len(node.Content); i++ {
262215
childPath := fmt.Sprintf("%s[%d]", path, i)
263-
didChangeChild, err := walk(node.Content[i], childPath, callback)
216+
err := walk(node.Content[i], childPath, callback)
264217
if err != nil {
265-
return false, err
218+
return err
266219
}
267-
didChange = didChange || didChangeChild
268220
}
269221
case yaml.ScalarNode:
270222
// nothing to do
271223
case yaml.AliasNode:
272-
return false, errors.New("Alias nodes are not supported")
224+
return errors.New("Alias nodes are not supported")
273225
}
274226

275-
return didChange, nil
227+
return nil
276228
}
277229

278-
func yamlMarshal(node *yaml.Node) ([]byte, error) {
230+
func YamlMarshal(node *yaml.Node) ([]byte, error) {
279231
var buffer bytes.Buffer
280232
encoder := yaml.NewEncoder(&buffer)
281233
encoder.SetIndent(2)

0 commit comments

Comments
 (0)