package gluaenv import ( "github.com/yuin/gopher-lua" "os" "bufio" "strings" "errors" ) func Loader(L *lua.LState) int { tb := L.NewTable() L.SetFuncs(tb, map[string]lua.LGFunction{ "set": envSet, "get": envGet, "loadfile": envLoadFile, }) L.Push(tb) return 1 } func envSet(L *lua.LState) int { // same github.com/yuin/gopher-lua/oslib.go err := os.Setenv(L.CheckString(1), L.CheckString(2)) if err != nil { L.Push(lua.LNil) L.Push(lua.LString(err.Error())) return 2 } else { L.Push(lua.LTrue) return 1 } } func envGet(L *lua.LState) int { // same github.com/yuin/gopher-lua/oslib.go v := os.Getenv(L.CheckString(1)) if len(v) == 0 { L.Push(lua.LNil) } else { L.Push(lua.LString(v)) } return 1 } func envLoadFile(L *lua.LState) int { if err := loadFile(L.CheckString(1)); err != nil { L.Push(lua.LNil) L.Push(lua.LString(err.Error())) return 2 } else { L.Push(lua.LTrue) return 1 } } // loadFile' s code is highly inspired by https://github.com/joho/godotenv/blob/master/godotenv.go //Copyright (c) 2013 John Barton // //MIT License // //Permission is hereby granted, free of charge, to any person obtaining //a copy of this software and associated documentation files (the //"Software"), to deal in the Software without restriction, including //without limitation the rights to use, copy, modify, merge, publish, //distribute, sublicense, and/or sell copies of the Software, and to //permit persons to whom the Software is furnished to do so, subject to //the following conditions: // //The above copyright notice and this permission notice shall be //included in all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, //EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF //MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND //NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE //LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION //OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION //WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. func loadFile(filename string) error { envMap, err := readFile(filename) if err != nil { return err } for key, value := range envMap { 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() envMap = make(map[string]string) var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) } for _, fullLine := range lines { if !isIgnoredLine(fullLine) { key, value, err := parseLine(fullLine) if err == nil { envMap[key] = value } } } return } func parseLine(line 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, "#") } // now split key from value splitString := strings.SplitN(line, "=", 2) if len(splitString) != 2 { // try yaml mode! 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.Trim(key, " ") // Parse the value value = splitString[1] // trim value = strings.Trim(value, " ") // check if we've got quoted values if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 { // pull the quotes off the edges value = strings.Trim(value, "\"'") // expand quotes value = strings.Replace(value, "\\\"", "\"", -1) // expand newlines value = strings.Replace(value, "\\n", "\n", -1) } return } func isIgnoredLine(line string) bool { trimmedLine := strings.Trim(line, " \n\t") return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") }