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  }