http urls monitor.

godotenv.go 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. // Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
  2. //
  3. // Examples/readme can be found on the github page at https://github.com/joho/godotenv
  4. //
  5. // The TL;DR is that you make a .env file that looks something like
  6. //
  7. // SOME_ENV_VAR=somevalue
  8. //
  9. // and then in your go code you can call
  10. //
  11. // godotenv.Load()
  12. //
  13. // and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
  14. package godotenv
  15. import (
  16. "bufio"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "os"
  21. "os/exec"
  22. "regexp"
  23. "sort"
  24. "strings"
  25. )
  26. const doubleQuoteSpecialChars = "\\\n\r\"!$`"
  27. // Load will read your env file(s) and load them into ENV for this process.
  28. //
  29. // Call this function as close as possible to the start of your program (ideally in main)
  30. //
  31. // If you call Load without any args it will default to loading .env in the current path
  32. //
  33. // You can otherwise tell it which files to load (there can be more than one) like
  34. //
  35. // godotenv.Load("fileone", "filetwo")
  36. //
  37. // It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
  38. func Load(filenames ...string) (err error) {
  39. filenames = filenamesOrDefault(filenames)
  40. for _, filename := range filenames {
  41. err = loadFile(filename, false)
  42. if err != nil {
  43. return // return early on a spazout
  44. }
  45. }
  46. return
  47. }
  48. // Overload will read your env file(s) and load them into ENV for this process.
  49. //
  50. // Call this function as close as possible to the start of your program (ideally in main)
  51. //
  52. // If you call Overload without any args it will default to loading .env in the current path
  53. //
  54. // You can otherwise tell it which files to load (there can be more than one) like
  55. //
  56. // godotenv.Overload("fileone", "filetwo")
  57. //
  58. // It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
  59. func Overload(filenames ...string) (err error) {
  60. filenames = filenamesOrDefault(filenames)
  61. for _, filename := range filenames {
  62. err = loadFile(filename, true)
  63. if err != nil {
  64. return // return early on a spazout
  65. }
  66. }
  67. return
  68. }
  69. // Read all env (with same file loading semantics as Load) but return values as
  70. // a map rather than automatically writing values into env
  71. func Read(filenames ...string) (envMap map[string]string, err error) {
  72. filenames = filenamesOrDefault(filenames)
  73. envMap = make(map[string]string)
  74. for _, filename := range filenames {
  75. individualEnvMap, individualErr := readFile(filename)
  76. if individualErr != nil {
  77. err = individualErr
  78. return // return early on a spazout
  79. }
  80. for key, value := range individualEnvMap {
  81. envMap[key] = value
  82. }
  83. }
  84. return
  85. }
  86. // Parse reads an env file from io.Reader, returning a map of keys and values.
  87. func Parse(r io.Reader) (envMap map[string]string, err error) {
  88. envMap = make(map[string]string)
  89. var lines []string
  90. scanner := bufio.NewScanner(r)
  91. for scanner.Scan() {
  92. lines = append(lines, scanner.Text())
  93. }
  94. if err = scanner.Err(); err != nil {
  95. return
  96. }
  97. for _, fullLine := range lines {
  98. if !isIgnoredLine(fullLine) {
  99. var key, value string
  100. key, value, err = parseLine(fullLine)
  101. if err != nil {
  102. return
  103. }
  104. envMap[key] = value
  105. }
  106. }
  107. return
  108. }
  109. //Unmarshal reads an env file from a string, returning a map of keys and values.
  110. func Unmarshal(str string) (envMap map[string]string, err error) {
  111. return Parse(strings.NewReader(str))
  112. }
  113. // Exec loads env vars from the specified filenames (empty map falls back to default)
  114. // then executes the cmd specified.
  115. //
  116. // Simply hooks up os.Stdin/err/out to the command and calls Run()
  117. //
  118. // If you want more fine grained control over your command it's recommended
  119. // that you use `Load()` or `Read()` and the `os/exec` package yourself.
  120. func Exec(filenames []string, cmd string, cmdArgs []string) error {
  121. Load(filenames...)
  122. command := exec.Command(cmd, cmdArgs...)
  123. command.Stdin = os.Stdin
  124. command.Stdout = os.Stdout
  125. command.Stderr = os.Stderr
  126. return command.Run()
  127. }
  128. // Write serializes the given environment and writes it to a file
  129. func Write(envMap map[string]string, filename string) error {
  130. content, error := Marshal(envMap)
  131. if error != nil {
  132. return error
  133. }
  134. file, error := os.Create(filename)
  135. if error != nil {
  136. return error
  137. }
  138. _, err := file.WriteString(content)
  139. return err
  140. }
  141. // Marshal outputs the given environment as a dotenv-formatted environment file.
  142. // Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
  143. func Marshal(envMap map[string]string) (string, error) {
  144. lines := make([]string, 0, len(envMap))
  145. for k, v := range envMap {
  146. lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
  147. }
  148. sort.Strings(lines)
  149. return strings.Join(lines, "\n"), nil
  150. }
  151. func filenamesOrDefault(filenames []string) []string {
  152. if len(filenames) == 0 {
  153. return []string{".env"}
  154. }
  155. return filenames
  156. }
  157. func loadFile(filename string, overload bool) error {
  158. envMap, err := readFile(filename)
  159. if err != nil {
  160. return err
  161. }
  162. currentEnv := map[string]bool{}
  163. rawEnv := os.Environ()
  164. for _, rawEnvLine := range rawEnv {
  165. key := strings.Split(rawEnvLine, "=")[0]
  166. currentEnv[key] = true
  167. }
  168. for key, value := range envMap {
  169. if !currentEnv[key] || overload {
  170. os.Setenv(key, value)
  171. }
  172. }
  173. return nil
  174. }
  175. func readFile(filename string) (envMap map[string]string, err error) {
  176. file, err := os.Open(filename)
  177. if err != nil {
  178. return
  179. }
  180. defer file.Close()
  181. return Parse(file)
  182. }
  183. func parseLine(line string) (key string, value string, err error) {
  184. if len(line) == 0 {
  185. err = errors.New("zero length string")
  186. return
  187. }
  188. // ditch the comments (but keep quoted hashes)
  189. if strings.Contains(line, "#") {
  190. segmentsBetweenHashes := strings.Split(line, "#")
  191. quotesAreOpen := false
  192. var segmentsToKeep []string
  193. for _, segment := range segmentsBetweenHashes {
  194. if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
  195. if quotesAreOpen {
  196. quotesAreOpen = false
  197. segmentsToKeep = append(segmentsToKeep, segment)
  198. } else {
  199. quotesAreOpen = true
  200. }
  201. }
  202. if len(segmentsToKeep) == 0 || quotesAreOpen {
  203. segmentsToKeep = append(segmentsToKeep, segment)
  204. }
  205. }
  206. line = strings.Join(segmentsToKeep, "#")
  207. }
  208. firstEquals := strings.Index(line, "=")
  209. firstColon := strings.Index(line, ":")
  210. splitString := strings.SplitN(line, "=", 2)
  211. if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) {
  212. //this is a yaml-style line
  213. splitString = strings.SplitN(line, ":", 2)
  214. }
  215. if len(splitString) != 2 {
  216. err = errors.New("Can't separate key from value")
  217. return
  218. }
  219. // Parse the key
  220. key = splitString[0]
  221. if strings.HasPrefix(key, "export") {
  222. key = strings.TrimPrefix(key, "export")
  223. }
  224. key = strings.Trim(key, " ")
  225. // Parse the value
  226. value = parseValue(splitString[1])
  227. return
  228. }
  229. func parseValue(value string) string {
  230. // trim
  231. value = strings.Trim(value, " ")
  232. // check if we've got quoted values or possible escapes
  233. if len(value) > 1 {
  234. first := string(value[0:1])
  235. last := string(value[len(value)-1:])
  236. if first == last && strings.ContainsAny(first, `"'`) {
  237. // pull the quotes off the edges
  238. value = value[1 : len(value)-1]
  239. // handle escapes
  240. escapeRegex := regexp.MustCompile(`\\.`)
  241. value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string {
  242. c := strings.TrimPrefix(match, `\`)
  243. switch c {
  244. case "n":
  245. return "\n"
  246. case "r":
  247. return "\r"
  248. default:
  249. return c
  250. }
  251. })
  252. }
  253. }
  254. return value
  255. }
  256. func isIgnoredLine(line string) bool {
  257. trimmedLine := strings.Trim(line, " \n\t")
  258. return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
  259. }
  260. func doubleQuoteEscape(line string) string {
  261. for _, c := range doubleQuoteSpecialChars {
  262. toReplace := "\\" + string(c)
  263. if c == '\n' {
  264. toReplace = `\n`
  265. }
  266. if c == '\r' {
  267. toReplace = `\r`
  268. }
  269. line = strings.Replace(line, string(c), toReplace, -1)
  270. }
  271. return line
  272. }