xref: /aosp_15_r20/cts/tests/mediapc/requirements/templatefns.go (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1*b7c941bbSAndroid Build Coastguard Worker// Copyright (C) 2024 The Android Open Source Project
2*b7c941bbSAndroid Build Coastguard Worker//
3*b7c941bbSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*b7c941bbSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*b7c941bbSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*b7c941bbSAndroid Build Coastguard Worker//
7*b7c941bbSAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*b7c941bbSAndroid Build Coastguard Worker//
9*b7c941bbSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*b7c941bbSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*b7c941bbSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*b7c941bbSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*b7c941bbSAndroid Build Coastguard Worker// limitations under the License.
14*b7c941bbSAndroid Build Coastguard Worker
15*b7c941bbSAndroid Build Coastguard Worker// Package templatefns contains functions that are made available in genreqsrc templates.
16*b7c941bbSAndroid Build Coastguard Workerpackage templatefns
17*b7c941bbSAndroid Build Coastguard Worker
18*b7c941bbSAndroid Build Coastguard Workerimport (
19*b7c941bbSAndroid Build Coastguard Worker	"fmt"
20*b7c941bbSAndroid Build Coastguard Worker	"strings"
21*b7c941bbSAndroid Build Coastguard Worker	"text/template"
22*b7c941bbSAndroid Build Coastguard Worker	"unicode"
23*b7c941bbSAndroid Build Coastguard Worker
24*b7c941bbSAndroid Build Coastguard Worker	pb "cts/test/mediapc/requirements/requirements_go_proto"
25*b7c941bbSAndroid Build Coastguard Worker)
26*b7c941bbSAndroid Build Coastguard Worker
27*b7c941bbSAndroid Build Coastguard Worker// Funcs returns a mapping from names of template helper functions to the
28*b7c941bbSAndroid Build Coastguard Worker// functions themselves.
29*b7c941bbSAndroid Build Coastguard Workerfunc Funcs() template.FuncMap {
30*b7c941bbSAndroid Build Coastguard Worker	// These function are made available in templates by calling their key values, e.g. {{SnakeCase "HelloWorld"}}.
31*b7c941bbSAndroid Build Coastguard Worker	return template.FuncMap{
32*b7c941bbSAndroid Build Coastguard Worker		// go/keep-sorted start
33*b7c941bbSAndroid Build Coastguard Worker		"Dict":             dict,
34*b7c941bbSAndroid Build Coastguard Worker		"HasConfigVariant": HasConfigVariant,
35*b7c941bbSAndroid Build Coastguard Worker		"KebabCase":        kebabCase,
36*b7c941bbSAndroid Build Coastguard Worker		"LowerCamelCase":   lowerCamelCase,
37*b7c941bbSAndroid Build Coastguard Worker		"LowerCase":        strings.ToLower,
38*b7c941bbSAndroid Build Coastguard Worker		"SafeReqID":        safeReqID,
39*b7c941bbSAndroid Build Coastguard Worker		"SafeTestConfigID": safeTestConfigID,
40*b7c941bbSAndroid Build Coastguard Worker		"SnakeCase":        snakeCase,
41*b7c941bbSAndroid Build Coastguard Worker		"TitleCase":        titleCase,
42*b7c941bbSAndroid Build Coastguard Worker		"UpperCamelCase":   upperCamelCase,
43*b7c941bbSAndroid Build Coastguard Worker		"UpperCase":        strings.ToUpper,
44*b7c941bbSAndroid Build Coastguard Worker		// go/keep-sorted end
45*b7c941bbSAndroid Build Coastguard Worker	}
46*b7c941bbSAndroid Build Coastguard Worker}
47*b7c941bbSAndroid Build Coastguard Worker
48*b7c941bbSAndroid Build Coastguard Worker// isDelimiter checks if a rune is some kind of whitespace, '_' or '-'.
49*b7c941bbSAndroid Build Coastguard Worker// helper function
50*b7c941bbSAndroid Build Coastguard Workerfunc isDelimiter(r rune) bool {
51*b7c941bbSAndroid Build Coastguard Worker	return r == '-' || r == '_' || unicode.IsSpace(r)
52*b7c941bbSAndroid Build Coastguard Worker}
53*b7c941bbSAndroid Build Coastguard Worker
54*b7c941bbSAndroid Build Coastguard Workerfunc shouldWriteDelimiter(r, prev, next rune) bool {
55*b7c941bbSAndroid Build Coastguard Worker	if isDelimiter(prev) {
56*b7c941bbSAndroid Build Coastguard Worker		// Don't delimit if we just delimited
57*b7c941bbSAndroid Build Coastguard Worker		return false
58*b7c941bbSAndroid Build Coastguard Worker	}
59*b7c941bbSAndroid Build Coastguard Worker	// Delimit before new uppercase letters and after acronyms
60*b7c941bbSAndroid Build Coastguard Worker	caseDelimit := unicode.IsUpper(r) && (unicode.IsLower(prev) || unicode.IsLower(next))
61*b7c941bbSAndroid Build Coastguard Worker	// Delimit after digits
62*b7c941bbSAndroid Build Coastguard Worker	digitDelimit := !unicode.IsDigit(r) && unicode.IsDigit(prev)
63*b7c941bbSAndroid Build Coastguard Worker	return isDelimiter(r) || caseDelimit || digitDelimit
64*b7c941bbSAndroid Build Coastguard Worker}
65*b7c941bbSAndroid Build Coastguard Worker
66*b7c941bbSAndroid Build Coastguard Worker// titleCase converts a string into Title Case.
67*b7c941bbSAndroid Build Coastguard Workerfunc titleCase(s string) string {
68*b7c941bbSAndroid Build Coastguard Worker	runes := []rune(s)
69*b7c941bbSAndroid Build Coastguard Worker	var out strings.Builder
70*b7c941bbSAndroid Build Coastguard Worker	for i, r := range runes {
71*b7c941bbSAndroid Build Coastguard Worker		prev, next := ' ', ' '
72*b7c941bbSAndroid Build Coastguard Worker		if i > 0 {
73*b7c941bbSAndroid Build Coastguard Worker			prev = runes[i-1]
74*b7c941bbSAndroid Build Coastguard Worker			if i+1 < len(runes) {
75*b7c941bbSAndroid Build Coastguard Worker				next = runes[i+1]
76*b7c941bbSAndroid Build Coastguard Worker			}
77*b7c941bbSAndroid Build Coastguard Worker		}
78*b7c941bbSAndroid Build Coastguard Worker
79*b7c941bbSAndroid Build Coastguard Worker		if shouldWriteDelimiter(r, prev, next) {
80*b7c941bbSAndroid Build Coastguard Worker			out.WriteRune(' ')
81*b7c941bbSAndroid Build Coastguard Worker		}
82*b7c941bbSAndroid Build Coastguard Worker
83*b7c941bbSAndroid Build Coastguard Worker		if !isDelimiter(r) {
84*b7c941bbSAndroid Build Coastguard Worker			// Output all non-delimiters unchanged
85*b7c941bbSAndroid Build Coastguard Worker			out.WriteRune(r)
86*b7c941bbSAndroid Build Coastguard Worker		}
87*b7c941bbSAndroid Build Coastguard Worker	}
88*b7c941bbSAndroid Build Coastguard Worker	return strings.Title(out.String())
89*b7c941bbSAndroid Build Coastguard Worker}
90*b7c941bbSAndroid Build Coastguard Worker
91*b7c941bbSAndroid Build Coastguard Worker// snakeCase converts a string into snake_case.
92*b7c941bbSAndroid Build Coastguard Workerfunc snakeCase(s string) string {
93*b7c941bbSAndroid Build Coastguard Worker	return strings.ReplaceAll(strings.ToLower(titleCase(s)), " ", "_")
94*b7c941bbSAndroid Build Coastguard Worker}
95*b7c941bbSAndroid Build Coastguard Worker
96*b7c941bbSAndroid Build Coastguard Worker// kebabCase converts a string into kebab-case.
97*b7c941bbSAndroid Build Coastguard Workerfunc kebabCase(s string) string {
98*b7c941bbSAndroid Build Coastguard Worker	return strings.ReplaceAll(strings.ToLower(titleCase(s)), " ", "-")
99*b7c941bbSAndroid Build Coastguard Worker}
100*b7c941bbSAndroid Build Coastguard Worker
101*b7c941bbSAndroid Build Coastguard Worker// upperCamelCase converts a string into UpperCamelCase.
102*b7c941bbSAndroid Build Coastguard Workerfunc upperCamelCase(s string) string {
103*b7c941bbSAndroid Build Coastguard Worker	return strings.ReplaceAll(titleCase(s), " ", "")
104*b7c941bbSAndroid Build Coastguard Worker}
105*b7c941bbSAndroid Build Coastguard Worker
106*b7c941bbSAndroid Build Coastguard Worker// lowerCamelCase converts a string into lowerCamelCase.
107*b7c941bbSAndroid Build Coastguard Workerfunc lowerCamelCase(s string) string {
108*b7c941bbSAndroid Build Coastguard Worker	if len(s) < 2 {
109*b7c941bbSAndroid Build Coastguard Worker		return strings.ToLower(s)
110*b7c941bbSAndroid Build Coastguard Worker	}
111*b7c941bbSAndroid Build Coastguard Worker	runes := []rune(upperCamelCase(s))
112*b7c941bbSAndroid Build Coastguard Worker	for i, r := range runes {
113*b7c941bbSAndroid Build Coastguard Worker		// Lowercase leading acronyms
114*b7c941bbSAndroid Build Coastguard Worker		if i > 1 && unicode.IsLower(r) {
115*b7c941bbSAndroid Build Coastguard Worker			return strings.ToLower(string(runes[:i-1])) + string(runes[i-1:])
116*b7c941bbSAndroid Build Coastguard Worker		}
117*b7c941bbSAndroid Build Coastguard Worker	}
118*b7c941bbSAndroid Build Coastguard Worker	return string(unicode.ToLower(runes[0])) + string(runes[1:])
119*b7c941bbSAndroid Build Coastguard Worker}
120*b7c941bbSAndroid Build Coastguard Worker
121*b7c941bbSAndroid Build Coastguard Worker// safeReqID converts a Media Performance Class (MPC) requirement id to a variable name safe string.
122*b7c941bbSAndroid Build Coastguard Workerfunc safeReqID(s string) string {
123*b7c941bbSAndroid Build Coastguard Worker	f := func(a, b, c string) string {
124*b7c941bbSAndroid Build Coastguard Worker		return strings.Replace(a, b, c, -1)
125*b7c941bbSAndroid Build Coastguard Worker	}
126*b7c941bbSAndroid Build Coastguard Worker	return "r" + strings.ToLower(f(f(f(s, "/", "__"), ".", "_"), "-", "_"))
127*b7c941bbSAndroid Build Coastguard Worker}
128*b7c941bbSAndroid Build Coastguard Worker
129*b7c941bbSAndroid Build Coastguard Worker// safeTestConfigID converts a group name to a variable name safe string to append onto a requirement id.
130*b7c941bbSAndroid Build Coastguard Workerfunc safeTestConfigID(s string) string {
131*b7c941bbSAndroid Build Coastguard Worker	if s == "" {
132*b7c941bbSAndroid Build Coastguard Worker		return ""
133*b7c941bbSAndroid Build Coastguard Worker	}
134*b7c941bbSAndroid Build Coastguard Worker	return "__" + snakeCase(s)
135*b7c941bbSAndroid Build Coastguard Worker}
136*b7c941bbSAndroid Build Coastguard Worker
137*b7c941bbSAndroid Build Coastguard Worker// dict converts a list of key-value pairs into a map.
138*b7c941bbSAndroid Build Coastguard Worker// If there is an odd number of values, the last value is nil.
139*b7c941bbSAndroid Build Coastguard Worker// The last key is preserved so in the template it can be referenced like {{$myDict.key}}.
140*b7c941bbSAndroid Build Coastguard Workerfunc dict(v ...any) map[string]any {
141*b7c941bbSAndroid Build Coastguard Worker	dict := map[string]any{}
142*b7c941bbSAndroid Build Coastguard Worker	lenv := len(v)
143*b7c941bbSAndroid Build Coastguard Worker	for i := 0; i < lenv; i += 2 {
144*b7c941bbSAndroid Build Coastguard Worker		key := toString(v[i])
145*b7c941bbSAndroid Build Coastguard Worker		if i+1 >= lenv {
146*b7c941bbSAndroid Build Coastguard Worker			dict[key] = nil
147*b7c941bbSAndroid Build Coastguard Worker			continue
148*b7c941bbSAndroid Build Coastguard Worker		}
149*b7c941bbSAndroid Build Coastguard Worker		dict[key] = v[i+1]
150*b7c941bbSAndroid Build Coastguard Worker	}
151*b7c941bbSAndroid Build Coastguard Worker	return dict
152*b7c941bbSAndroid Build Coastguard Worker}
153*b7c941bbSAndroid Build Coastguard Worker
154*b7c941bbSAndroid Build Coastguard Worker// HasConfigVariant checks if a requirement has a spec for a given test config and variant.
155*b7c941bbSAndroid Build Coastguard Workerfunc HasConfigVariant(r *pb.Requirement, configID string, variantID string) bool {
156*b7c941bbSAndroid Build Coastguard Worker	for _, spec := range r.GetSpecs() {
157*b7c941bbSAndroid Build Coastguard Worker		if configID == spec.GetTestConfigId() {
158*b7c941bbSAndroid Build Coastguard Worker			_, ok := spec.GetVariantSpecs()[variantID]
159*b7c941bbSAndroid Build Coastguard Worker			if ok {
160*b7c941bbSAndroid Build Coastguard Worker				return true
161*b7c941bbSAndroid Build Coastguard Worker			}
162*b7c941bbSAndroid Build Coastguard Worker		}
163*b7c941bbSAndroid Build Coastguard Worker	}
164*b7c941bbSAndroid Build Coastguard Worker	return false
165*b7c941bbSAndroid Build Coastguard Worker}
166*b7c941bbSAndroid Build Coastguard Worker
167*b7c941bbSAndroid Build Coastguard Worker// toString converts a value to a string.
168*b7c941bbSAndroid Build Coastguard Workerfunc toString(v any) string {
169*b7c941bbSAndroid Build Coastguard Worker	switch v := v.(type) {
170*b7c941bbSAndroid Build Coastguard Worker	case string:
171*b7c941bbSAndroid Build Coastguard Worker		return v
172*b7c941bbSAndroid Build Coastguard Worker	case []byte:
173*b7c941bbSAndroid Build Coastguard Worker		return string(v)
174*b7c941bbSAndroid Build Coastguard Worker	case error:
175*b7c941bbSAndroid Build Coastguard Worker		return v.Error()
176*b7c941bbSAndroid Build Coastguard Worker	case fmt.Stringer:
177*b7c941bbSAndroid Build Coastguard Worker		return v.String()
178*b7c941bbSAndroid Build Coastguard Worker	default:
179*b7c941bbSAndroid Build Coastguard Worker		return fmt.Sprintf("%v", v)
180*b7c941bbSAndroid Build Coastguard Worker	}
181*b7c941bbSAndroid Build Coastguard Worker}
182