@@ -54,8 +54,18 @@ func (y *ValuesFile) SetValue(key string, value string) error {
5454 doc := y .file .Docs [0 ]
5555 root := doc .Body
5656
57+ // Handle comment-only YAML or empty documents by initializing an empty mapping
5758 if root == nil {
58- return fmt .Errorf ("empty document body" )
59+ root = makeEmptyMapping (0 )
60+ doc .Body = root
61+ } else if commentGroup , isCommentOnly := root .(* ast.CommentGroupNode ); isCommentOnly {
62+ // Comment-only YAML - create empty mapping and preserve comments
63+ newRoot := makeEmptyMapping (0 )
64+ if err := newRoot .SetComment (commentGroup ); err != nil {
65+ return fmt .Errorf ("failed to preserve comments: %w" , err )
66+ }
67+ doc .Body = newRoot
68+ root = newRoot
5969 }
6070
6171 rootMapping , ok := root .(* ast.MappingNode )
@@ -91,18 +101,14 @@ func (y *ValuesFile) GetValue(key string) (string, error) {
91101 return "" , fmt .Errorf ("root is not a mapping node" )
92102 }
93103
94- // First try nested path
95- keys := strings .Split (key , "." )
96- if val , err := getNestedValue (rootMapping , keys ); err == nil {
97- return val , nil
98- }
99-
100- // Try as literal key
104+ // First, check if the full key exists as a literal key
101105 if node := findKeyInMapping (rootMapping , key ); node != nil {
102106 return getScalarValue (node )
103107 }
104108
105- return "" , fmt .Errorf ("key %q not found" , key )
109+ // Try navigating as a nested path
110+ keys := strings .Split (key , "." )
111+ return getNestedValue (rootMapping , keys )
106112}
107113
108114// findKeyInMapping finds a key in a mapping node and returns its value node.
@@ -115,51 +121,80 @@ func findKeyInMapping(mapping *ast.MappingNode, key string) ast.Node {
115121 return nil
116122}
117123
118- // setNodeValue sets the value of a scalar node, preserving comments.
119- func setNodeValue (node ast.Node , value string ) error {
120- // Unwrap anchor/alias nodes
121- node = unwrapNode (node )
124+ // updateNodeToken updates the token value for a node and returns its comment.
125+ func updateNodeToken (node ast.Node , value string ) (* ast.CommentGroupNode , error ) {
126+ type tokenNode interface {
127+ GetComment () * ast.CommentGroupNode
128+ GetToken () * token.Token
129+ }
122130
123- stringNode , ok := node .(* ast. StringNode )
131+ tn , ok := node .(tokenNode )
124132 if ! ok {
125- return fmt .Errorf ("cannot set value on non-scalar node (type: %T)" , node )
133+ return nil , fmt .Errorf ("cannot set value on non-scalar node (type: %T)" , node )
126134 }
127135
128- comment := stringNode .GetComment ()
129- stringNode .Value = value
130- if comment != nil {
131- if err := stringNode .SetComment (comment ); err != nil {
132- return fmt .Errorf ("failed to preserve comment: %w" , err )
136+ // Validate value based on node type
137+ switch n := node .(type ) {
138+ case * ast.IntegerNode :
139+ if _ , err := strconv .ParseInt (value , 10 , 64 ); err != nil {
140+ return nil , fmt .Errorf ("cannot set integer value to %q: %w" , value , err )
141+ }
142+ case * ast.FloatNode :
143+ if _ , err := strconv .ParseFloat (value , 64 ); err != nil {
144+ return nil , fmt .Errorf ("cannot set float value to %q: %w" , value , err )
145+ }
146+ case * ast.BoolNode :
147+ if _ , err := strconv .ParseBool (value ); err != nil {
148+ return nil , fmt .Errorf ("cannot set bool value to %q: %w" , value , err )
133149 }
150+ case * ast.StringNode :
151+ // String node - update the value field
152+ n .Value = value
134153 }
135154
136- return nil
155+ // Update token for serialization
156+ if tok := tn .GetToken (); tok != nil {
157+ tok .Value = value
158+ }
159+
160+ return tn .GetComment (), nil
137161}
138162
139- // getScalarValue extracts the string value from a node.
140- func getScalarValue (node ast.Node ) (string , error ) {
141- // Handle alias nodes
142- if aliasNode , ok := node .(* ast.AliasNode ); ok {
143- return getScalarValue (aliasNode .Value )
144- }
163+ // setNodeValue sets the value of a scalar node, preserving comments and type.
164+ func setNodeValue (node ast.Node , value string ) error {
165+ node = unwrapNode (node )
145166
146- if stringNode , ok := node .(* ast.StringNode ); ok {
147- return stringNode .Value , nil
167+ comment , err := updateNodeToken (node , value )
168+ if err != nil {
169+ return err
148170 }
149171
150- if intNode , ok := node .(* ast.IntegerNode ); ok {
151- return intNode .GetToken ().Value , nil
172+ // Restore comment
173+ if comment != nil {
174+ if err := node .SetComment (comment ); err != nil {
175+ return fmt .Errorf ("failed to preserve comment: %w" , err )
176+ }
152177 }
153178
154- if floatNode , ok := node .(* ast.FloatNode ); ok {
155- return floatNode .GetToken ().Value , nil
156- }
179+ return nil
180+ }
157181
158- if boolNode , ok := node .(* ast.BoolNode ); ok {
159- return boolNode .GetToken ().Value , nil
182+ // getScalarValue extracts the string value from a node.
183+ func getScalarValue (node ast.Node ) (string , error ) {
184+ switch n := node .(type ) {
185+ case * ast.AliasNode :
186+ return getScalarValue (n .Value )
187+ case * ast.StringNode :
188+ return n .Value , nil
189+ case * ast.IntegerNode :
190+ return n .GetToken ().Value , nil
191+ case * ast.FloatNode :
192+ return n .GetToken ().Value , nil
193+ case * ast.BoolNode :
194+ return n .GetToken ().Value , nil
195+ default :
196+ return "" , fmt .Errorf ("node is not a scalar value (type: %T)" , node )
160197 }
161-
162- return "" , fmt .Errorf ("node is not a scalar value (type: %T)" , node )
163198}
164199
165200// unwrapNode unwraps AnchorNode and AliasNode to get the actual value node.
@@ -179,29 +214,32 @@ func unwrapNode(node ast.Node) ast.Node {
179214// setNestedValue sets a value at a nested path, creating nodes as needed.
180215func setNestedValue (mapping * ast.MappingNode , keys []string , value string ) error {
181216 current := mapping
217+ // Infer indent step once from the root mapping
218+ indentStep := getIndentStep (mapping )
182219
183220 for i , k := range keys {
184221 keyPart , arrayIdx := parseArrayIndex (k )
185222
186223 var valueNode ast.Node
187- var mappingValueNode * ast.MappingValueNode
188-
189224 for _ , item := range current .Values {
190225 if keyNode , ok := item .Key .(* ast.StringNode ); ok && keyNode .Value == keyPart {
191226 valueNode = item .Value
192- mappingValueNode = item
193227 break
194228 }
195229 }
196230
197231 // If key not found, create it
198232 if valueNode == nil {
233+ // Cannot create missing array indices
234+ if arrayIdx != nil {
235+ return fmt .Errorf ("cannot set value: key %q does not exist (array indices must reference existing sequences)" , keyPart )
236+ }
199237 if i == len (keys )- 1 {
200238 // Last key - create scalar value
201239 return addKeyValue (current , k , value )
202240 }
203- // Create intermediate mapping
204- newMapping , err := addKeyMapping (current , keyPart )
241+ // Create intermediate mapping with the inferred indent step
242+ newMapping , err := addKeyMapping (current , keyPart , indentStep )
205243 if err != nil {
206244 return err
207245 }
@@ -225,9 +263,6 @@ func setNestedValue(mapping *ast.MappingNode, keys []string, value string) error
225263 }
226264
227265 if i == len (keys )- 1 {
228- if mappingValueNode != nil && arrayIdx == nil {
229- return setNodeValue (unwrapNode (mappingValueNode .Value ), value )
230- }
231266 return setNodeValue (valueNode , value )
232267 }
233268
@@ -318,137 +353,88 @@ func getMappingIndent(mapping *ast.MappingNode) int {
318353 return 0
319354}
320355
321- // addKeyValue adds a new key-value pair to a mapping.
322- func addKeyValue (mapping * ast.MappingNode , key string , value string ) error {
323- indent := getMappingIndent (mapping )
324-
325- keyTok := & token.Token {
326- Type : token .StringType ,
327- Value : key ,
328- Origin : key ,
356+ // makeToken creates a token with position information.
357+ // Column is 1-indexed, so indent 4 means column 5.
358+ func makeToken (tokenType token.Type , value string , indent int ) * token.Token {
359+ return & token.Token {
360+ Type : tokenType ,
361+ Value : value ,
362+ Origin : value ,
329363 Position : & token.Position {
330364 IndentNum : indent ,
331365 IndentLevel : 0 ,
332366 Column : indent + 1 ,
333367 },
334368 }
369+ }
335370
336- colonTok := & token. Token {
337- Type : token . MappingValueType ,
338- Value : ":" ,
339- Origin : ":" ,
371+ // makeEmptyMapping creates an empty mapping node with the given indent.
372+ func makeEmptyMapping ( indent int ) * ast. MappingNode {
373+ startTok := & token. Token {
374+ Type : token . MappingStartType ,
340375 Position : & token.Position {
341376 IndentNum : indent ,
342377 IndentLevel : 0 ,
343- Column : indent + len ( key ) + 1 ,
378+ Column : indent + 1 ,
344379 },
345380 }
381+ return ast .Mapping (startTok , false )
382+ }
346383
347- valTok := & token.Token {
348- Type : token .StringType ,
349- Value : value ,
350- Origin : value ,
351- Position : & token.Position {
352- IndentNum : indent ,
353- IndentLevel : 0 ,
354- Column : indent + len (key ) + 3 ,
355- },
384+ // getIndentStep infers the indent step size from the document.
385+ // Returns the number of spaces to add when creating a child level.
386+ func getIndentStep (mapping * ast.MappingNode ) int {
387+ // Try to infer from a nested mapping value
388+ for _ , item := range mapping .Values {
389+ if childMapping , ok := unwrapNode (item .Value ).(* ast.MappingNode ); ok {
390+ if len (childMapping .Values ) > 0 {
391+ parentIndent := item .Key .GetToken ().Position .IndentNum
392+ childIndent := childMapping .Values [0 ].Key .GetToken ().Position .IndentNum
393+ if childIndent > parentIndent {
394+ return childIndent - parentIndent
395+ }
396+ }
397+ }
356398 }
399+ // Default to 2 spaces if we can't infer
400+ return 2
401+ }
357402
358- keyNode := & ast.StringNode {
359- BaseNode : & ast.BaseNode {},
360- Token : keyTok ,
361- Value : key ,
362- }
403+ // addKeyValue adds a new key-value pair to a mapping.
404+ func addKeyValue (mapping * ast.MappingNode , key string , value string ) error {
405+ indent := getMappingIndent (mapping )
363406
364- valNode := & ast.StringNode {
365- BaseNode : & ast.BaseNode {},
366- Token : valTok ,
367- Value : value ,
368- }
407+ keyTok := makeToken (token .StringType , key , indent )
408+ colonTok := makeToken (token .MappingValueType , ":" , indent )
409+ valTok := makeToken (token .StringType , value , indent )
369410
370- newValue := & ast.MappingValueNode {
371- BaseNode : & ast.BaseNode {},
372- Start : colonTok ,
373- Key : keyNode ,
374- Value : valNode ,
375- }
411+ keyNode := ast .String (keyTok )
412+ valNode := ast .String (valTok )
413+ newValue := ast .MappingValue (colonTok , keyNode , valNode )
376414
377415 mapping .Values = append (mapping .Values , newValue )
378416 return nil
379417}
380418
381419// addKeyMapping adds a new key with an empty mapping value.
382- func addKeyMapping (mapping * ast.MappingNode , key string ) (* ast.MappingNode , error ) {
420+ func addKeyMapping (mapping * ast.MappingNode , key string , indentStep int ) (* ast.MappingNode , error ) {
383421 indent := getMappingIndent (mapping )
422+ childIndent := indent + indentStep
384423
385- // Calculate child indent (2 more than current)
386- childIndent := indent + 2
387-
388- keyTok := & token.Token {
389- Type : token .StringType ,
390- Value : key ,
391- Origin : key ,
392- Position : & token.Position {
393- IndentNum : indent ,
394- IndentLevel : 0 ,
395- Column : indent + 1 ,
396- },
397- }
398-
399- colonTok := & token.Token {
400- Type : token .MappingValueType ,
401- Value : ":" ,
402- Origin : ":" ,
403- Position : & token.Position {
404- IndentNum : indent ,
405- IndentLevel : 0 ,
406- Column : indent + len (key ) + 1 ,
407- },
408- }
409-
410- keyNode := & ast.StringNode {
411- BaseNode : & ast.BaseNode {},
412- Token : keyTok ,
413- Value : key ,
414- }
424+ keyTok := makeToken (token .StringType , key , indent )
425+ colonTok := makeToken (token .MappingValueType , ":" , indent )
415426
416- newMapping := & ast.MappingNode {
417- BaseNode : & ast.BaseNode {},
418- Values : []* ast.MappingValueNode {},
419- Start : & token.Token {
420- Type : token .MappingStartType ,
421- Position : & token.Position {
422- IndentNum : childIndent ,
423- IndentLevel : 0 ,
424- },
425- },
426- }
427-
428- newValue := & ast.MappingValueNode {
429- BaseNode : & ast.BaseNode {},
430- Start : colonTok ,
431- Key : keyNode ,
432- Value : newMapping ,
433- }
427+ keyNode := ast .String (keyTok )
428+ newMapping := makeEmptyMapping (childIndent )
429+ newValue := ast .MappingValue (colonTok , keyNode , newMapping )
434430
435431 mapping .Values = append (mapping .Values , newValue )
436432 return newMapping , nil
437433}
438434
439435// CreateEmptyValuesFile creates an empty values file with an optional header comment.
440436func CreateEmptyValuesFile (headerComment string ) * ValuesFile {
441- mapping := & ast.MappingNode {
442- BaseNode : & ast.BaseNode {},
443- Values : []* ast.MappingValueNode {},
444- Start : & token.Token {
445- Type : token .MappingStartType ,
446- Position : & token.Position {
447- IndentNum : 0 ,
448- IndentLevel : 0 ,
449- },
450- },
451- }
437+ mapping := makeEmptyMapping (0 )
452438
453439 doc := & ast.DocumentNode {
454440 BaseNode : & ast.BaseNode {},
0 commit comments