file.go 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. package opts
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "unicode"
  9. "unicode/utf8"
  10. )
  11. const whiteSpaces = " \t"
  12. func parseKeyValueFile(filename string, lookupFn func(string) (string, bool)) ([]string, error) {
  13. fh, err := os.Open(filename)
  14. if err != nil {
  15. return []string{}, err
  16. }
  17. defer fh.Close()
  18. lines := []string{}
  19. scanner := bufio.NewScanner(fh)
  20. utf8bom := []byte{0xEF, 0xBB, 0xBF}
  21. for currentLine := 1; scanner.Scan(); currentLine++ {
  22. scannedBytes := scanner.Bytes()
  23. if !utf8.Valid(scannedBytes) {
  24. return []string{}, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filename, currentLine, scannedBytes)
  25. }
  26. // We trim UTF8 BOM
  27. if currentLine == 1 {
  28. scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
  29. }
  30. // trim the line from all leading whitespace first. trailing whitespace
  31. // is part of the value, and is kept unmodified.
  32. line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
  33. if len(line) == 0 || line[0] == '#' {
  34. // skip empty lines and comments (lines starting with '#')
  35. continue
  36. }
  37. key, _, hasValue := strings.Cut(line, "=")
  38. if len(key) == 0 {
  39. return []string{}, fmt.Errorf("no variable name on line '%s'", line)
  40. }
  41. // leading whitespace was already removed from the line, but
  42. // variables are not allowed to contain whitespace or have
  43. // trailing whitespace.
  44. if strings.ContainsAny(key, whiteSpaces) {
  45. return []string{}, fmt.Errorf("variable '%s' contains whitespaces", key)
  46. }
  47. if hasValue {
  48. // key/value pair is valid and has a value; add the line as-is.
  49. lines = append(lines, line)
  50. continue
  51. }
  52. if lookupFn != nil {
  53. // No value given; try to look up the value. The value may be
  54. // empty but if no value is found, the key is omitted.
  55. if value, found := lookupFn(line); found {
  56. lines = append(lines, key+"="+value)
  57. }
  58. }
  59. }
  60. return lines, scanner.Err()
  61. }