github.com/theliebeskind/genfig@v0.1.5-alpha/parsers/dotenv.go (about)

     1  package parsers
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  
    11  	mergo "github.com/imdario/mergo"
    12  	"github.com/theliebeskind/genfig/util"
    13  )
    14  
    15  var (
    16  	allowedKVSeparators  = []string{"=", ":"}
    17  	allowedEnvSeparators = []string{"_", "-"}
    18  	keyRegex             = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]*$`)
    19  )
    20  
    21  // DotenvStrategy parses .env files.
    22  // Emply lines are ignored.
    23  // Allowed key-value separators are only "=" and ":".
    24  // Supported values are: string, int64, float64, bool and json arrays,
    25  // e.g. `["a", "b", "c"]` ([]string) or `["a", 1, true]` ([]interface {}).
    26  // Key can be nests by eithe one of the allowed env separators, e.g. `DB_NAME` or `SERVER-HOST`
    27  // Comments are only allowed in seperate lines.
    28  type DotenvStrategy struct {
    29  }
    30  
    31  // Parse of DotenvStrategy parses yaml and json files into Parsing result
    32  func (s *DotenvStrategy) Parse(data []byte) (map[string]interface{}, error) {
    33  	if len(data) == 0 {
    34  		return nil, errors.New("Empty data")
    35  	}
    36  
    37  	r := map[string]interface{}{}
    38  
    39  	scanner := bufio.NewScanner(bytes.NewBuffer(data))
    40  
    41  	for scanner.Scan() {
    42  		line := strings.TrimSpace(scanner.Text())
    43  		// ignore empty or comment lines
    44  		if line == "" || strings.HasPrefix(line, "#") {
    45  			continue
    46  		}
    47  		// try to split line into key and value by allowed separators
    48  		var kv []string
    49  		for i, sep := range allowedKVSeparators {
    50  			if kv = strings.SplitN(line, sep, 2); len(kv) == 2 {
    51  				break
    52  			}
    53  			if i == len(allowedKVSeparators)-1 {
    54  				return nil, fmt.Errorf("Invalid dotenv line: '%s'", line)
    55  			}
    56  		}
    57  
    58  		// trim key and value
    59  		k := strings.TrimSpace(kv[0])
    60  		v := strings.TrimSpace(kv[1])
    61  
    62  		var keys []string
    63  		for _, sep := range allowedEnvSeparators {
    64  			if keys = strings.Split(strings.ToLower(k), sep); len(keys) > 1 {
    65  				break
    66  			}
    67  		}
    68  
    69  		var item interface{} = r
    70  		for i, key := range keys {
    71  			if !keyRegex.MatchString(key) {
    72  				return nil, fmt.Errorf("Key '%s' is not valid", key)
    73  			}
    74  			if item, found := item.(map[string]interface{})[key]; found {
    75  				switch item.(type) {
    76  				case map[string]interface{}:
    77  					if len(keys) == i+1 {
    78  						return nil, fmt.Errorf("Key '%s' is already present with differnt type (old: map, new: basic)", keys)
    79  					}
    80  				default:
    81  					return nil, fmt.Errorf("Key '%s' is already present with different type (old: basic, new: map)", keys)
    82  				}
    83  			}
    84  		}
    85  
    86  		util.ReverseStrings(keys)
    87  		tmp := map[string]interface{}{}
    88  
    89  		for i, k := range keys {
    90  			if i == 0 {
    91  				tmp[k] = util.ParseString(v)
    92  				continue
    93  			}
    94  			tmp = map[string]interface{}{k: tmp}
    95  		}
    96  
    97  		mergo.Map(&r, tmp)
    98  	}
    99  
   100  	if err := scanner.Err(); err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	return r, nil
   105  }