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  }