codec.go raw

   1  package ini
   2  
   3  import (
   4  	"bytes"
   5  	"sort"
   6  	"strings"
   7  
   8  	"github.com/spf13/cast"
   9  	"gopkg.in/ini.v1"
  10  )
  11  
  12  // LoadOptions contains all customized options used for load data source(s).
  13  // This type is added here for convenience: this way consumers can import a single package called "ini".
  14  type LoadOptions = ini.LoadOptions
  15  
  16  // Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding.
  17  type Codec struct {
  18  	KeyDelimiter string
  19  	LoadOptions  LoadOptions
  20  }
  21  
  22  func (c Codec) Encode(v map[string]any) ([]byte, error) {
  23  	cfg := ini.Empty()
  24  	ini.PrettyFormat = false
  25  
  26  	flattened := map[string]any{}
  27  
  28  	flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
  29  
  30  	keys := make([]string, 0, len(flattened))
  31  
  32  	for key := range flattened {
  33  		keys = append(keys, key)
  34  	}
  35  
  36  	sort.Strings(keys)
  37  
  38  	for _, key := range keys {
  39  		sectionName, keyName := "", key
  40  
  41  		lastSep := strings.LastIndex(key, ".")
  42  		if lastSep != -1 {
  43  			sectionName = key[:(lastSep)]
  44  			keyName = key[(lastSep + 1):]
  45  		}
  46  
  47  		// TODO: is this a good idea?
  48  		if sectionName == "default" {
  49  			sectionName = ""
  50  		}
  51  
  52  		cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key]))
  53  	}
  54  
  55  	var buf bytes.Buffer
  56  
  57  	_, err := cfg.WriteTo(&buf)
  58  	if err != nil {
  59  		return nil, err
  60  	}
  61  
  62  	return buf.Bytes(), nil
  63  }
  64  
  65  func (c Codec) Decode(b []byte, v map[string]any) error {
  66  	cfg := ini.Empty(c.LoadOptions)
  67  
  68  	err := cfg.Append(b)
  69  	if err != nil {
  70  		return err
  71  	}
  72  
  73  	sections := cfg.Sections()
  74  
  75  	for i := 0; i < len(sections); i++ {
  76  		section := sections[i]
  77  		keys := section.Keys()
  78  
  79  		for j := 0; j < len(keys); j++ {
  80  			key := keys[j]
  81  			value := cfg.Section(section.Name()).Key(key.Name()).String()
  82  
  83  			deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter()))
  84  
  85  			// set innermost value
  86  			deepestMap[key.Name()] = value
  87  		}
  88  	}
  89  
  90  	return nil
  91  }
  92  
  93  func (c Codec) keyDelimiter() string {
  94  	if c.KeyDelimiter == "" {
  95  		return "."
  96  	}
  97  
  98  	return c.KeyDelimiter
  99  }
 100