github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/opts/file.go (about)

     1  package opts
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"unicode"
    10  	"unicode/utf8"
    11  )
    12  
    13  const whiteSpaces = " \t"
    14  
    15  // ErrBadKey typed error for bad environment variable
    16  type ErrBadKey struct {
    17  	msg string
    18  }
    19  
    20  func (e ErrBadKey) Error() string {
    21  	return fmt.Sprintf("poorly formatted environment: %s", e.msg)
    22  }
    23  
    24  func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([]string, error) {
    25  	fh, err := os.Open(filename)
    26  	if err != nil {
    27  		return []string{}, err
    28  	}
    29  	defer fh.Close()
    30  
    31  	lines := []string{}
    32  	scanner := bufio.NewScanner(fh)
    33  	currentLine := 0
    34  	utf8bom := []byte{0xEF, 0xBB, 0xBF}
    35  	for scanner.Scan() {
    36  		scannedBytes := scanner.Bytes()
    37  		if !utf8.Valid(scannedBytes) {
    38  			return []string{}, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filename, currentLine+1, scannedBytes)
    39  		}
    40  		// We trim UTF8 BOM
    41  		if currentLine == 0 {
    42  			scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
    43  		}
    44  		// trim the line from all leading whitespace first
    45  		line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
    46  		currentLine++
    47  		// line is not empty, and not starting with '#'
    48  		if len(line) > 0 && !strings.HasPrefix(line, "#") {
    49  			variable, value, hasValue := strings.Cut(line, "=")
    50  
    51  			// trim the front of a variable, but nothing else
    52  			variable = strings.TrimLeft(variable, whiteSpaces)
    53  			if strings.ContainsAny(variable, whiteSpaces) {
    54  				return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' contains whitespaces", variable)}
    55  			}
    56  			if len(variable) == 0 {
    57  				return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)}
    58  			}
    59  
    60  			if hasValue {
    61  				// pass the value through, no trimming
    62  				lines = append(lines, variable+"="+value)
    63  			} else {
    64  				var present bool
    65  				if emptyFn != nil {
    66  					value, present = emptyFn(line)
    67  				}
    68  				if present {
    69  					// if only a pass-through variable is given, clean it up.
    70  					lines = append(lines, strings.TrimSpace(variable)+"="+value)
    71  				}
    72  			}
    73  		}
    74  	}
    75  	return lines, scanner.Err()
    76  }