github.com/olljanat/moby@v1.13.1/cli/compose/template/template.go (about)

     1  package template
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  )
     8  
     9  var delimiter = "\\$"
    10  var substitution = "[_a-z][_a-z0-9]*(?::?-[^}]+)?"
    11  
    12  var patternString = fmt.Sprintf(
    13  	"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
    14  	delimiter, delimiter, substitution, substitution,
    15  )
    16  
    17  var pattern = regexp.MustCompile(patternString)
    18  
    19  // InvalidTemplateError is returned when a variable template is not in a valid
    20  // format
    21  type InvalidTemplateError struct {
    22  	Template string
    23  }
    24  
    25  func (e InvalidTemplateError) Error() string {
    26  	return fmt.Sprintf("Invalid template: %#v", e.Template)
    27  }
    28  
    29  // Mapping is a user-supplied function which maps from variable names to values.
    30  // Returns the value as a string and a bool indicating whether
    31  // the value is present, to distinguish between an empty string
    32  // and the absence of a value.
    33  type Mapping func(string) (string, bool)
    34  
    35  // Substitute variables in the string with their values
    36  func Substitute(template string, mapping Mapping) (result string, err *InvalidTemplateError) {
    37  	result = pattern.ReplaceAllStringFunc(template, func(substring string) string {
    38  		matches := pattern.FindStringSubmatch(substring)
    39  		groups := make(map[string]string)
    40  		for i, name := range pattern.SubexpNames() {
    41  			if i != 0 {
    42  				groups[name] = matches[i]
    43  			}
    44  		}
    45  
    46  		substitution := groups["named"]
    47  		if substitution == "" {
    48  			substitution = groups["braced"]
    49  		}
    50  		if substitution != "" {
    51  			// Soft default (fall back if unset or empty)
    52  			if strings.Contains(substitution, ":-") {
    53  				name, defaultValue := partition(substitution, ":-")
    54  				value, ok := mapping(name)
    55  				if !ok || value == "" {
    56  					return defaultValue
    57  				}
    58  				return value
    59  			}
    60  
    61  			// Hard default (fall back if-and-only-if empty)
    62  			if strings.Contains(substitution, "-") {
    63  				name, defaultValue := partition(substitution, "-")
    64  				value, ok := mapping(name)
    65  				if !ok {
    66  					return defaultValue
    67  				}
    68  				return value
    69  			}
    70  
    71  			// No default (fall back to empty string)
    72  			value, ok := mapping(substitution)
    73  			if !ok {
    74  				return ""
    75  			}
    76  			return value
    77  		}
    78  
    79  		if escaped := groups["escaped"]; escaped != "" {
    80  			return escaped
    81  		}
    82  
    83  		err = &InvalidTemplateError{Template: template}
    84  		return ""
    85  	})
    86  
    87  	return result, err
    88  }
    89  
    90  // Split the string at the first occurrence of sep, and return the part before the separator,
    91  // and the part after the separator.
    92  //
    93  // If the separator is not found, return the string itself, followed by an empty string.
    94  func partition(s, sep string) (string, string) {
    95  	if strings.Contains(s, sep) {
    96  		parts := strings.SplitN(s, sep, 2)
    97  		return parts[0], parts[1]
    98  	}
    99  	return s, ""
   100  }