github.com/searKing/golang/go@v1.2.117/strings/format.go (about) 1 // Copyright 2020 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package strings 6 7 import ( 8 "strings" 9 "unicode" 10 "unicode/utf8" 11 12 unicode_ "github.com/searKing/golang/go/unicode" 13 ) 14 15 // see https://en.wikipedia.org/wiki/Camel_case 16 // Camel case (stylized as camelCase; also known as camel caps or more formally as medial capitals) is the practice of 17 // writing phrases such that each word or abbreviation in the middle of the phrase begins with a capital letter, 18 // with no intervening spaces or punctuation. 19 // Common examples include "iPhone" and "eBay". 20 21 var ( 22 PascalCase = UpperCamelCase 23 CapitalizedWordsCase = UpperCamelCase 24 CapWordsCase = UpperCamelCase 25 CapitalizedWords = UpperCamelCase 26 27 // SentenceCase is a mixed-case style in which the first word of the sentence is capitalised, 28 // as well as proper nouns and other words as required by a more specific rule. 29 // This is generally equivalent to the baseline universal standard of formal English orthography. 30 // https://en.wikipedia.org/wiki/Letter_case#Sentence_Case 31 // "The quick brown fox jumps over the lazy dog" 32 SentenceCase = strings.Title 33 34 // TitleCase capitalises all words but retains the spaces between them 35 // https://en.wikipedia.org/wiki/Letter_case#Title_Case 36 // "The Quick Brown Fox Jumps over the Lazy Dog" 37 TitleCase = strings.ToTitle 38 39 // AllCapsCase is an unicase style with capital letters only. 40 // https://en.wikipedia.org/wiki/Letter_case#All_caps 41 // "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG" 42 AllCapsCase = strings.ToUpper 43 AllUpperCase = AllCapsCase 44 45 // AllLowercase is an unicase style with no capital letters. 46 // https://en.wikipedia.org/wiki/Letter_case#All_lowercase 47 // "the quick brown fox jumps over the lazy dog" 48 AllLowercase = strings.ToLower 49 ) 50 51 var ( 52 DromedaryCase = LowerCamelCase 53 // Some people and organizations, notably Microsoft, use the term camel case only for lower camel case. 54 // Pascal case means only upper camel case. 55 CamelCase = LowerCamelCase 56 57 // MixedCase for lower camel case in Python 58 MixedCase = LowerCamelCase 59 ) 60 61 var ( 62 // lowercase 63 LowerCase = strings.ToLower 64 ) 65 66 // UpperCamelCase returns the CamelCased name by initial uppercase letter. 67 // If there is an interior split rune such as an underscore followed by a lower case letter, 68 // drop the underscore and convert the letter to upper case. 69 // There is a remote possibility of this rewrite causing a name collision, 70 // but it's so remote we're prepared to pretend it's nonexistent - since the 71 // C++ JoinGenerator lowercases names, it's extremely unlikely to have two fields 72 // with different capitalizations. 73 // In short, _my_field_name_2 becomes XMyFieldName_2. 74 // "TheQuickBrownFoxJumpsOverTheLazyDog" 75 func UpperCamelCase(s string, seps ...rune) string { 76 return TransformCase(s, func(r string) string { 77 r = withDefault(leadingString, strings.ToLower)(r) 78 return ToUpperLeading(r) 79 }, seps...) 80 } 81 82 // LowerCamelCase returns the CamelCased name by lowercase uppercase letter. 83 // In short, _my_field_name_2 becomes xMyFieldName_2. 84 // "theQuickBrownFoxJumpsOverTheLazyDog" 85 func LowerCamelCase(s string, seps ...rune) string { 86 s = UpperCamelCase(s, seps...) 87 sr, s := ExtractFirstRune(s) 88 return ToLowerLeading(string(sr)) + s 89 } 90 91 // SnakeCase returns the SnakeCased name. 92 // In short, _my_field_name_2 becomes x_my_field_name_2. 93 // seps will append '_' if len(seps) == 0 94 // "the_quick_brown_fox_jumps_over_the_lazy_dog" 95 func SnakeCase(s string, seps ...rune) string { 96 if len(seps) == 0 { 97 seps = append(seps, '_') 98 } 99 return TransformCase(s, JoinGenerator("_", withDefault(leadingString, strings.ToLower)), seps...) 100 } 101 102 // DarwinCase returns the DarwinCased name. 103 // Darwin case uses underscores between words with initial uppercase letters, as in "Sample_Type" 104 // In short, _my_field_name_2 becomes X_My_Field_Name_2. 105 // see https://en.wikipedia.org/wiki/Camel_case 106 func DarwinCase(s string, seps ...rune) string { 107 return TransformCase(s, JoinGenerator("_", withDefault(leadingString, func(s string) string { 108 return strings.Title(strings.ToLower(s)) 109 })), seps...) 110 } 111 112 // KebabCase returns the KebabCased name. 113 // In short, _my_field_name_2 becomes x-my-field-name-2. 114 // "the-quick-brown-fox-jumps-over-the-lazy-dog" 115 func KebabCase(s string, seps ...rune) string { 116 return TransformCase(s, JoinGenerator("-", withDefault(leadingString, strings.ToLower)), seps...) 117 } 118 119 // DotCase returns the KebabCased name. 120 // In short, _my_field_name_2 becomes x.my.field.name.2. 121 func DotCase(s string, seps ...rune) string { 122 return TransformCase(s, JoinGenerator(".", withDefault(leadingString, strings.ToLower)), seps...) 123 } 124 125 // Studly caps is a form of text notation in which the capitalization of letters varies by some pattern, or arbitrarily, 126 // usually also omitting spaces between words and often omitting some letters, for example, StUdLyCaPs or STuDLyCaPS. 127 // Such patterns are identified by many users, ambiguously, as camel case. 128 // The typical alternative is to just replace spaces with underscores (as in snake case). 129 // Messages may be hidden in the capital and lower-case letters such as "ShoEboX" which spells 130 // "SEX" in capitals and "hobo" in lower-case. 131 // https://en.wikipedia.org/wiki/Studly_caps 132 // "tHeqUicKBrOWnFoXJUmpsoVeRThElAzydOG" 133 // "THiS iS aN eXCePTioNaLLy eLiTe SeNTeNCe" 134 func StudlyCapsCase(upperCase unicode.SpecialCase, s string) string { 135 return strings.ToLowerSpecial(upperCase, s) 136 } 137 138 // "thEqUIckbrOwnfOxjUmpsOvErthElAzydOg" 139 func StudlyCapsVowelUpperCase(s string) string { 140 return strings.ToLowerSpecial(unicode_.VowelCase(nil, func(r rune) rune { 141 return unicode.ToUpper(r) 142 }, nil), s) 143 } 144 145 // "THeQuiCKBRoWNFoXJuMPSoVeRTHeLaZYDoG" 146 func StudlyCapsConsonantUpperCase(s string) string { 147 return strings.ToLowerSpecial(unicode_.ConsonantCase(nil, func(r rune) rune { 148 return unicode.ToUpper(r) 149 }, nil), s) 150 } 151 152 // lower_case_with_underscores 153 func LowerCaseWithUnderscores(s string, seps ...rune) string { 154 return func(s_ string) string { 155 return strings.ToLower(SnakeCase(s_, seps...)) 156 }(s) 157 } 158 159 func ExtractFirstRune(s string) (rune, string) { 160 if s == "" { 161 return -1, s 162 } 163 // Extract first rune from each string. 164 var sr rune 165 if s[0] < utf8.RuneSelf { 166 sr, s = rune(s[0]), s[1:] 167 } else { 168 r, size := utf8.DecodeRuneInString(s) 169 sr, s = r, s[size:] 170 } 171 return sr, s 172 } 173 174 // UpperCamelCaseSlice is like UpperCamelCase, but the argument is a slice of strings to 175 // be joined with "_". 176 func UpperCamelCaseSlice(elem ...string) string { return UpperCamelCase(strings.Join(elem, "_"), '_') } 177 178 // LowerCamelCaseSlice is like LowerCamelCase, but the argument is a slice of strings to 179 // be joined with "_". 180 func LowerCamelCaseSlice(elem ...string) string { return LowerCamelCase(strings.Join(elem, "_"), '_') } 181 182 // DottedSlice turns a sliced name into a dotted name. 183 func DottedSlice(elem ...string) string { return strings.Join(elem, ".") } 184 185 const ( 186 leadingString = "X" 187 ) 188 189 // TransformCase Splits and apply map on every splits 190 func TransformCase(s string, join func(r string) string, seps ...rune) string { 191 var out strings.Builder 192 for _, sub := range splits(s, seps...) { 193 out.WriteString(join(sub)) 194 } 195 return out.String() 196 } 197 198 // split s into sub strings 199 // meet seps, split 200 // meet Capital rune, split 201 // if s is leading with seps, filled with "" at first 202 func splits(s string, seps ...rune) []string { 203 if s == "" { 204 return nil 205 } 206 var splited []string 207 sr, s_ := ExtractFirstRune(s) 208 if strings.ContainsRune(string(seps), sr) { 209 // Need a non sep letter; drop the split rune, such as '_'. 210 splited = append(splited, "") 211 s = s_ 212 } 213 214 var ele strings.Builder 215 // Invariant: if the next letter is lower case, it must be converted 216 // to upper case. 217 // That is, we process a word at a time, where words are marked by _ or 218 // upper case letter. Digits are treated as words. 219 for s != "" { 220 ele.Reset() 221 sr, s = ExtractFirstRune(s) 222 if strings.ContainsRune(string(seps), sr) && s != "" { 223 for s != "" { 224 sr, s = ExtractFirstRune(s) 225 if strings.ContainsRune(string(seps), sr) { 226 // ignore seps that follows 227 continue 228 } 229 break 230 } 231 // EOF 232 if strings.ContainsRune(string(seps), sr) { 233 break 234 } 235 } 236 // Assume we have a letter now - if not, it's a bogus identifier. 237 // The next word is a sequence of characters that must start upper case. 238 ele.WriteRune(sr) 239 // Accept lower case sequence that follows. 240 for s != "" { 241 sr, s_ = ExtractFirstRune(s) 242 if !strings.ContainsRune(string(seps), sr) && unicode.IsLower(sr) { 243 s = s_ 244 ele.WriteRune(sr) 245 continue 246 } 247 break 248 } 249 splited = append(splited, ele.String()) 250 } 251 return splited 252 } 253 254 func withDefault(def string, f func(s string) string) func(s string) string { 255 return func(s string) string { 256 if s == "" { 257 s = def 258 } 259 return f(s) 260 } 261 }