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 }