github.com/gogf/gf/v2@v2.7.4/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/gogf/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  // CaseCamel("anyKindOfString")    -> AnyKindOfString
   111  func CaseCamel(s string) string {
   112  	return toCamelInitCase(s, true)
   113  }
   114  
   115  // CaseCamelLower converts a string to lowerCamelCase.
   116  //
   117  // Example:
   118  // CaseCamelLower("any_kind_of_string") -> anyKindOfString
   119  // CaseCamelLower("AnyKindOfString")    -> anyKindOfString
   120  func CaseCamelLower(s string) string {
   121  	if s == "" {
   122  		return s
   123  	}
   124  	if r := rune(s[0]); r >= 'A' && r <= 'Z' {
   125  		s = strings.ToLower(string(r)) + s[1:]
   126  	}
   127  	return toCamelInitCase(s, false)
   128  }
   129  
   130  // CaseSnake converts a string to snake_case.
   131  //
   132  // Example:
   133  // CaseSnake("AnyKindOfString") -> any_kind_of_string
   134  func CaseSnake(s string) string {
   135  	return CaseDelimited(s, '_')
   136  }
   137  
   138  // CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING.
   139  //
   140  // Example:
   141  // CaseSnakeScreaming("AnyKindOfString") -> ANY_KIND_OF_STRING
   142  func CaseSnakeScreaming(s string) string {
   143  	return CaseDelimitedScreaming(s, '_', true)
   144  }
   145  
   146  // CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5".
   147  // TODO for efficiency should change regexp to traversing string in future.
   148  //
   149  // Example:
   150  // CaseSnakeFirstUpper("RGBCodeMd5") -> rgb_code_md5
   151  func CaseSnakeFirstUpper(word string, underscore ...string) string {
   152  	replace := "_"
   153  	if len(underscore) > 0 {
   154  		replace = underscore[0]
   155  	}
   156  
   157  	m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1)
   158  	if len(m) > 0 {
   159  		word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace)
   160  	}
   161  
   162  	for {
   163  		m = firstCamelCaseStart.FindAllStringSubmatch(word, 1)
   164  		if len(m) > 0 && m[0][1] != "" {
   165  			w := strings.ToLower(m[0][1])
   166  			w = w[:len(w)-1] + replace + string(w[len(w)-1])
   167  
   168  			word = strings.Replace(word, m[0][1], w, 1)
   169  		} else {
   170  			break
   171  		}
   172  	}
   173  
   174  	return TrimLeft(word, replace)
   175  }
   176  
   177  // CaseKebab converts a string to kebab-case.
   178  //
   179  // Example:
   180  // CaseKebab("AnyKindOfString") -> any-kind-of-string
   181  func CaseKebab(s string) string {
   182  	return CaseDelimited(s, '-')
   183  }
   184  
   185  // CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING.
   186  //
   187  // Example:
   188  // CaseKebab("AnyKindOfString") -> ANY-KIND-OF-STRING
   189  func CaseKebabScreaming(s string) string {
   190  	return CaseDelimitedScreaming(s, '-', true)
   191  }
   192  
   193  // CaseDelimited converts a string to snake.case.delimited.
   194  //
   195  // Example:
   196  // CaseDelimited("AnyKindOfString", '.') -> any.kind.of.string
   197  func CaseDelimited(s string, del byte) string {
   198  	return CaseDelimitedScreaming(s, del, false)
   199  }
   200  
   201  // CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case.
   202  //
   203  // Example:
   204  // CaseDelimitedScreaming("AnyKindOfString", '.') -> ANY.KIND.OF.STRING
   205  func CaseDelimitedScreaming(s string, del uint8, screaming bool) string {
   206  	s = addWordBoundariesToNumbers(s)
   207  	s = strings.Trim(s, " ")
   208  	n := ""
   209  	for i, v := range s {
   210  		// treat acronyms as words, eg for JSONData -> JSON is a whole word
   211  		nextCaseIsChanged := false
   212  		if i+1 < len(s) {
   213  			next := s[i+1]
   214  			if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
   215  				nextCaseIsChanged = true
   216  			}
   217  		}
   218  
   219  		if i > 0 && n[len(n)-1] != del && nextCaseIsChanged {
   220  			// add underscore if next letter case type is changed
   221  			if v >= 'A' && v <= 'Z' {
   222  				n += string(del) + string(v)
   223  			} else if v >= 'a' && v <= 'z' {
   224  				n += string(v) + string(del)
   225  			}
   226  		} else if v == ' ' || v == '_' || v == '-' || v == '.' {
   227  			// replace spaces/underscores with delimiters
   228  			n += string(del)
   229  		} else {
   230  			n = n + string(v)
   231  		}
   232  	}
   233  
   234  	if screaming {
   235  		n = strings.ToUpper(n)
   236  	} else {
   237  		n = strings.ToLower(n)
   238  	}
   239  	return n
   240  }
   241  
   242  func addWordBoundariesToNumbers(s string) string {
   243  	r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
   244  		var result []byte
   245  		match := numberSequence.FindSubmatch(bytes)
   246  		if len(match[1]) > 0 {
   247  			result = append(result, match[1]...)
   248  			result = append(result, []byte(" ")...)
   249  		}
   250  		result = append(result, match[2]...)
   251  		if len(match[3]) > 0 {
   252  			result = append(result, []byte(" ")...)
   253  			result = append(result, match[3]...)
   254  		}
   255  		return result
   256  	})
   257  	return string(r)
   258  }
   259  
   260  // Converts a string to CamelCase
   261  func toCamelInitCase(s string, initCase bool) string {
   262  	s = addWordBoundariesToNumbers(s)
   263  	s = strings.Trim(s, " ")
   264  	n := ""
   265  	capNext := initCase
   266  	for _, v := range s {
   267  		if v >= 'A' && v <= 'Z' {
   268  			n += string(v)
   269  		}
   270  		if v >= '0' && v <= '9' {
   271  			n += string(v)
   272  		}
   273  		if v >= 'a' && v <= 'z' {
   274  			if capNext {
   275  				n += strings.ToUpper(string(v))
   276  			} else {
   277  				n += string(v)
   278  			}
   279  		}
   280  		if v == '_' || v == ' ' || v == '-' || v == '.' {
   281  			capNext = true
   282  		} else {
   283  			capNext = false
   284  		}
   285  	}
   286  	return n
   287  }