1 // Copyright 2018 The CUE Authors 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 strings implements simple functions to manipulate UTF-8 encoded 16 // strings.package strings. 17 // 18 // Some of the functions in this package are specifically intended as field 19 // constraints. For instance, MaxRunes as used in this CUE program 20 // 21 // import "strings" 22 // 23 // myString: strings.MaxRunes(5) 24 // 25 // specifies that the myString should be at most 5 code points. 26 package strings 27 28 import ( 29 "fmt" 30 "strings" 31 "unicode" 32 ) 33 34 // ByteAt reports the ith byte of the underlying strings or byte. 35 func ByteAt(b []byte, i int) (byte, error) { 36 if i < 0 || i >= len(b) { 37 return 0, fmt.Errorf("index out of range") 38 } 39 return b[i], nil 40 } 41 42 // ByteSlice reports the bytes of the underlying string data from the start 43 // index up to but not including the end index. 44 func ByteSlice(b []byte, start, end int) ([]byte, error) { 45 if start < 0 || start > end || end > len(b) { 46 return nil, fmt.Errorf("index out of range") 47 } 48 return b[start:end], nil 49 } 50 51 // Runes returns the Unicode code points of the given string. 52 func Runes(s string) []rune { 53 return []rune(s) 54 } 55 56 // MinRunes reports whether the number of runes (Unicode codepoints) in a string 57 // is at least a certain minimum. MinRunes can be used a field constraint to 58 // except all strings for which this property holds. 59 func MinRunes(s string, min int) bool { 60 // TODO: CUE strings cannot be invalid UTF-8. In case this changes, we need 61 // to use the following conversion to count properly: 62 // s, _ = unicodeenc.UTF8.NewDecoder().String(s) 63 return len([]rune(s)) >= min 64 } 65 66 // MaxRunes reports whether the number of runes (Unicode codepoints) in a string 67 // exceeds a certain maximum. MaxRunes can be used a field constraint to 68 // except all strings for which this property holds 69 func MaxRunes(s string, max int) bool { 70 // See comment in MinRunes implementation. 71 return len([]rune(s)) <= max 72 } 73 74 // ToTitle returns a copy of the string s with all Unicode letters that begin 75 // words mapped to their title case. 76 func ToTitle(s string) string { 77 // Use a closure here to remember state. 78 // Hackish but effective. Depends on Map scanning in order and calling 79 // the closure once per rune. 80 prev := ' ' 81 return strings.Map( 82 func(r rune) rune { 83 if unicode.IsSpace(prev) { 84 prev = r 85 return unicode.ToTitle(r) 86 } 87 prev = r 88 return r 89 }, 90 s) 91 } 92 93 // ToCamel returns a copy of the string s with all Unicode letters that begin 94 // words mapped to lower case. 95 func ToCamel(s string) string { 96 // Use a closure here to remember state. 97 // Hackish but effective. Depends on Map scanning in order and calling 98 // the closure once per rune. 99 prev := ' ' 100 return strings.Map( 101 func(r rune) rune { 102 if unicode.IsSpace(prev) { 103 prev = r 104 return unicode.ToLower(r) 105 } 106 prev = r 107 return r 108 }, 109 s) 110 } 111 112 // SliceRunes returns a string of the underlying string data from the start index 113 // up to but not including the end index. 114 func SliceRunes(s string, start, end int) (string, error) { 115 runes := []rune(s) 116 if start < 0 || start > end || end > len(runes) { 117 return "", fmt.Errorf("index out of range") 118 } 119 return string(runes[start:end]), nil 120 } 121