github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/config/interpolation.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/Sirupsen/logrus"
     9  )
    10  
    11  func isNum(c uint8) bool {
    12  	return c >= '0' && c <= '9'
    13  }
    14  
    15  func validVariableNameChar(c uint8) bool {
    16  	return c == '_' ||
    17  		c >= 'A' && c <= 'Z' ||
    18  		c >= 'a' && c <= 'z' ||
    19  		isNum(c)
    20  }
    21  
    22  func parseVariable(line string, pos int, mapping func(string) string) (string, int, bool) {
    23  	var buffer bytes.Buffer
    24  
    25  	for ; pos < len(line); pos++ {
    26  		c := line[pos]
    27  
    28  		switch {
    29  		case validVariableNameChar(c):
    30  			buffer.WriteByte(c)
    31  		default:
    32  			return mapping(buffer.String()), pos - 1, true
    33  		}
    34  	}
    35  
    36  	return mapping(buffer.String()), pos, true
    37  }
    38  
    39  func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) {
    40  	var buffer bytes.Buffer
    41  
    42  	for ; pos < len(line); pos++ {
    43  		c := line[pos]
    44  
    45  		switch {
    46  		case c == '}':
    47  			bufferString := buffer.String()
    48  
    49  			if bufferString == "" {
    50  				return "", 0, false
    51  			}
    52  
    53  			return mapping(buffer.String()), pos, true
    54  		case validVariableNameChar(c):
    55  			buffer.WriteByte(c)
    56  		default:
    57  			return "", 0, false
    58  		}
    59  	}
    60  
    61  	return "", 0, false
    62  }
    63  
    64  func parseInterpolationExpression(line string, pos int, mapping func(string) string) (string, int, bool) {
    65  	c := line[pos]
    66  
    67  	switch {
    68  	case c == '$':
    69  		return "$", pos, true
    70  	case c == '{':
    71  		return parseVariableWithBraces(line, pos+1, mapping)
    72  	case !isNum(c) && validVariableNameChar(c):
    73  		// Variables can't start with a number
    74  		return parseVariable(line, pos, mapping)
    75  	default:
    76  		return "", 0, false
    77  	}
    78  }
    79  
    80  func parseLine(line string, mapping func(string) string) (string, bool) {
    81  	var buffer bytes.Buffer
    82  
    83  	for pos := 0; pos < len(line); pos++ {
    84  		c := line[pos]
    85  		switch {
    86  		case c == '$':
    87  			var replaced string
    88  			var success bool
    89  
    90  			replaced, pos, success = parseInterpolationExpression(line, pos+1, mapping)
    91  
    92  			if !success {
    93  				return "", false
    94  			}
    95  
    96  			buffer.WriteString(replaced)
    97  		default:
    98  			buffer.WriteByte(c)
    99  		}
   100  	}
   101  
   102  	return buffer.String(), true
   103  }
   104  
   105  func parseConfig(option, service string, data *interface{}, mapping func(string) string) error {
   106  	switch typedData := (*data).(type) {
   107  	case string:
   108  		var success bool
   109  
   110  		*data, success = parseLine(typedData, mapping)
   111  
   112  		if !success {
   113  			return fmt.Errorf("Invalid interpolation format for \"%s\" option in service \"%s\": \"%s\"", option, service, typedData)
   114  		}
   115  	case []interface{}:
   116  		for k, v := range typedData {
   117  			err := parseConfig(option, service, &v, mapping)
   118  
   119  			if err != nil {
   120  				return err
   121  			}
   122  
   123  			typedData[k] = v
   124  		}
   125  	case map[interface{}]interface{}:
   126  		for k, v := range typedData {
   127  			err := parseConfig(option, service, &v, mapping)
   128  
   129  			if err != nil {
   130  				return err
   131  			}
   132  
   133  			typedData[k] = v
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  // Interpolate replaces variables in the raw map representation of the project file
   141  func Interpolate(environmentLookup EnvironmentLookup, config *RawServiceMap) error {
   142  	for k, v := range *config {
   143  		for k2, v2 := range v {
   144  			err := parseConfig(k2, k, &v2, func(s string) string {
   145  				values := environmentLookup.Lookup(s, k, nil)
   146  
   147  				if len(values) == 0 {
   148  					logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
   149  					return ""
   150  				}
   151  
   152  				// Use first result if many are given
   153  				value := values[0]
   154  
   155  				// Environment variables come in key=value format
   156  				// Return everything past first '='
   157  				return strings.SplitN(value, "=", 2)[1]
   158  			})
   159  
   160  			if err != nil {
   161  				return err
   162  			}
   163  
   164  			(*config)[k][k2] = v2
   165  		}
   166  	}
   167  
   168  	return nil
   169  }