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 25// Funcs returns a mapping from names of template helper functions to the 26// functions themselves. 27func Funcs() template.FuncMap { 28 // These function are made available in templates by calling their key values, e.g. {{SnakeCase "HelloWorld"}}. 29 return template.FuncMap{ 30 // go/keep-sorted start 31 "Dict": dict, 32 "KebabCase": kebabCase, 33 "LowerCamelCase": lowerCamelCase, 34 "LowerCase": strings.ToLower, 35 "SafeReqID": safeReqID, 36 "SnakeCase": snakeCase, 37 "TitleCase": titleCase, 38 "UpperCamelCase": upperCamelCase, 39 "UpperCase": strings.ToUpper, 40 // go/keep-sorted end 41 } 42} 43 44// isDelimiter checks if a rune is some kind of whitespace, '_' or '-'. 45// helper function 46func isDelimiter(r rune) bool { 47 return r == '-' || r == '_' || unicode.IsSpace(r) 48} 49 50func shouldWriteDelimiter(r, prev, next rune) bool { 51 if isDelimiter(prev) { 52 // Don't delimit if we just delimited 53 return false 54 } 55 // Delimit before new uppercase letters and after acronyms 56 caseDelimit := unicode.IsUpper(r) && (unicode.IsLower(prev) || unicode.IsLower(next)) 57 // Delimit after digits 58 digitDelimit := !unicode.IsDigit(r) && unicode.IsDigit(prev) 59 return isDelimiter(r) || caseDelimit || digitDelimit 60} 61 62// titleCase converts a string into Title Case. 63func titleCase(s string) string { 64 runes := []rune(s) 65 var out strings.Builder 66 for i, r := range runes { 67 prev, next := ' ', ' ' 68 if i > 0 { 69 prev = runes[i-1] 70 if i+1 < len(runes) { 71 next = runes[i+1] 72 } 73 } 74 75 if shouldWriteDelimiter(r, prev, next) { 76 out.WriteRune(' ') 77 } 78 79 if !isDelimiter(r) { 80 // Output all non-delimiters unchanged 81 out.WriteRune(r) 82 } 83 } 84 return strings.Title(out.String()) 85} 86 87// snakeCase converts a string into snake_case. 88func snakeCase(s string) string { 89 return strings.ReplaceAll(strings.ToLower(titleCase(s)), " ", "_") 90} 91 92// kebabCase converts a string into kebab-case. 93func kebabCase(s string) string { 94 return strings.ReplaceAll(strings.ToLower(titleCase(s)), " ", "-") 95} 96 97// upperCamelCase converts a string into UpperCamelCase. 98func upperCamelCase(s string) string { 99 return strings.ReplaceAll(titleCase(s), " ", "") 100} 101 102// lowerCamelCase converts a string into lowerCamelCase. 103func lowerCamelCase(s string) string { 104 if len(s) < 2 { 105 return strings.ToLower(s) 106 } 107 runes := []rune(upperCamelCase(s)) 108 for i, r := range runes { 109 // Lowercase leading acronyms 110 if i > 1 && unicode.IsLower(r) { 111 return strings.ToLower(string(runes[:i-1])) + string(runes[i-1:]) 112 } 113 } 114 return string(unicode.ToLower(runes[0])) + string(runes[1:]) 115} 116 117// safeReqID converts a Media Performance Class (MPC) requirement id to a variable name safe string. 118func safeReqID(s string) string { 119 f := func(a, b, c string) string { 120 return strings.Replace(a, b, c, -1) 121 } 122 return "r" + strings.ToLower(f(f(f(s, "/", "__"), ".", "_"), "-", "_")) 123} 124 125// dict converts a list of key-value pairs into a map. 126// If there is an odd number of values, the last value is nil. 127// The last key is preserved so in the template it can be referenced like {{$myDict.key}}. 128func dict(v ...any) map[string]any { 129 dict := map[string]any{} 130 lenv := len(v) 131 for i := 0; i < lenv; i += 2 { 132 key := toString(v[i]) 133 if i+1 >= lenv { 134 dict[key] = nil 135 continue 136 } 137 dict[key] = v[i+1] 138 } 139 return dict 140} 141 142// toString converts a value to a string. 143func toString(v any) string { 144 switch v := v.(type) { 145 case string: 146 return v 147 case []byte: 148 return string(v) 149 case error: 150 return v.Error() 151 case fmt.Stringer: 152 return v.String() 153 default: 154 return fmt.Sprintf("%v", v) 155 } 156} 157