@@ -69,6 +69,27 @@ func (te *tomlEncoder) CanHandleAliases() bool {
6969
7070// ---- helpers ----
7171
72+ // tomlKey returns the key quoted if it contains characters that are not valid
73+ // in a TOML bare key. TOML bare keys may only contain ASCII letters, ASCII
74+ // digits, underscores, and dashes.
75+ func tomlKey (key string ) string {
76+ for _ , r := range key {
77+ if ! ((r >= 'A' && r <= 'Z' ) || (r >= 'a' && r <= 'z' ) || (r >= '0' && r <= '9' ) || r == '_' || r == '-' ) {
78+ return fmt .Sprintf ("%q" , key )
79+ }
80+ }
81+ return key
82+ }
83+
84+ // tomlDottedKey joins path components, quoting any that require it.
85+ func tomlDottedKey (path []string ) string {
86+ parts := make ([]string , len (path ))
87+ for i , p := range path {
88+ parts [i ] = tomlKey (p )
89+ }
90+ return strings .Join (parts , "." )
91+ }
92+
7293func (te * tomlEncoder ) writeComment (w io.Writer , comment string ) error {
7394 if comment == "" {
7495 return nil
@@ -148,9 +169,10 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
148169 }
149170 if allMaps {
150171 key := path [len (path )- 1 ]
172+ quotedKey := tomlKey (key )
151173 for _ , it := range node .Content {
152174 // [[key]] then body
153- if _ , err := w .Write ([]byte ("[[" + key + "]]\n " )); err != nil {
175+ if _ , err := w .Write ([]byte ("[[" + quotedKey + "]]\n " )); err != nil {
154176 return err
155177 }
156178 if err := te .encodeMappingBodyWithPath (w , []string {key }, it ); err != nil {
@@ -185,7 +207,7 @@ func (te *tomlEncoder) writeAttribute(w io.Writer, key string, value *CandidateN
185207 }
186208
187209 // Write the attribute
188- line := key + " = " + te .formatScalar (value )
210+ line := tomlKey ( key ) + " = " + te .formatScalar (value )
189211
190212 // Add line comment if present
191213 if value .LineComment != "" {
@@ -210,7 +232,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
210232
211233 // Handle empty arrays
212234 if len (seq .Content ) == 0 {
213- line := key + " = []"
235+ line := tomlKey ( key ) + " = []"
214236 if seq .LineComment != "" {
215237 lineComment := strings .TrimSpace (seq .LineComment )
216238 if ! strings .HasPrefix (lineComment , "#" ) {
@@ -233,7 +255,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
233255
234256 if hasElementComments {
235257 // Write multiline array format with comments
236- if _ , err := w .Write ([]byte (key + " = [\n " )); err != nil {
258+ if _ , err := w .Write ([]byte (tomlKey ( key ) + " = [\n " )); err != nil {
237259 return err
238260 }
239261
@@ -324,7 +346,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
324346 }
325347 }
326348
327- line := key + " = [" + strings .Join (items , ", " ) + "]"
349+ line := tomlKey ( key ) + " = [" + strings .Join (items , ", " ) + "]"
328350
329351 // Add line comment if present
330352 if seq .LineComment != "" {
@@ -372,21 +394,21 @@ func (te *tomlEncoder) mappingToInlineTable(m *CandidateNode) (string, error) {
372394 v := m .Content [i + 1 ]
373395 switch v .Kind {
374396 case ScalarNode :
375- parts = append (parts , fmt .Sprintf ("%s = %s" , k , te .formatScalar (v )))
397+ parts = append (parts , fmt .Sprintf ("%s = %s" , tomlKey ( k ) , te .formatScalar (v )))
376398 case SequenceNode :
377399 // inline array in inline table
378400 arr , err := te .sequenceToInlineArray (v )
379401 if err != nil {
380402 return "" , err
381403 }
382- parts = append (parts , fmt .Sprintf ("%s = %s" , k , arr ))
404+ parts = append (parts , fmt .Sprintf ("%s = %s" , tomlKey ( k ) , arr ))
383405 case MappingNode :
384406 // nested inline table
385407 inline , err := te .mappingToInlineTable (v )
386408 if err != nil {
387409 return "" , err
388410 }
389- parts = append (parts , fmt .Sprintf ("%s = %s" , k , inline ))
411+ parts = append (parts , fmt .Sprintf ("%s = %s" , tomlKey ( k ) , inline ))
390412 default :
391413 return "" , fmt .Errorf ("unsupported inline table value kind: %v" , v .Kind )
392414 }
@@ -399,7 +421,7 @@ func (te *tomlEncoder) writeInlineTableAttribute(w io.Writer, key string, m *Can
399421 if err != nil {
400422 return err
401423 }
402- _ , err = w .Write ([]byte (key + " = " + inline + "\n " ))
424+ _ , err = w .Write ([]byte (tomlKey ( key ) + " = " + inline + "\n " ))
403425 return err
404426}
405427
@@ -421,7 +443,7 @@ func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *Candidate
421443 }
422444
423445 // Write table header [a.b.c]
424- header := "[" + strings . Join (path , "." ) + "]\n "
446+ header := "[" + tomlDottedKey (path ) + "]\n "
425447 _ , err := w .Write ([]byte (header ))
426448 return err
427449}
@@ -488,7 +510,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
488510 }
489511 }
490512 if allMaps {
491- key := strings . Join (append (append ([]string {}, path ... ), k ), "." )
513+ key := tomlDottedKey (append (append ([]string {}, path ... ), k ))
492514 for _ , it := range v .Content {
493515 if _ , err := w .Write ([]byte ("[[" + key + "]]\n " )); err != nil {
494516 return err
@@ -586,7 +608,7 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
586608 }
587609 }
588610 if allMaps {
589- dotted := strings . Join (append (append ([]string {}, path ... ), k ), "." )
611+ dotted := tomlDottedKey (append (append ([]string {}, path ... ), k ))
590612 for _ , it := range v .Content {
591613 if _ , err := w .Write ([]byte ("[[" + dotted + "]]\n " )); err != nil {
592614 return err
0 commit comments