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