Created
October 30, 2020 13:56
Revisions
-
juliankoehn created this gist
Oct 30, 2020 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,205 @@ package env import ( "bufio" "errors" "fmt" "io" "os" "regexp" "strings" ) func init() { fmt.Println("trying to load environemtn from .env") loadFile(".env", false) } func loadFile(filename string, overload bool) error { envMap, err := readFile(filename) if err != nil { return err } currentEnv := map[string]bool{} rawEnv := os.Environ() for _, rawEnvLine := range rawEnv { key := strings.Split(rawEnvLine, "=")[0] currentEnv[key] = true } for key, value := range envMap { if !currentEnv[key] || overload { os.Setenv(key, value) } } return nil } func readFile(filename string) (envMap map[string]string, err error) { file, err := os.Open(filename) if err != nil { return } defer file.Close() return Parse(file) } // Parse reads an env file from io.Reader, returning a map of keys and values. func Parse(r io.Reader) (envMap map[string]string, err error) { envMap = make(map[string]string) var lines []string scanner := bufio.NewScanner(r) for scanner.Scan() { lines = append(lines, scanner.Text()) } if err = scanner.Err(); err != nil { return } for _, fullLine := range lines { if !isIgnoredLine(fullLine) { var key, value string key, value, err = parseLine(fullLine, envMap) if err != nil { return } envMap[key] = value } } return } func isIgnoredLine(line string) bool { trimmedLine := strings.TrimSpace(line) return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") } var exportRegex = regexp.MustCompile(`^\s*(?:export\s+)?(.*?)\s*$`) func parseLine(line string, envMap map[string]string) (key string, value string, err error) { if len(line) == 0 { err = errors.New("zero length string") return } // ditch the comments (but keep quoted hashes) if strings.Contains(line, "#") { segmentsBetweenHashes := strings.Split(line, "#") quotesAreOpen := false var segmentsToKeep []string for _, segment := range segmentsBetweenHashes { if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { if quotesAreOpen { quotesAreOpen = false segmentsToKeep = append(segmentsToKeep, segment) } else { quotesAreOpen = true } } if len(segmentsToKeep) == 0 || quotesAreOpen { segmentsToKeep = append(segmentsToKeep, segment) } } line = strings.Join(segmentsToKeep, "#") } firstEquals := strings.Index(line, "=") firstColon := strings.Index(line, ":") splitString := strings.SplitN(line, "=", 2) if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) { //this is a yaml-style line splitString = strings.SplitN(line, ":", 2) } if len(splitString) != 2 { err = errors.New("Can't separate key from value") return } // Parse the key key = splitString[0] if strings.HasPrefix(key, "export") { key = strings.TrimPrefix(key, "export") } key = strings.TrimSpace(key) key = exportRegex.ReplaceAllString(splitString[0], "$1") // Parse the value value = parseValue(splitString[1], envMap) return } var ( singleQuotesRegex = regexp.MustCompile(`\A'(.*)'\z`) doubleQuotesRegex = regexp.MustCompile(`\A"(.*)"\z`) escapeRegex = regexp.MustCompile(`\\.`) unescapeCharsRegex = regexp.MustCompile(`\\([^$])`) ) func parseValue(value string, envMap map[string]string) string { // trim value = strings.Trim(value, " ") // check if we've got quoted values or possible escapes if len(value) > 1 { singleQuotes := singleQuotesRegex.FindStringSubmatch(value) doubleQuotes := doubleQuotesRegex.FindStringSubmatch(value) if singleQuotes != nil || doubleQuotes != nil { // pull the quotes off the edges value = value[1 : len(value)-1] } if doubleQuotes != nil { // expand newlines value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string { c := strings.TrimPrefix(match, `\`) switch c { case "n": return "\n" case "r": return "\r" default: return match } }) // unescape characters value = unescapeCharsRegex.ReplaceAllString(value, "$1") } if singleQuotes == nil { value = expandVariables(value, envMap) } } return value } var expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) func expandVariables(v string, m map[string]string) string { return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string { submatch := expandVarRegex.FindStringSubmatch(s) if submatch == nil { return s } if submatch[1] == "\\" || submatch[2] == "(" { return submatch[0][1:] } else if submatch[4] != "" { return m[submatch[4]] } return s }) }