mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 19:52:24 -05:00 
			
		
		
		
	Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.14.0 to 1.15.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.14.0...v1.15.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
		
			
				
	
	
		
			381 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			381 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package gotenv provides functionality to dynamically load the environment variables
 | |
| package gotenv
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// Pattern for detecting valid line format
 | |
| 	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
 | |
| 
 | |
| 	// Pattern for detecting valid variable within a value
 | |
| 	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
 | |
| 
 | |
| 	// Byte order mark character
 | |
| 	bom = "\xef\xbb\xbf"
 | |
| )
 | |
| 
 | |
| // Env holds key/value pair of valid environment variable
 | |
| type Env map[string]string
 | |
| 
 | |
| // 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.
 | |
| // When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
 | |
| // Otherwise, it will loop over the filenames parameter and set the proper environment variables.
 | |
| func Load(filenames ...string) error {
 | |
| 	return loadenv(false, filenames...)
 | |
| }
 | |
| 
 | |
| // OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables.
 | |
| func OverLoad(filenames ...string) error {
 | |
| 	return loadenv(true, filenames...)
 | |
| }
 | |
| 
 | |
| // Must is wrapper function that will panic when supplied function returns an error.
 | |
| func Must(fn func(filenames ...string) error, filenames ...string) {
 | |
| 	if err := fn(filenames...); err != nil {
 | |
| 		panic(err.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist.
 | |
| func Apply(r io.Reader) error {
 | |
| 	return parset(r, false)
 | |
| }
 | |
| 
 | |
| // OverApply is a function to load an io Reader then export and override the valid variables into environment variables.
 | |
| func OverApply(r io.Reader) error {
 | |
| 	return parset(r, true)
 | |
| }
 | |
| 
 | |
| func loadenv(override bool, filenames ...string) error {
 | |
| 	if len(filenames) == 0 {
 | |
| 		filenames = []string{".env"}
 | |
| 	}
 | |
| 
 | |
| 	for _, filename := range filenames {
 | |
| 		f, err := os.Open(filename)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		err = parset(f, override)
 | |
| 		f.Close()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // parse and set :)
 | |
| func parset(r io.Reader, override bool) error {
 | |
| 	env, err := strictParse(r, override)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	for key, val := range env {
 | |
| 		setenv(key, val, override)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func setenv(key, val string, override bool) {
 | |
| 	if override {
 | |
| 		os.Setenv(key, val)
 | |
| 	} else {
 | |
| 		if _, present := os.LookupEnv(key); !present {
 | |
| 			os.Setenv(key, val)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
 | |
| // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
 | |
| // This function is skipping any invalid lines and only processing the valid one.
 | |
| func Parse(r io.Reader) Env {
 | |
| 	env, _ := strictParse(r, false)
 | |
| 	return env
 | |
| }
 | |
| 
 | |
| // StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
 | |
| // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
 | |
| // This function is returning an error if there are any invalid lines.
 | |
| func StrictParse(r io.Reader) (Env, error) {
 | |
| 	return strictParse(r, false)
 | |
| }
 | |
| 
 | |
| // Read is a function to parse a file line by line and returns the valid Env key/value pair of valid variables.
 | |
| // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
 | |
| // This function is skipping any invalid lines and only processing the valid one.
 | |
| func Read(filename string) (Env, error) {
 | |
| 	f, err := os.Open(filename)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 	return strictParse(f, false)
 | |
| }
 | |
| 
 | |
| // Unmarshal reads a string line by line and returns the valid Env key/value pair of valid variables.
 | |
| // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
 | |
| // This function is returning an error if there are any invalid lines.
 | |
| func Unmarshal(str string) (Env, error) {
 | |
| 	return strictParse(strings.NewReader(str), false)
 | |
| }
 | |
| 
 | |
| // Marshal outputs the given environment as a env file.
 | |
| // Variables will be sorted by name.
 | |
| func Marshal(env Env) (string, error) {
 | |
| 	lines := make([]string, 0, len(env))
 | |
| 	for k, v := range env {
 | |
| 		if d, err := strconv.Atoi(v); err == nil {
 | |
| 			lines = append(lines, fmt.Sprintf(`%s=%d`, k, d))
 | |
| 		} else {
 | |
| 			lines = append(lines, fmt.Sprintf(`%s=%q`, k, v))
 | |
| 		}
 | |
| 	}
 | |
| 	sort.Strings(lines)
 | |
| 	return strings.Join(lines, "\n"), nil
 | |
| }
 | |
| 
 | |
| // Write serializes the given environment and writes it to a file
 | |
| func Write(env Env, filename string) error {
 | |
| 	content, err := Marshal(env)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	// ensure the path exists
 | |
| 	if err := os.MkdirAll(filepath.Dir(filename), 0o775); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	// create or truncate the file
 | |
| 	file, err := os.Create(filename)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer file.Close()
 | |
| 	_, err = file.WriteString(content + "\n")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return file.Sync()
 | |
| }
 | |
| 
 | |
| // splitLines is a valid SplitFunc for a bufio.Scanner. It will split lines on CR ('\r'), LF ('\n') or CRLF (any of the three sequences).
 | |
| // If a CR is immediately followed by a LF, it is treated as a CRLF (one single line break).
 | |
| func splitLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
 | |
| 	if atEOF && len(data) == 0 {
 | |
| 		return 0, nil, bufio.ErrFinalToken
 | |
| 	}
 | |
| 
 | |
| 	idx := bytes.IndexAny(data, "\r\n")
 | |
| 	switch {
 | |
| 	case atEOF && idx < 0:
 | |
| 		return len(data), data, bufio.ErrFinalToken
 | |
| 
 | |
| 	case idx < 0:
 | |
| 		return 0, nil, nil
 | |
| 	}
 | |
| 
 | |
| 	// consume CR or LF
 | |
| 	eol := idx + 1
 | |
| 	// detect CRLF
 | |
| 	if len(data) > eol && data[eol-1] == '\r' && data[eol] == '\n' {
 | |
| 		eol++
 | |
| 	}
 | |
| 
 | |
| 	return eol, data[:idx], nil
 | |
| }
 | |
| 
 | |
| func strictParse(r io.Reader, override bool) (Env, error) {
 | |
| 	env := make(Env)
 | |
| 	scanner := bufio.NewScanner(r)
 | |
| 	scanner.Split(splitLines)
 | |
| 
 | |
| 	firstLine := true
 | |
| 
 | |
| 	for scanner.Scan() {
 | |
| 		line := strings.TrimSpace(scanner.Text())
 | |
| 
 | |
| 		if firstLine {
 | |
| 			line = strings.TrimPrefix(line, bom)
 | |
| 			firstLine = false
 | |
| 		}
 | |
| 
 | |
| 		if line == "" || line[0] == '#' {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		quote := ""
 | |
| 		// look for the delimiter character
 | |
| 		idx := strings.Index(line, "=")
 | |
| 		if idx == -1 {
 | |
| 			idx = strings.Index(line, ":")
 | |
| 		}
 | |
| 		// look for a quote character
 | |
| 		if idx > 0 && idx < len(line)-1 {
 | |
| 			val := strings.TrimSpace(line[idx+1:])
 | |
| 			if val[0] == '"' || val[0] == '\'' {
 | |
| 				quote = val[:1]
 | |
| 				// look for the closing quote character within the same line
 | |
| 				idx = strings.LastIndex(strings.TrimSpace(val[1:]), quote)
 | |
| 				if idx >= 0 && val[idx] != '\\' {
 | |
| 					quote = ""
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		// look for the closing quote character
 | |
| 		for quote != "" && scanner.Scan() {
 | |
| 			l := scanner.Text()
 | |
| 			line += "\n" + l
 | |
| 			idx := strings.LastIndex(l, quote)
 | |
| 			if idx > 0 && l[idx-1] == '\\' {
 | |
| 				// foud a matching quote character but it's escaped
 | |
| 				continue
 | |
| 			}
 | |
| 			if idx >= 0 {
 | |
| 				// foud a matching quote
 | |
| 				quote = ""
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if quote != "" {
 | |
| 			return env, fmt.Errorf("missing quotes")
 | |
| 		}
 | |
| 
 | |
| 		err := parseLine(line, env, override)
 | |
| 		if err != nil {
 | |
| 			return env, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return env, nil
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	lineRgx     = regexp.MustCompile(linePattern)
 | |
| 	unescapeRgx = regexp.MustCompile(`\\([^$])`)
 | |
| 	varRgx      = regexp.MustCompile(variablePattern)
 | |
| )
 | |
| 
 | |
| func parseLine(s string, env Env, override bool) error {
 | |
| 	rm := lineRgx.FindStringSubmatch(s)
 | |
| 
 | |
| 	if len(rm) == 0 {
 | |
| 		return checkFormat(s, env)
 | |
| 	}
 | |
| 
 | |
| 	key := strings.TrimSpace(rm[1])
 | |
| 	val := strings.TrimSpace(rm[2])
 | |
| 
 | |
| 	var hsq, hdq bool
 | |
| 
 | |
| 	// check if the value is quoted
 | |
| 	if l := len(val); l >= 2 {
 | |
| 		l -= 1
 | |
| 		// has double quotes
 | |
| 		hdq = val[0] == '"' && val[l] == '"'
 | |
| 		// has single quotes
 | |
| 		hsq = val[0] == '\'' && val[l] == '\''
 | |
| 
 | |
| 		// remove quotes '' or ""
 | |
| 		if hsq || hdq {
 | |
| 			val = val[1:l]
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if hdq {
 | |
| 		val = strings.ReplaceAll(val, `\n`, "\n")
 | |
| 		val = strings.ReplaceAll(val, `\r`, "\r")
 | |
| 
 | |
| 		// Unescape all characters except $ so variables can be escaped properly
 | |
| 		val = unescapeRgx.ReplaceAllString(val, "$1")
 | |
| 	}
 | |
| 
 | |
| 	if !hsq {
 | |
| 		fv := func(s string) string {
 | |
| 			return varReplacement(s, hsq, env, override)
 | |
| 		}
 | |
| 		val = varRgx.ReplaceAllStringFunc(val, fv)
 | |
| 	}
 | |
| 
 | |
| 	env[key] = val
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func parseExport(st string, env Env) error {
 | |
| 	if strings.HasPrefix(st, "export") {
 | |
| 		vs := strings.SplitN(st, " ", 2)
 | |
| 
 | |
| 		if len(vs) > 1 {
 | |
| 			if _, ok := env[vs[1]]; !ok {
 | |
| 				return fmt.Errorf("line `%s` has an unset variable", st)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var varNameRgx = regexp.MustCompile(`(\$)(\{?([A-Z0-9_]+)\}?)`)
 | |
| 
 | |
| func varReplacement(s string, hsq bool, env Env, override bool) string {
 | |
| 	if s == "" {
 | |
| 		return s
 | |
| 	}
 | |
| 
 | |
| 	if s[0] == '\\' {
 | |
| 		// the dollar sign is escaped
 | |
| 		return s[1:]
 | |
| 	}
 | |
| 
 | |
| 	if hsq {
 | |
| 		return s
 | |
| 	}
 | |
| 
 | |
| 	mn := varNameRgx.FindStringSubmatch(s)
 | |
| 
 | |
| 	if len(mn) == 0 {
 | |
| 		return s
 | |
| 	}
 | |
| 
 | |
| 	v := mn[3]
 | |
| 
 | |
| 	if replace, ok := os.LookupEnv(v); ok && !override {
 | |
| 		return replace
 | |
| 	}
 | |
| 
 | |
| 	if replace, ok := env[v]; ok {
 | |
| 		return replace
 | |
| 	}
 | |
| 
 | |
| 	return os.Getenv(v)
 | |
| }
 | |
| 
 | |
| func checkFormat(s string, env Env) error {
 | |
| 	st := strings.TrimSpace(s)
 | |
| 
 | |
| 	if st == "" || st[0] == '#' {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if err := parseExport(st, env); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Errorf("line `%s` doesn't match format", s)
 | |
| }
 |