/
template.go
160 lines (141 loc) · 5.03 KB
/
template.go
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package template implements handling of package name templates.
//
// Package name templates look like e.g. "foo/${platform}" and code in this
// package knows how to expand them into full package names.
package template
import (
"fmt"
"regexp"
"strings"
"github.com/TriggerMail/luci-go/cipd/client/cipd/platform"
"github.com/TriggerMail/luci-go/common/errors"
)
// Expander is a mapping of simple string substitutions which is used to
// expand cipd package name templates. For example:
//
// ex, err := template.Expander{
// "platform": "mac-amd64"
// }.Expand("foo/${platform}")
//
// `ex` would be "foo/mac-amd64".
//
// Use DefaultExpander() to obtain the default mapping for CIPD
// applications.
type Expander map[string]string
// ErrSkipTemplate may be returned from Expander.Expand to indicate that
// a given expansion doesn't apply to the current template parameters. For
// example, expanding `"foo/${os=linux,mac}"` with a template parameter of "os"
// == "win", would return ErrSkipTemplate.
var ErrSkipTemplate = errors.New("package template does not apply to the current system")
var templateParm = regexp.MustCompile(`\${[^}]*}`)
// Expand applies package template expansion rules to the package template,
//
// If err == ErrSkipTemplate, that means that this template does not apply to
// this os/arch combination and should be skipped.
//
// The expansion rules are as follows:
// - "some text" will pass through unchanged
// - "${variable}" will directly substitute the given variable
// - "${variable=val1,val2}" will substitute the given variable, if its value
// matches one of the values in the list of values. If the current value
// does not match, this returns ErrSkipTemplate.
//
// Attempting to expand an unknown variable is an error.
// After expansion, any lingering '$' in the template is an error.
func (t Expander) Expand(template string) (pkg string, err error) {
return t.expandImpl(template, false)
}
// Validate returns an error if this template doesn't appear to be valid given
// the current Expander parameters.
//
// This will catch issues like malformed template parameters and unknown
// variables, and will replace all ${param=value} items with the first item in
// the value list, even if the current TemplateExpander value doesn't match.
//
// This is mostly used for validating user input when the correct values of
// Expander aren't known yet.
func (t Expander) Validate(template string) (pkg string, err error) {
return t.expandImpl(template, true)
}
func (t Expander) expandImpl(template string, alwaysFill bool) (pkg string, err error) {
skip := false
pkg = templateParm.ReplaceAllStringFunc(template, func(parm string) string {
// ${...}
contents := parm[2 : len(parm)-1]
varNameValues := strings.SplitN(contents, "=", 2)
if len(varNameValues) == 1 {
// ${varName}
if value, ok := t[varNameValues[0]]; ok {
return value
}
err = errors.Reason("unknown variable in ${%s}", contents).Err()
}
// ${varName=value,value}
ourValue, ok := t[varNameValues[0]]
if !ok {
err = errors.Reason("unknown variable %q", parm).Err()
return parm
}
for _, val := range strings.Split(varNameValues[1], ",") {
if val == ourValue || alwaysFill {
return ourValue
}
}
skip = true
return parm
})
if skip {
err = ErrSkipTemplate
}
if err == nil && strings.ContainsRune(pkg, '$') {
err = errors.Reason("unable to process some variables in %q", template).Err()
}
return
}
// Platform contains the parameters for a "${platform}" template.
//
// The string value can be obtained by calling String().
// be parsed using ParsePlatform.
type Platform struct {
OS string
Arch string
}
// ParsePlatform parses a Platform from its string representation.
func ParsePlatform(v string) (Platform, error) {
parts := strings.Split(v, "-")
if len(parts) != 2 {
return Platform{}, errors.Reason("platform must be <os>-<arch>: %q", v).Err()
}
return Platform{parts[0], parts[1]}, nil
}
func (tp Platform) String() string {
return fmt.Sprintf("%s-%s", tp.OS, tp.Arch)
}
// Expander returns an Expander populated with tp's fields.
func (tp Platform) Expander() Expander {
return Expander{
"os": tp.OS,
"arch": tp.Arch,
"platform": tp.String(),
}
}
// DefaultExpander returns the default template expander.
//
// This has values populated for ${os}, ${arch} and ${platform}.
func DefaultExpander() Expander {
p := Platform{platform.CurrentOS(), platform.CurrentArchitecture()}
return p.Expander()
}