66 "io"
77 "strconv"
88 "strings"
9+ "time"
910
1011 "emperror.dev/errors"
1112 "github.com/apex/log"
@@ -14,6 +15,7 @@ import (
1415 "github.com/goccy/go-json"
1516 "github.com/icza/dyno"
1617 "github.com/magiconair/properties"
18+ "github.com/pelletier/go-toml/v2"
1719 "github.com/tidwall/pretty"
1820 "gopkg.in/ini.v1"
1921 "gopkg.in/yaml.v3"
@@ -30,6 +32,7 @@ const (
3032 Ini = "ini"
3133 Json = "json"
3234 Xml = "xml"
35+ Toml = "toml"
3336)
3437
3538type ReplaceValue struct {
@@ -228,6 +231,8 @@ func (f *ConfigurationFile) Parse(file ufs.File) error {
228231 err = f .parseIniFile (file )
229232 case Xml :
230233 err = f .parseXmlFile (file )
234+ case Toml :
235+ err = f .parseTomlFile (file )
231236 }
232237 return err
233238}
@@ -480,6 +485,90 @@ func (f *ConfigurationFile) parseYamlFile(file ufs.File) error {
480485 return nil
481486}
482487
488+ // Parses a toml file and updates any matching key/value pairs before persisting
489+ // it back to the disk.
490+ func (f * ConfigurationFile ) parseTomlFile (file ufs.File ) error {
491+ b , err := io .ReadAll (file )
492+ if err != nil {
493+ return err
494+ }
495+
496+ i := make (map [string ]interface {})
497+ if err := toml .Unmarshal (b , & i ); err != nil {
498+ return err
499+ }
500+
501+ // Convert to JSON to reuse IterateOverJson for value replacement.
502+ jsonBytes , err := json .Marshal (dyno .ConvertMapI2MapS (i ))
503+ if err != nil {
504+ return err
505+ }
506+
507+ data , err := f .IterateOverJson (jsonBytes )
508+ if err != nil {
509+ return err
510+ }
511+
512+ var jsonData interface {}
513+ decoder := json .NewDecoder (bytes .NewReader (data ))
514+ decoder .UseNumber ()
515+ if err := decoder .Decode (& jsonData ); err != nil {
516+ return err
517+ }
518+ jsonData = normalizeTomlTypes (jsonData )
519+
520+ // Remarshal back to TOML format.
521+ marshaled , err := toml .Marshal (jsonData )
522+ if err != nil {
523+ return err
524+ }
525+
526+ if _ , err := file .Seek (0 , io .SeekStart ); err != nil {
527+ return err
528+ }
529+ if err := file .Truncate (0 ); err != nil {
530+ return err
531+ }
532+
533+ if _ , err := io .Copy (file , bytes .NewReader (marshaled )); err != nil {
534+ return errors .Wrap (err , "parser: failed to write toml file to disk" )
535+ }
536+ return nil
537+ }
538+
539+ func normalizeTomlTypes (value interface {}) interface {} {
540+ switch typed := value .(type ) {
541+ case map [string ]interface {}:
542+ for key , item := range typed {
543+ typed [key ] = normalizeTomlTypes (item )
544+ }
545+ return typed
546+ case []interface {}:
547+ for i := range typed {
548+ typed [i ] = normalizeTomlTypes (typed [i ])
549+ }
550+ return typed
551+ case json.Number :
552+ if intVal , err := typed .Int64 (); err == nil {
553+ return intVal
554+ }
555+ if floatVal , err := typed .Float64 (); err == nil {
556+ return floatVal
557+ }
558+ return typed .String ()
559+ case string :
560+ if timeVal , err := time .Parse (time .RFC3339Nano , typed ); err == nil {
561+ return timeVal
562+ }
563+ if timeVal , err := time .Parse (time .RFC3339 , typed ); err == nil {
564+ return timeVal
565+ }
566+ return typed
567+ default :
568+ return value
569+ }
570+ }
571+
483572// Parses a text file using basic find and replace. This is a highly inefficient method of
484573// scanning a file and performing a replacement. You should attempt to use anything other
485574// than this function where possible.
0 commit comments