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