github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/linter/strs/strs.go (about)

     1  // Package strs contains common string manipulation functionality.
     2  package strs
     3  
     4  import (
     5  	"strings"
     6  	"unicode"
     7  	"unicode/utf8"
     8  )
     9  
    10  // IsUpperCamelCase returns true if s is not empty and is camel case with an initial capital.
    11  func IsUpperCamelCase(s string) bool {
    12  	if !isCapitalized(s) {
    13  		return false
    14  	}
    15  	return isCamelCase(s)
    16  }
    17  
    18  // IsLowerCamelCase returns true if s is not empty and is camel case without an initial capital.
    19  func IsLowerCamelCase(s string) bool {
    20  	if isCapitalized(s) {
    21  		return false
    22  	}
    23  	return isCamelCase(s)
    24  }
    25  
    26  // IsUpperSnakeCase returns true if s only contains uppercase letters,
    27  // digits, and/or underscores. s MUST NOT begin or end with an underscore.
    28  func IsUpperSnakeCase(s string) bool {
    29  	if s == "" || s[0] == '_' || s[len(s)-1] == '_' {
    30  		return false
    31  	}
    32  	for _, r := range s {
    33  		if !(isUpper(r) || isDigit(r) || r == '_') {
    34  			return false
    35  		}
    36  	}
    37  	return true
    38  }
    39  
    40  // IsLowerSnakeCase returns true if s only contains lowercase letters,
    41  // digits, and/or underscores. s MUST NOT begin or end with an underscore.
    42  func IsLowerSnakeCase(s string) bool {
    43  	if s == "" || s[0] == '_' || s[len(s)-1] == '_' {
    44  		return false
    45  	}
    46  	for _, r := range s {
    47  		if !(isLower(r) || isDigit(r) || r == '_') {
    48  			return false
    49  		}
    50  	}
    51  	return true
    52  }
    53  
    54  // isCapitalized returns true if is not empty and the first letter is
    55  // an uppercase character.
    56  func isCapitalized(s string) bool {
    57  	if s == "" {
    58  		return false
    59  	}
    60  	r, _ := utf8.DecodeRuneInString(s)
    61  	return isUpper(r)
    62  }
    63  
    64  // isCamelCase returns false if s is empty or contains any character that is
    65  // not between 'A' and 'Z', 'a' and 'z', '0' and '9', or in extraRunes.
    66  // It does not care about lowercase or uppercase.
    67  func isCamelCase(s string) bool {
    68  	if s == "" {
    69  		return false
    70  	}
    71  	for _, c := range s {
    72  		if !(isLetter(c) || isDigit(c)) {
    73  			return false
    74  		}
    75  	}
    76  	return true
    77  }
    78  
    79  // isSnake returns true if s only contains letters, digits, and/or underscores.
    80  // s MUST NOT begin or end with an underscore.
    81  func isSnake(s string) bool {
    82  	if s == "" || s[0] == '_' || s[len(s)-1] == '_' {
    83  		return false
    84  	}
    85  	for _, r := range s {
    86  		if !(isLetter(r) || isDigit(r) || r == '_') {
    87  			return false
    88  		}
    89  	}
    90  	return true
    91  }
    92  
    93  // HasAnyUpperCase returns true if s contains any of characters in the range A-Z.
    94  func HasAnyUpperCase(s string) bool {
    95  	for _, r := range s {
    96  		if isUpper(r) {
    97  			return true
    98  		}
    99  	}
   100  	return false
   101  }
   102  
   103  // ToUpperSnakeCase converts s to UPPER_SNAKE_CASE.
   104  func ToUpperSnakeCase(s string) string {
   105  	s = strings.ReplaceAll(s, ".", "_")
   106  	s = strings.ReplaceAll(s, "-", "_")
   107  	ws := SplitCamelCaseWord(s)
   108  	if ws == nil {
   109  		ws = []string{s}
   110  	}
   111  	return strings.ToUpper(
   112  		strings.Join(ws, "_"),
   113  	)
   114  }
   115  
   116  // ToLowerSnakeCase converts s to lower_snake_case.
   117  func ToLowerSnakeCase(s string) string {
   118  	s = strings.ReplaceAll(s, ".", "_")
   119  	s = strings.ReplaceAll(s, "-", "_")
   120  	ws := SplitCamelCaseWord(s)
   121  	if ws == nil {
   122  		ws = []string{s}
   123  	}
   124  	return strings.ToLower(
   125  		strings.Join(ws, "_"),
   126  	)
   127  }
   128  
   129  // ToUpperCamelCase converts s to UpperCamelCase.
   130  func ToUpperCamelCase(s string) string {
   131  	if IsUpperSnakeCase(s) {
   132  		s = strings.ToLower(s)
   133  	}
   134  
   135  	var output string
   136  	for _, w := range SplitSnakeCaseWord(s) {
   137  		output += strings.Title(w)
   138  	}
   139  	return output
   140  }
   141  
   142  // ToLowerCamelCase converts s to lowerCamelCase.
   143  func ToLowerCamelCase(s string) string {
   144  	var output string
   145  	for i, r := range ToUpperCamelCase(s) {
   146  		if i == 0 {
   147  			output += string(unicode.ToLower(r))
   148  		} else {
   149  			output += string(r)
   150  		}
   151  	}
   152  	return output
   153  }
   154  
   155  // toSnake converts s to snake_case.
   156  func toSnake(s string) string {
   157  	output := ""
   158  	s = strings.TrimSpace(s)
   159  	priorUpperN := 0
   160  	for _, c := range s {
   161  		if isLower(c) {
   162  			if 2 < priorUpperN {
   163  				output = output[:len(output)-1] + "_" + output[len(output)-1:]
   164  			}
   165  		} else if priorUpperN == 0 && len(output) > 0 {
   166  			output += "_"
   167  		}
   168  		output += string(c)
   169  		if isUpper(c) {
   170  			priorUpperN += 1
   171  		} else {
   172  			priorUpperN = 0
   173  		}
   174  	}
   175  	return output
   176  }
   177  
   178  // SplitCamelCaseWord splits a CamelCase word into its parts.
   179  //
   180  // If s is empty, returns nil.
   181  // If s is not CamelCase, returns nil.
   182  func SplitCamelCaseWord(s string) []string {
   183  	if s == "" {
   184  		return nil
   185  	}
   186  	s = strings.TrimSpace(s)
   187  	if !isCamelCase(s) {
   188  		return nil
   189  	}
   190  	return SplitSnakeCaseWord(toSnake(s))
   191  }
   192  
   193  // SplitSnakeCaseWord splits a snake_case word into its parts.
   194  //
   195  // If s is empty, returns nil.
   196  // If s is not snake_case, returns nil.
   197  func SplitSnakeCaseWord(s string) []string {
   198  	if s == "" {
   199  		return nil
   200  	}
   201  	s = strings.TrimSpace(s)
   202  	if !isSnake(s) {
   203  		return nil
   204  	}
   205  	return strings.Split(s, "_")
   206  }
   207  
   208  func isLetter(r rune) bool {
   209  	return isUpper(r) || isLower(r)
   210  }
   211  
   212  func isLower(r rune) bool {
   213  	return 'a' <= r && r <= 'z'
   214  }
   215  
   216  func isUpper(r rune) bool {
   217  	return 'A' <= r && r <= 'Z'
   218  }
   219  
   220  func isDigit(r rune) bool {
   221  	return '0' <= r && r <= '9'
   222  }