Created
October 30, 2020 13:56
-
-
Save juliankoehn/06b809b81e2327aa5fe4b14d36034bdc to your computer and use it in GitHub Desktop.
Medium GoCademy Tutorial: Packages
This file contains hidden or 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 characters
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 | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment