github.com/stevenmatthewt/agent@v3.5.4+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 // Figure out the absolute path 25 absolutePath, err := f.AbsolutePath() 26 if err != nil { 27 return err 28 } 29 30 // Open the file 31 file, err := os.Open(absolutePath) 32 if err != nil { 33 return err 34 } 35 36 // Make sure the config file is closed when this function finishes 37 defer file.Close() 38 39 // Get all the lines in the file 40 var lines []string 41 scanner := bufio.NewScanner(file) 42 for scanner.Scan() { 43 lines = append(lines, scanner.Text()) 44 } 45 46 // Parse each line 47 for _, fullLine := range lines { 48 if !isIgnoredLine(fullLine) { 49 key, value, err := parseLine(fullLine) 50 if err != nil { 51 return err 52 } 53 54 f.Config[key] = value 55 } 56 } 57 58 return nil 59 } 60 61 func (f File) AbsolutePath() (string, error) { 62 return utils.NormalizeFilePath(f.Path) 63 } 64 65 func (f File) Exists() bool { 66 // If getting the absolute path fails, we can just assume it doesn't 67 // exit...probably... 68 absolutePath, err := f.AbsolutePath() 69 if err != nil { 70 return false 71 } 72 73 if _, err := os.Stat(absolutePath); err == nil { 74 return true 75 } else { 76 return false 77 } 78 } 79 80 // This file parsing code was copied from: 81 // https://github.com/joho/godotenv/blob/master/godotenv.go 82 // 83 // The project is released under an MIT License, which can be seen here: 84 // https://github.com/joho/godotenv/blob/master/LICENCE 85 func parseLine(line string) (key string, value string, err error) { 86 if len(line) == 0 { 87 err = errors.New("zero length string") 88 return 89 } 90 91 // ditch the comments (but keep quoted hashes) 92 if strings.Contains(line, "#") { 93 segmentsBetweenHashes := strings.Split(line, "#") 94 quotesAreOpen := false 95 segmentsToKeep := make([]string, 0) 96 for _, segment := range segmentsBetweenHashes { 97 if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { 98 if quotesAreOpen { 99 quotesAreOpen = false 100 segmentsToKeep = append(segmentsToKeep, segment) 101 } else { 102 quotesAreOpen = true 103 } 104 } 105 106 if len(segmentsToKeep) == 0 || quotesAreOpen { 107 segmentsToKeep = append(segmentsToKeep, segment) 108 } 109 } 110 111 line = strings.Join(segmentsToKeep, "#") 112 } 113 114 // now split key from value 115 splitString := strings.SplitN(line, "=", 2) 116 117 if len(splitString) != 2 { 118 // try yaml mode! 119 splitString = strings.SplitN(line, ":", 2) 120 } 121 122 if len(splitString) != 2 { 123 err = errors.New("Can't separate key from value") 124 return 125 } 126 127 // Parse the key 128 key = splitString[0] 129 if strings.HasPrefix(key, "export") { 130 key = strings.TrimPrefix(key, "export") 131 } 132 key = strings.Trim(key, " ") 133 134 // Parse the value 135 value = splitString[1] 136 // trim 137 value = strings.Trim(value, " ") 138 139 // check if we've got quoted values 140 if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 { 141 // pull the quotes off the edges 142 value = strings.Trim(value, "\"'") 143 144 // expand quotes 145 value = strings.Replace(value, "\\\"", "\"", -1) 146 // expand newlines 147 value = strings.Replace(value, "\\n", "\n", -1) 148 } 149 150 return 151 } 152 153 func isIgnoredLine(line string) bool { 154 trimmedLine := strings.Trim(line, " \n\t") 155 return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") 156 }