File: template.go

package info (click to toggle)
golang-github-aanand-compose-file 0.0~git20161122.0.a3e5876-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 604 kB
  • ctags: 567
  • sloc: makefile: 12; sh: 9
file content (108 lines) | stat: -rw-r--r-- 2,607 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package template

import (
	"fmt"
	"regexp"
	"strings"
)

var delimiter = "\\$"
var substitution = "[_a-z][_a-z0-9]*(?::?-[^}]+)?"

var patternString = fmt.Sprintf(
	"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
	delimiter, delimiter, substitution, substitution,
)

var pattern = regexp.MustCompile(patternString)

type InvalidTemplateError struct {
	Template string
}

func (e InvalidTemplateError) Error() string {
	return fmt.Sprintf("Invalid template: %#v", e.Template)
}

// A user-supplied function which maps from variable names to values.
// Returns the value as a string and a bool indicating whether
// the value is present, to distinguish between an empty string
// and the absence of a value.
type Mapping func(string) (string, bool)

func Substitute(template string, mapping Mapping) (result string, err *InvalidTemplateError) {
	defer func() {
		if r := recover(); r != nil {
			if e, ok := r.(*InvalidTemplateError); ok {
				err = e
			} else {
				panic(r)
			}
		}
	}()

	result = pattern.ReplaceAllStringFunc(template, func(substring string) string {
		matches := pattern.FindStringSubmatch(substring)
		groups := make(map[string]string)
		for i, name := range pattern.SubexpNames() {
			if i != 0 {
				groups[name] = matches[i]
			}
		}

		substitution := groups["named"]
		if substitution == "" {
			substitution = groups["braced"]
		}
		if substitution != "" {
			// Soft default (fall back if unset or empty)
			if strings.Contains(substitution, ":-") {
				name, defaultValue := partition(substitution, ":-")
				value, ok := mapping(name)
				if !ok || value == "" {
					return defaultValue
				}
				return value
			}

			// Hard default (fall back if-and-only-if empty)
			if strings.Contains(substitution, "-") {
				name, defaultValue := partition(substitution, "-")
				value, ok := mapping(name)
				if !ok {
					return defaultValue
				}
				return value
			}

			// No default (fall back to empty string)
			value, ok := mapping(substitution)
			if !ok {
				return ""
			}
			return value
		}

		if escaped := groups["escaped"]; escaped != "" {
			return escaped
		}

		panic(&InvalidTemplateError{Template: template})
		return ""
	})

	return
}

// Split the string at the first occurrence of sep, and return the part before the separator,
// and the part after the separator.
//
// If the separator is not found, return the string itself, followed by an empty string.
func partition(s, sep string) (string, string) {
	if strings.Contains(s, sep) {
		parts := strings.SplitN(s, sep, 2)
		return parts[0], parts[1]
	} else {
		return s, ""
	}
}