github.com/nikron/prototool@v1.3.0/internal/strs/strs.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  // Package strs contains common string manipulation functionality.
    22  //
    23  // This functionality is not really centralized anywhere in Golang OSS world,
    24  // and there are some specific requirements we have. This is used mostly
    25  // in the lint package.
    26  package strs
    27  
    28  import (
    29  	"sort"
    30  	"strings"
    31  	"unicode"
    32  	"unicode/utf8"
    33  )
    34  
    35  // IsCapitalized returns true if is not empty and the first letter is
    36  // an uppercase character.
    37  func IsCapitalized(s string) bool {
    38  	if s == "" {
    39  		return false
    40  	}
    41  	r, _ := utf8.DecodeRuneInString(s)
    42  	return isUpper(r)
    43  }
    44  
    45  // IsCamelCase returns false if s is empty or contains any character that is
    46  // not between 'A' and 'Z', 'a' and 'z', '0' and '9', or in extraRunes.
    47  // It does not care about lowercase or uppercase.
    48  func IsCamelCase(s string) bool {
    49  	if s == "" {
    50  		return false
    51  	}
    52  	for _, c := range s {
    53  		if !(isLetter(c) || isDigit(c)) {
    54  			return false
    55  		}
    56  	}
    57  	return true
    58  }
    59  
    60  // IsLowerSnakeCase returns true if s only contains lowercase letters,
    61  // digits, and/or underscores. s MUST NOT begin or end with an underscore.
    62  func IsLowerSnakeCase(s string) bool {
    63  	if s == "" || s[0] == '_' || s[len(s)-1] == '_' {
    64  		return false
    65  	}
    66  	for _, r := range s {
    67  		if !(isLower(r) || isDigit(r) || r == '_') {
    68  			return false
    69  		}
    70  	}
    71  	return true
    72  }
    73  
    74  // IsUpperSnakeCase returns true if s only contains uppercase letters,
    75  // digits, and/or underscores. s MUST NOT begin or end with an underscore.
    76  func IsUpperSnakeCase(s string) bool {
    77  	if s == "" || s[0] == '_' || s[len(s)-1] == '_' {
    78  		return false
    79  	}
    80  	for _, r := range s {
    81  		if !(isUpper(r) || isDigit(r) || r == '_') {
    82  			return false
    83  		}
    84  	}
    85  	return true
    86  }
    87  
    88  // ToUpperSnakeCase converts s to UPPER_SNAKE_CASE.
    89  func ToUpperSnakeCase(s string) string {
    90  	return strings.ToUpper(toSnake(s))
    91  }
    92  
    93  // ToUpperCamelCase converts s to UpperCamelCase.
    94  //
    95  // We use this for files, so any delimiter (_, -, or space) is
    96  // used to denote word boundaries, but we trim spaces from the
    97  // beginning and end of the string first.
    98  //
    99  // If a letter is uppercase, it will stay uppercase regardless,
   100  // this is for cases of abbreviations.
   101  func ToUpperCamelCase(s string) string {
   102  	output := ""
   103  	var previous rune
   104  	for i, c := range strings.TrimSpace(s) {
   105  		if !isDelimiter(c) {
   106  			if i == 0 || isDelimiter(previous) || isUpper(c) {
   107  				output += string(unicode.ToUpper(c))
   108  			} else {
   109  				output += string(unicode.ToLower(c))
   110  			}
   111  		}
   112  		previous = c
   113  	}
   114  	return output
   115  }
   116  
   117  // DedupeSort returns s with no duplicates and no empty strings, sorted.
   118  // If modifier is not nil, modifier will be applied to each element in s.
   119  func DedupeSort(s []string, modifier func(string) string) []string {
   120  	m := make(map[string]struct{}, len(s))
   121  	o := make([]string, 0, len(m))
   122  	for _, e := range s {
   123  		if e == "" {
   124  			continue
   125  		}
   126  		key := e
   127  		if modifier != nil {
   128  			key = modifier(e)
   129  		}
   130  		if _, ok := m[key]; ok {
   131  			continue
   132  		}
   133  		m[key] = struct{}{}
   134  		o = append(o, key)
   135  	}
   136  	sort.Strings(o)
   137  	return o
   138  }
   139  
   140  // Intersection return the intersection between one and
   141  // two, sorted and dropping empty strings.
   142  func Intersection(one []string, two []string) []string {
   143  	m1 := make(map[string]struct{})
   144  	for _, e := range one {
   145  		if e == "" {
   146  			continue
   147  		}
   148  		m1[e] = struct{}{}
   149  	}
   150  	m2 := make(map[string]struct{})
   151  	for _, e := range two {
   152  		if e == "" {
   153  			continue
   154  		}
   155  		m2[e] = struct{}{}
   156  	}
   157  	for key := range m1 {
   158  		if _, ok := m2[key]; !ok {
   159  			delete(m1, key)
   160  		}
   161  	}
   162  	s := make([]string, 0, len(m1))
   163  	for key := range m1 {
   164  		s = append(s, key)
   165  	}
   166  	sort.Strings(s)
   167  	return s
   168  }
   169  
   170  // IsLowercase returns true if s is not empty and is all lowercase.
   171  func IsLowercase(s string) bool {
   172  	if s == "" {
   173  		return false
   174  	}
   175  	return strings.ToLower(s) == s
   176  }
   177  
   178  // IsUppercase returns true if s is not empty and is all uppercase.
   179  func IsUppercase(s string) bool {
   180  	if s == "" {
   181  		return false
   182  	}
   183  	return strings.ToUpper(s) == s
   184  }
   185  
   186  // toSnake converts s to snake_case.
   187  // It is assumed s has no spaces.
   188  func toSnake(s string) string {
   189  	output := ""
   190  	for i, c := range s {
   191  		if i > 0 && isUpper(c) && output[len(output)-1] != '_' && i < len(s)-1 && !isUpper(rune(s[i+1])) {
   192  			output += "_" + string(c)
   193  		} else {
   194  			output += string(c)
   195  		}
   196  	}
   197  	return output
   198  }
   199  
   200  func isLetter(r rune) bool {
   201  	return isUpper(r) || isLower(r)
   202  }
   203  
   204  func isLower(r rune) bool {
   205  	return 'a' <= r && r <= 'z'
   206  }
   207  
   208  func isUpper(r rune) bool {
   209  	return 'A' <= r && r <= 'Z'
   210  }
   211  
   212  func isDigit(r rune) bool {
   213  	return '0' <= r && r <= '9'
   214  }
   215  
   216  func isDelimiter(r rune) bool {
   217  	return r == '-' || r == '_' || r == ' ' || r == '\t' || r == '\n' || r == '\r'
   218  }