github.com/wangyougui/gf/v2@v2.6.5/text/gstr/gstr_case.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  //
     7  //   | Function                          | Result             |
     8  //   |-----------------------------------|--------------------|
     9  //   | CaseSnake(s)                      | any_kind_of_string |
    10  //   | CaseSnakeScreaming(s)             | ANY_KIND_OF_STRING |
    11  //   | CaseSnakeFirstUpper("RGBCodeMd5") | rgb_code_md5       |
    12  //   | CaseKebab(s)                      | any-kind-of-string |
    13  //   | CaseKebabScreaming(s)             | ANY-KIND-OF-STRING |
    14  //   | CaseDelimited(s, '.')             | any.kind.of.string |
    15  //   | CaseDelimitedScreaming(s, '.')    | ANY.KIND.OF.STRING |
    16  //   | CaseCamel(s)                      | AnyKindOfString    |
    17  //   | CaseCamelLower(s)                 | anyKindOfString    |
    18  
    19  package gstr
    20  
    21  import (
    22  	"regexp"
    23  	"strings"
    24  )
    25  
    26  // CaseType is the type for Case.
    27  type CaseType string
    28  
    29  // The case type constants.
    30  const (
    31  	Camel           CaseType = "Camel"
    32  	CamelLower      CaseType = "CamelLower"
    33  	Snake           CaseType = "Snake"
    34  	SnakeFirstUpper CaseType = "SnakeFirstUpper"
    35  	SnakeScreaming  CaseType = "SnakeScreaming"
    36  	Kebab           CaseType = "Kebab"
    37  	KebabScreaming  CaseType = "KebabScreaming"
    38  	Lower           CaseType = "Lower"
    39  )
    40  
    41  var (
    42  	numberSequence      = regexp.MustCompile(`([a-zA-Z]{0,1})(\d+)([a-zA-Z]{0,1})`)
    43  	firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
    44  	firstCamelCaseEnd   = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
    45  )
    46  
    47  // CaseTypeMatch matches the case type from string.
    48  func CaseTypeMatch(caseStr string) CaseType {
    49  	caseTypes := []CaseType{
    50  		Camel,
    51  		CamelLower,
    52  		Snake,
    53  		SnakeFirstUpper,
    54  		SnakeScreaming,
    55  		Kebab,
    56  		KebabScreaming,
    57  		Lower,
    58  	}
    59  
    60  	for _, caseType := range caseTypes {
    61  		if Equal(caseStr, string(caseType)) {
    62  			return caseType
    63  		}
    64  	}
    65  
    66  	return CaseType(caseStr)
    67  }
    68  
    69  // CaseConvert converts a string to the specified naming convention.
    70  // Use CaseTypeMatch to match the case type from string.
    71  func CaseConvert(s string, caseType CaseType) string {
    72  	if s == "" || caseType == "" {
    73  		return s
    74  	}
    75  
    76  	switch caseType {
    77  	case Camel:
    78  		return CaseCamel(s)
    79  
    80  	case CamelLower:
    81  		return CaseCamelLower(s)
    82  
    83  	case Kebab:
    84  		return CaseKebab(s)
    85  
    86  	case KebabScreaming:
    87  		return CaseKebabScreaming(s)
    88  
    89  	case Snake:
    90  		return CaseSnake(s)
    91  
    92  	case SnakeFirstUpper:
    93  		return CaseSnakeFirstUpper(s)
    94  
    95  	case SnakeScreaming:
    96  		return CaseSnakeScreaming(s)
    97  
    98  	case Lower:
    99  		return ToLower(s)
   100  
   101  	default:
   102  		return s
   103  	}
   104  }
   105  
   106  // CaseCamel converts a string to CamelCase.
   107  //
   108  // Example:
   109  // CaseCamel("any_kind_of_string") -> AnyKindOfString
   110  func CaseCamel(s string) string {
   111  	return toCamelInitCase(s, true)
   112  }
   113  
   114  // CaseCamelLower converts a string to lowerCamelCase.
   115  //
   116  // Example:
   117  // CaseCamelLower("any_kind_of_string") -> anyKindOfString
   118  func CaseCamelLower(s string) string {
   119  	if s == "" {
   120  		return s
   121  	}
   122  	if r := rune(s[0]); r >= 'A' && r <= 'Z' {
   123  		s = strings.ToLower(string(r)) + s[1:]
   124  	}
   125  	return toCamelInitCase(s, false)
   126  }
   127  
   128  // CaseSnake converts a string to snake_case.
   129  //
   130  // Example:
   131  // CaseSnake("AnyKindOfString") -> any_kind_of_string
   132  func CaseSnake(s string) string {
   133  	return CaseDelimited(s, '_')
   134  }
   135  
   136  // CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING.
   137  //
   138  // Example:
   139  // CaseSnakeScreaming("AnyKindOfString") -> ANY_KIND_OF_STRING
   140  func CaseSnakeScreaming(s string) string {
   141  	return CaseDelimitedScreaming(s, '_', true)
   142  }
   143  
   144  // CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5".
   145  // TODO for efficiency should change regexp to traversing string in future.
   146  //
   147  // Example:
   148  // CaseSnakeFirstUpper("RGBCodeMd5") -> rgb_code_md5
   149  func CaseSnakeFirstUpper(word string, underscore ...string) string {
   150  	replace := "_"
   151  	if len(underscore) > 0 {
   152  		replace = underscore[0]
   153  	}
   154  
   155  	m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1)
   156  	if len(m) > 0 {
   157  		word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace)
   158  	}
   159  
   160  	for {
   161  		m = firstCamelCaseStart.FindAllStringSubmatch(word, 1)
   162  		if len(m) > 0 && m[0][1] != "" {
   163  			w := strings.ToLower(m[0][1])
   164  			w = w[:len(w)-1] + replace + string(w[len(w)-1])
   165  
   166  			word = strings.Replace(word, m[0][1], w, 1)
   167  		} else {
   168  			break
   169  		}
   170  	}
   171  
   172  	return TrimLeft(word, replace)
   173  }
   174  
   175  // CaseKebab converts a string to kebab-case.
   176  //
   177  // Example:
   178  // CaseKebab("AnyKindOfString") -> any-kind-of-string
   179  func CaseKebab(s string) string {
   180  	return CaseDelimited(s, '-')
   181  }
   182  
   183  // CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING.
   184  //
   185  // Example:
   186  // CaseKebab("AnyKindOfString") -> ANY-KIND-OF-STRING
   187  func CaseKebabScreaming(s string) string {
   188  	return CaseDelimitedScreaming(s, '-', true)
   189  }
   190  
   191  // CaseDelimited converts a string to snake.case.delimited.
   192  //
   193  // Example:
   194  // CaseDelimited("AnyKindOfString", '.') -> any.kind.of.string
   195  func CaseDelimited(s string, del byte) string {
   196  	return CaseDelimitedScreaming(s, del, false)
   197  }
   198  
   199  // CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case.
   200  //
   201  // Example:
   202  // CaseDelimitedScreaming("AnyKindOfString", '.') -> ANY.KIND.OF.STRING
   203  func CaseDelimitedScreaming(s string, del uint8, screaming bool) string {
   204  	s = addWordBoundariesToNumbers(s)
   205  	s = strings.Trim(s, " ")
   206  	n := ""
   207  	for i, v := range s {
   208  		// treat acronyms as words, eg for JSONData -> JSON is a whole word
   209  		nextCaseIsChanged := false
   210  		if i+1 < len(s) {
   211  			next := s[i+1]
   212  			if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
   213  				nextCaseIsChanged = true
   214  			}
   215  		}
   216  
   217  		if i > 0 && n[len(n)-1] != del && nextCaseIsChanged {
   218  			// add underscore if next letter case type is changed
   219  			if v >= 'A' && v <= 'Z' {
   220  				n += string(del) + string(v)
   221  			} else if v >= 'a' && v <= 'z' {
   222  				n += string(v) + string(del)
   223  			}
   224  		} else if v == ' ' || v == '_' || v == '-' || v == '.' {
   225  			// replace spaces/underscores with delimiters
   226  			n += string(del)
   227  		} else {
   228  			n = n + string(v)
   229  		}
   230  	}
   231  
   232  	if screaming {
   233  		n = strings.ToUpper(n)
   234  	} else {
   235  		n = strings.ToLower(n)
   236  	}
   237  	return n
   238  }
   239  
   240  func addWordBoundariesToNumbers(s string) string {
   241  	r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
   242  		var result []byte
   243  		match := numberSequence.FindSubmatch(bytes)
   244  		if len(match[1]) > 0 {
   245  			result = append(result, match[1]...)
   246  			result = append(result, []byte(" ")...)
   247  		}
   248  		result = append(result, match[2]...)
   249  		if len(match[3]) > 0 {
   250  			result = append(result, []byte(" ")...)
   251  			result = append(result, match[3]...)
   252  		}
   253  		return result
   254  	})
   255  	return string(r)
   256  }
   257  
   258  // Converts a string to CamelCase
   259  func toCamelInitCase(s string, initCase bool) string {
   260  	s = addWordBoundariesToNumbers(s)
   261  	s = strings.Trim(s, " ")
   262  	n := ""
   263  	capNext := initCase
   264  	for _, v := range s {
   265  		if v >= 'A' && v <= 'Z' {
   266  			n += string(v)
   267  		}
   268  		if v >= '0' && v <= '9' {
   269  			n += string(v)
   270  		}
   271  		if v >= 'a' && v <= 'z' {
   272  			if capNext {
   273  				n += strings.ToUpper(string(v))
   274  			} else {
   275  				n += string(v)
   276  			}
   277  		}
   278  		if v == '_' || v == ' ' || v == '-' || v == '.' {
   279  			capNext = true
   280  		} else {
   281  			capNext = false
   282  		}
   283  	}
   284  	return n
   285  }