gotenv.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Package gotenv provides functionality to dynamically load the environment variables
  2. package gotenv
  3. import (
  4. "bufio"
  5. "fmt"
  6. "io"
  7. "os"
  8. "regexp"
  9. "strings"
  10. )
  11. const (
  12. // Pattern for detecting valid line format
  13. linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
  14. // Pattern for detecting valid variable within a value
  15. variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
  16. )
  17. // Env holds key/value pair of valid environment variable
  18. type Env map[string]string
  19. /*
  20. Load is a function to load a file or multiple files and then export the valid variables into environment variables if they do not exist.
  21. When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
  22. Otherwise, it will loop over the filenames parameter and set the proper environment variables.
  23. */
  24. func Load(filenames ...string) error {
  25. return loadenv(false, filenames...)
  26. }
  27. /*
  28. OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables.
  29. */
  30. func OverLoad(filenames ...string) error {
  31. return loadenv(true, filenames...)
  32. }
  33. /*
  34. Must is wrapper function that will panic when supplied function returns an error.
  35. */
  36. func Must(fn func(filenames ...string) error, filenames ...string) {
  37. if err := fn(filenames...); err != nil {
  38. panic(err.Error())
  39. }
  40. }
  41. /*
  42. Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist.
  43. */
  44. func Apply(r io.Reader) error {
  45. return parset(r, false)
  46. }
  47. /*
  48. OverApply is a function to load an io Reader then export and override the valid variables into environment variables.
  49. */
  50. func OverApply(r io.Reader) error {
  51. return parset(r, true)
  52. }
  53. func loadenv(override bool, filenames ...string) error {
  54. if len(filenames) == 0 {
  55. filenames = []string{".env"}
  56. }
  57. for _, filename := range filenames {
  58. f, err := os.Open(filename)
  59. if err != nil {
  60. return err
  61. }
  62. err = parset(f, override)
  63. if err != nil {
  64. return err
  65. }
  66. f.Close()
  67. }
  68. return nil
  69. }
  70. // parse and set :)
  71. func parset(r io.Reader, override bool) error {
  72. env, err := StrictParse(r)
  73. if err != nil {
  74. return err
  75. }
  76. for key, val := range env {
  77. setenv(key, val, override)
  78. }
  79. return nil
  80. }
  81. func setenv(key, val string, override bool) {
  82. if override {
  83. os.Setenv(key, val)
  84. } else {
  85. if _, present := os.LookupEnv(key); !present {
  86. os.Setenv(key, val)
  87. }
  88. }
  89. }
  90. // Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
  91. // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
  92. // This function is skipping any invalid lines and only processing the valid one.
  93. func Parse(r io.Reader) Env {
  94. env, _ := StrictParse(r)
  95. return env
  96. }
  97. // StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
  98. // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
  99. // This function is returning an error if there are any invalid lines.
  100. func StrictParse(r io.Reader) (Env, error) {
  101. env := make(Env)
  102. scanner := bufio.NewScanner(r)
  103. i := 1
  104. bom := string([]byte{239, 187, 191})
  105. for scanner.Scan() {
  106. line := scanner.Text()
  107. if i == 1 {
  108. line = strings.TrimPrefix(line, bom)
  109. }
  110. i++
  111. err := parseLine(line, env)
  112. if err != nil {
  113. return env, err
  114. }
  115. }
  116. return env, nil
  117. }
  118. func parseLine(s string, env Env) error {
  119. rl := regexp.MustCompile(linePattern)
  120. rm := rl.FindStringSubmatch(s)
  121. if len(rm) == 0 {
  122. return checkFormat(s, env)
  123. }
  124. key := rm[1]
  125. val := rm[2]
  126. // determine if string has quote prefix
  127. hdq := strings.HasPrefix(val, `"`)
  128. // determine if string has single quote prefix
  129. hsq := strings.HasPrefix(val, `'`)
  130. // trim whitespace
  131. val = strings.Trim(val, " ")
  132. // remove quotes '' or ""
  133. rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
  134. val = rq.ReplaceAllString(val, "$2")
  135. if hdq {
  136. val = strings.Replace(val, `\n`, "\n", -1)
  137. val = strings.Replace(val, `\r`, "\r", -1)
  138. // Unescape all characters except $ so variables can be escaped properly
  139. re := regexp.MustCompile(`\\([^$])`)
  140. val = re.ReplaceAllString(val, "$1")
  141. }
  142. rv := regexp.MustCompile(variablePattern)
  143. fv := func(s string) string {
  144. return varReplacement(s, hsq, env)
  145. }
  146. val = rv.ReplaceAllStringFunc(val, fv)
  147. val = parseVal(val, env)
  148. env[key] = val
  149. return nil
  150. }
  151. func parseExport(st string, env Env) error {
  152. if strings.HasPrefix(st, "export") {
  153. vs := strings.SplitN(st, " ", 2)
  154. if len(vs) > 1 {
  155. if _, ok := env[vs[1]]; !ok {
  156. return fmt.Errorf("line `%s` has an unset variable", st)
  157. }
  158. }
  159. }
  160. return nil
  161. }
  162. func varReplacement(s string, hsq bool, env Env) string {
  163. if strings.HasPrefix(s, "\\") {
  164. return strings.TrimPrefix(s, "\\")
  165. }
  166. if hsq {
  167. return s
  168. }
  169. sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
  170. rn := regexp.MustCompile(sn)
  171. mn := rn.FindStringSubmatch(s)
  172. if len(mn) == 0 {
  173. return s
  174. }
  175. v := mn[3]
  176. replace, ok := env[v]
  177. if !ok {
  178. replace = os.Getenv(v)
  179. }
  180. return replace
  181. }
  182. func checkFormat(s string, env Env) error {
  183. st := strings.TrimSpace(s)
  184. if (st == "") || strings.HasPrefix(st, "#") {
  185. return nil
  186. }
  187. if err := parseExport(st, env); err != nil {
  188. return err
  189. }
  190. return fmt.Errorf("line `%s` doesn't match format", s)
  191. }
  192. func parseVal(val string, env Env) string {
  193. if strings.Contains(val, "=") {
  194. if !(val == "\n" || val == "\r") {
  195. kv := strings.Split(val, "\n")
  196. if len(kv) == 1 {
  197. kv = strings.Split(val, "\r")
  198. }
  199. if len(kv) > 1 {
  200. val = kv[0]
  201. for i := 1; i < len(kv); i++ {
  202. parseLine(kv[i], env)
  203. }
  204. }
  205. }
  206. }
  207. return val
  208. }