Skip to content

Commit

Permalink
vendor go-dotenv (#955)
Browse files Browse the repository at this point in the history
* vendor go-dotenv

This makes it easier for Debian to package direnv. See #951

Repo: https://github.com/direnv/go-dotenv
Commit ID: 872ea3db4cb52bd8f1ae6690593f5d20c221c35b

* dotenv: fix linter warnings
  • Loading branch information
zimbatm committed Jun 21, 2022
1 parent bdc1ec9 commit 6e0ab67
Show file tree
Hide file tree
Showing 7 changed files with 508 additions and 5 deletions.
2 changes: 1 addition & 1 deletion default.nix
Expand Up @@ -12,7 +12,7 @@ buildGoModule rec {
version = lib.fileContents ./version.txt;
subPackages = [ "." ];

vendorSha256 = "sha256-gFGGnnR1UNT4MYC411X8NwIqVJZqhnmUlVR+XAnrKY8=";
vendorSha256 = "sha256-u/LukIOYRudFYOrrlZTMtDAlM3+WjoSBiueR7aySSVU=";

src = builtins.fetchGit ./.;

Expand Down
1 change: 0 additions & 1 deletion go.mod
Expand Up @@ -4,7 +4,6 @@ go 1.16

require (
github.com/BurntSushi/toml v1.1.0
github.com/direnv/go-dotenv v0.0.0-20220613081022-872ea3db4cb5
github.com/mattn/go-isatty v0.0.14
golang.org/x/mod v0.5.1
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
@@ -1,7 +1,5 @@
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/direnv/go-dotenv v0.0.0-20220613081022-872ea3db4cb5 h1:lMrU+5WHwaPfmvp+CIxUi2ujVJSu/mYh1Rcu23n7HaY=
github.com/direnv/go-dotenv v0.0.0-20220613081022-872ea3db4cb5/go.mod h1:K5R9ofHu1mQRwNWMZgDnr0s1Ca1nkvz9r09NN/U2UyM=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/cmd_dotenv.go
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"io/ioutil"

"github.com/direnv/go-dotenv"
"github.com/direnv/direnv/v2/pkg/dotenv"
)

// CmdDotEnv is `direnv dotenv [SHELL [PATH_TO_DOTENV]]`
Expand Down
29 changes: 29 additions & 0 deletions pkg/dotenv/README.md
@@ -0,0 +1,29 @@
# go-dotenv

Go parsing library for the dotenv format.

There is no formal definition of the dotenv format but it has been introduced
by https://github.com/bkeepers/dotenv which is thus canonical. This library is a port of that.

This library was developed specifically for [direnv](https://direnv.net).

## Features

* `k=v` format
* bash `export k=v` format
* yaml `k: v` format
* variable expansion, including default values as in `${FOO:-default}`
* comments

## Missing

* probably needs API breakage

## Alternatives

Some other good alternatives with various variations.

* https://github.com/joho/godotenv
* https://github.com/lazureykis/dotenv
* https://github.com/subosito/gotenv

150 changes: 150 additions & 0 deletions pkg/dotenv/parse.go
@@ -0,0 +1,150 @@
// Package dotenv implements the parsing of the .env format.
//
// There is no formal definition of the format but it has been introduced by
// https://github.com/bkeepers/dotenv which is thus canonical.
package dotenv

import (
"fmt"
"os"
"regexp"
"strings"
)

// LINE is the regexp matching a single line
const LINE = `
\A
\s*
(?:|#.*| # comment line
(?:export\s+)? # optional export
([\w\.]+) # key
(?:\s*=\s*|:\s+?) # separator
( # optional value begin
'(?:\'|[^'])*' # single quoted value
| # or
"(?:\"|[^"])*" # double quoted value
| # or
[^\s#\n]+ # unquoted value
)? # value end
\s*
(?:\#.*)? # optional comment
)
\z
`

var linesRe = regexp.MustCompile("[\\r\\n]+")
var lineRe = regexp.MustCompile(
regexp.MustCompile("\\s+").ReplaceAllLiteralString(
regexp.MustCompile("\\s+# .*").ReplaceAllLiteralString(LINE, ""), ""))

// Parse reads a string in the .env format and returns a map of the extracted key=values.
//
// Ported from https://github.com/bkeepers/dotenv/blob/84f33f48107c492c3a99bd41c1059e7b4c1bb67a/lib/dotenv/parser.rb
func Parse(data string) (map[string]string, error) {
var dotenv = make(map[string]string)

for _, line := range linesRe.Split(data, -1) {
if !lineRe.MatchString(line) {
return nil, fmt.Errorf("invalid line: %s", line)
}

match := lineRe.FindStringSubmatch(line)
// commented or empty line
if len(match) == 0 {
continue
}
if len(match[1]) == 0 {
continue
}

key := match[1]
value := match[2]

parseValue(key, value, dotenv)
}

return dotenv, nil
}

// MustParse works the same as Parse but panics on error
func MustParse(data string) map[string]string {
env, err := Parse(data)
if err != nil {
panic(err)
}
return env
}

func parseValue(key string, value string, dotenv map[string]string) {
if len(value) <= 1 {
dotenv[key] = value
return
}

singleQuoted := false

if value[0:1] == "'" && value[len(value)-1:] == "'" {
// single-quoted string, do not expand
singleQuoted = true
value = value[1 : len(value)-1]
} else if value[0:1] == `"` && value[len(value)-1:] == `"` {
value = value[1 : len(value)-1]
value = expandNewLines(value)
value = unescapeCharacters(value)
}

if !singleQuoted {
value = expandEnv(value, dotenv)
}

dotenv[key] = value
}

var escRe = regexp.MustCompile("\\\\([^$])")

func unescapeCharacters(value string) string {
return escRe.ReplaceAllString(value, "$1")
}

func expandNewLines(value string) string {
value = strings.Replace(value, "\\n", "\n", -1)
value = strings.Replace(value, "\\r", "\r", -1)
return value
}

func expandEnv(value string, dotenv map[string]string) string {
expander := func(value string) string {
envKey, defaultValue, hasDefault := splitKeyAndDefault(value, ":-")
expanded, found := lookupDotenv(envKey, dotenv)

if found {
return expanded
}
return getFromEnvOrDefault(envKey, defaultValue, hasDefault)
}

return os.Expand(value, expander)
}

func splitKeyAndDefault(value string, sep string) (string, string, bool) {
var i = strings.Index(value, sep)

if i == -1 {
return value, "", false
}
return value[0:i], value[i+len(sep):], true
}

func lookupDotenv(value string, dotenv map[string]string) (string, bool) {
retval, ok := dotenv[value]
return retval, ok
}

func getFromEnvOrDefault(envKey string, defaultValue string, hasDefault bool) string {
var envValue = os.Getenv(envKey)

if len(envValue) == 0 && hasDefault {
return defaultValue
}
return envValue
}

0 comments on commit 6e0ab67

Please sign in to comment.