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  }