github.com/discordapp/buildkite-agent@v2.6.6+incompatible/cliconfig/file.go (about) 1 package cliconfig 2 3 import ( 4 "bufio" 5 "errors" 6 "os" 7 "strings" 8 9 "github.com/buildkite/agent/utils" 10 ) 11 12 type File struct { 13 // The path to the file 14 Path string 15 16 // A map of key/values that was loaded from the file 17 Config map[string]string 18 } 19 20 func (f *File) Load() error { 21 // Set the default config 22 f.Config = map[string]string{} 23 24 // Open the file 25 file, err := os.Open(f.AbsolutePath()) 26 if err != nil { 27 return err 28 } 29 30 // Make sure the config file is closed when this function finishes 31 defer file.Close() 32 33 // Get all the lines in the file 34 var lines []string 35 scanner := bufio.NewScanner(file) 36 for scanner.Scan() { 37 lines = append(lines, scanner.Text()) 38 } 39 40 // Parse each line 41 for _, fullLine := range lines { 42 if !isIgnoredLine(fullLine) { 43 key, value, err := parseLine(fullLine) 44 if err != nil { 45 return err 46 } 47 48 f.Config[key] = value 49 } 50 } 51 52 return nil 53 } 54 55 func (f File) AbsolutePath() string { 56 return utils.NormalizeFilePath(f.Path) 57 } 58 59 func (f File) Exists() bool { 60 if _, err := os.Stat(f.AbsolutePath()); err == nil { 61 return true 62 } else { 63 return false 64 } 65 } 66 67 // This file parsing code was copied from: 68 // https://github.com/joho/godotenv/blob/master/godotenv.go 69 // 70 // The project is released under an MIT License, which can be seen here: 71 // https://github.com/joho/godotenv/blob/master/LICENCE 72 func parseLine(line string) (key string, value string, err error) { 73 if len(line) == 0 { 74 err = errors.New("zero length string") 75 return 76 } 77 78 // ditch the comments (but keep quoted hashes) 79 if strings.Contains(line, "#") { 80 segmentsBetweenHashes := strings.Split(line, "#") 81 quotesAreOpen := false 82 segmentsToKeep := make([]string, 0) 83 for _, segment := range segmentsBetweenHashes { 84 if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { 85 if quotesAreOpen { 86 quotesAreOpen = false 87 segmentsToKeep = append(segmentsToKeep, segment) 88 } else { 89 quotesAreOpen = true 90 } 91 } 92 93 if len(segmentsToKeep) == 0 || quotesAreOpen { 94 segmentsToKeep = append(segmentsToKeep, segment) 95 } 96 } 97 98 line = strings.Join(segmentsToKeep, "#") 99 } 100 101 // now split key from value 102 splitString := strings.SplitN(line, "=", 2) 103 104 if len(splitString) != 2 { 105 // try yaml mode! 106 splitString = strings.SplitN(line, ":", 2) 107 } 108 109 if len(splitString) != 2 { 110 err = errors.New("Can't separate key from value") 111 return 112 } 113 114 // Parse the key 115 key = splitString[0] 116 if strings.HasPrefix(key, "export") { 117 key = strings.TrimPrefix(key, "export") 118 } 119 key = strings.Trim(key, " ") 120 121 // Parse the value 122 value = splitString[1] 123 // trim 124 value = strings.Trim(value, " ") 125 126 // check if we've got quoted values 127 if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 { 128 // pull the quotes off the edges 129 value = strings.Trim(value, "\"'") 130 131 // expand quotes 132 value = strings.Replace(value, "\\\"", "\"", -1) 133 // expand newlines 134 value = strings.Replace(value, "\\n", "\n", -1) 135 } 136 137 return 138 } 139 140 func isIgnoredLine(line string) bool { 141 trimmedLine := strings.Trim(line, " \n\t") 142 return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") 143 }