diff --git a/database/gdb/gdb_core_tracing.go b/database/gdb/gdb_core_tracing.go
index d92124e25..c94c0b0a3 100644
--- a/database/gdb/gdb_core_tracing.go
+++ b/database/gdb/gdb_core_tracing.go
@@ -55,7 +55,7 @@ func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
labels = append(labels, gtrace.CommonLabels()...)
labels = append(labels,
attribute.String(tracingAttrDbType, c.db.GetConfig().Type),
- attribute.String(string(semconv.DBStatementKey), sql.Format),
+ semconv.DBStatementKey.String(sql.Format),
)
if c.db.GetConfig().Host != "" {
labels = append(labels, attribute.String(tracingAttrDbHost, c.db.GetConfig().Host))
diff --git a/net/ghttp/ghttp.go b/net/ghttp/ghttp.go
index 0dd27cda4..474463137 100644
--- a/net/ghttp/ghttp.go
+++ b/net/ghttp/ghttp.go
@@ -12,12 +12,13 @@ import (
"reflect"
"time"
+ "github.com/gorilla/websocket"
+
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gsession"
"github.com/gogf/gf/v2/protocol/goai"
- "github.com/gorilla/websocket"
)
type (
@@ -132,7 +133,7 @@ const (
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
- responseTraceIdHeader = "Trace-Id"
+ responseTraceIDHeader = "Trace-ID"
)
var (
diff --git a/net/ghttp/ghttp_response.go b/net/ghttp/ghttp_response.go
index d1f0d041c..eb6f89ef1 100644
--- a/net/ghttp/ghttp_response.go
+++ b/net/ghttp/ghttp_response.go
@@ -143,7 +143,7 @@ func (r *Response) ClearBuffer() {
// Flush outputs the buffer content to the client and clears the buffer.
func (r *Response) Flush() {
- r.Header().Set(responseTraceIdHeader, gtrace.GetTraceID(r.Request.Context()))
+ r.Header().Set(responseTraceIDHeader, gtrace.GetTraceID(r.Request.Context()))
if r.Server.config.ServerAgent != "" {
r.Header().Set("Server", r.Server.config.ServerAgent)
}
diff --git a/net/gtrace/gtrace.go b/net/gtrace/gtrace.go
index e5b55c819..5c005b8b0 100644
--- a/net/gtrace/gtrace.go
+++ b/net/gtrace/gtrace.go
@@ -12,7 +12,6 @@ import (
"os"
"strings"
- "github.com/gogf/gf/v2/net/gtrace/internal/provider"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
@@ -23,6 +22,7 @@ import (
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/net/gipv4"
+ "github.com/gogf/gf/v2/net/gtrace/internal/provider"
"github.com/gogf/gf/v2/util/gconv"
)
diff --git a/text/gstr/gstr.go b/text/gstr/gstr.go
index 277e5ff85..9932c5908 100644
--- a/text/gstr/gstr.go
+++ b/text/gstr/gstr.go
@@ -7,20 +7,6 @@
// Package gstr provides functions for string handling.
package gstr
-import (
- "bytes"
- "fmt"
- "math"
- "strconv"
- "strings"
- "unicode"
- "unicode/utf8"
-
- "github.com/gogf/gf/v2/internal/utils"
- "github.com/gogf/gf/v2/util/gconv"
- "github.com/gogf/gf/v2/util/grand"
-)
-
const (
// NotFoundIndex is the position index for string not found in searching functions.
NotFoundIndex = -1
@@ -29,441 +15,3 @@ const (
const (
defaultSuffixForStrLimit = "..."
)
-
-// Count counts the number of `substr` appears in `s`.
-// It returns 0 if no `substr` found in `s`.
-func Count(s, substr string) int {
- return strings.Count(s, substr)
-}
-
-// CountI counts the number of `substr` appears in `s`, case-insensitively.
-// It returns 0 if no `substr` found in `s`.
-func CountI(s, substr string) int {
- return strings.Count(ToLower(s), ToLower(substr))
-}
-
-// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case.
-func ToLower(s string) string {
- return strings.ToLower(s)
-}
-
-// ToUpper returns a copy of the string s with all Unicode letters mapped to their upper case.
-func ToUpper(s string) string {
- return strings.ToUpper(s)
-}
-
-// UcFirst returns a copy of the string s with the first letter mapped to its upper case.
-func UcFirst(s string) string {
- return utils.UcFirst(s)
-}
-
-// LcFirst returns a copy of the string s with the first letter mapped to its lower case.
-func LcFirst(s string) string {
- if len(s) == 0 {
- return s
- }
- if IsLetterUpper(s[0]) {
- return string(s[0]+32) + s[1:]
- }
- return s
-}
-
-// UcWords uppercase the first character of each word in a string.
-func UcWords(str string) string {
- return strings.Title(str)
-}
-
-// IsLetterLower tests whether the given byte b is in lower case.
-func IsLetterLower(b byte) bool {
- return utils.IsLetterLower(b)
-}
-
-// IsLetterUpper tests whether the given byte b is in upper case.
-func IsLetterUpper(b byte) bool {
- return utils.IsLetterUpper(b)
-}
-
-// IsNumeric tests whether the given string s is numeric.
-func IsNumeric(s string) bool {
- return utils.IsNumeric(s)
-}
-
-// Reverse returns a string which is the reverse of `str`.
-func Reverse(str string) string {
- runes := []rune(str)
- for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
- runes[i], runes[j] = runes[j], runes[i]
- }
- return string(runes)
-}
-
-// NumberFormat formats a number with grouped thousands.
-// `decimals`: Sets the number of decimal points.
-// `decPoint`: Sets the separator for the decimal point.
-// `thousandsSep`: Sets the thousands' separator.
-// See http://php.net/manual/en/function.number-format.php.
-func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string {
- neg := false
- if number < 0 {
- number = -number
- neg = true
- }
- // Will round off
- str := fmt.Sprintf("%."+strconv.Itoa(decimals)+"F", number)
- prefix, suffix := "", ""
- if decimals > 0 {
- prefix = str[:len(str)-(decimals+1)]
- suffix = str[len(str)-decimals:]
- } else {
- prefix = str
- }
- sep := []byte(thousandsSep)
- n, l1, l2 := 0, len(prefix), len(sep)
- // thousands sep num
- c := (l1 - 1) / 3
- tmp := make([]byte, l2*c+l1)
- pos := len(tmp) - 1
- for i := l1 - 1; i >= 0; i, n, pos = i-1, n+1, pos-1 {
- if l2 > 0 && n > 0 && n%3 == 0 {
- for j := range sep {
- tmp[pos] = sep[l2-j-1]
- pos--
- }
- }
- tmp[pos] = prefix[i]
- }
- s := string(tmp)
- if decimals > 0 {
- s += decPoint + suffix
- }
- if neg {
- s = "-" + s
- }
-
- return s
-}
-
-// ChunkSplit splits a string into smaller chunks.
-// Can be used to split a string into smaller chunks which is useful for
-// e.g. converting BASE64 string output to match RFC 2045 semantics.
-// It inserts end every chunkLen characters.
-// It considers parameter `body` and `end` as unicode string.
-func ChunkSplit(body string, chunkLen int, end string) string {
- if end == "" {
- end = "\r\n"
- }
- runes, endRunes := []rune(body), []rune(end)
- l := len(runes)
- if l <= 1 || l < chunkLen {
- return body + end
- }
- ns := make([]rune, 0, len(runes)+len(endRunes))
- for i := 0; i < l; i += chunkLen {
- if i+chunkLen > l {
- ns = append(ns, runes[i:]...)
- } else {
- ns = append(ns, runes[i:i+chunkLen]...)
- }
- ns = append(ns, endRunes...)
- }
- return string(ns)
-}
-
-// Compare returns an integer comparing two strings lexicographically.
-// The result will be 0 if a==b, -1 if a < b, and +1 if a > b.
-func Compare(a, b string) int {
- return strings.Compare(a, b)
-}
-
-// Equal reports whether `a` and `b`, interpreted as UTF-8 strings,
-// are equal under Unicode case-folding, case-insensitively.
-func Equal(a, b string) bool {
- return strings.EqualFold(a, b)
-}
-
-// Fields returns the words used in a string as slice.
-func Fields(str string) []string {
- return strings.Fields(str)
-}
-
-// HasPrefix tests whether the string s begins with prefix.
-func HasPrefix(s, prefix string) bool {
- return strings.HasPrefix(s, prefix)
-}
-
-// HasSuffix tests whether the string s ends with suffix.
-func HasSuffix(s, suffix string) bool {
- return strings.HasSuffix(s, suffix)
-}
-
-// CountWords returns information about words' count used in a string.
-// It considers parameter `str` as unicode string.
-func CountWords(str string) map[string]int {
- m := make(map[string]int)
- buffer := bytes.NewBuffer(nil)
- for _, r := range []rune(str) {
- if unicode.IsSpace(r) {
- if buffer.Len() > 0 {
- m[buffer.String()]++
- buffer.Reset()
- }
- } else {
- buffer.WriteRune(r)
- }
- }
- if buffer.Len() > 0 {
- m[buffer.String()]++
- }
- return m
-}
-
-// CountChars returns information about chars' count used in a string.
-// It considers parameter `str` as unicode string.
-func CountChars(str string, noSpace ...bool) map[string]int {
- m := make(map[string]int)
- countSpace := true
- if len(noSpace) > 0 && noSpace[0] {
- countSpace = false
- }
- for _, r := range []rune(str) {
- if !countSpace && unicode.IsSpace(r) {
- continue
- }
- m[string(r)]++
- }
- return m
-}
-
-// WordWrap wraps a string to a given number of characters.
-// TODO: Enable cut parameter, see http://php.net/manual/en/function.wordwrap.php.
-func WordWrap(str string, width int, br string) string {
- if br == "" {
- br = "\n"
- }
- var (
- current int
- wordBuf, spaceBuf bytes.Buffer
- init = make([]byte, 0, len(str))
- buf = bytes.NewBuffer(init)
- )
- for _, char := range []rune(str) {
- if char == '\n' {
- if wordBuf.Len() == 0 {
- if current+spaceBuf.Len() > width {
- current = 0
- } else {
- current += spaceBuf.Len()
- spaceBuf.WriteTo(buf)
- }
- spaceBuf.Reset()
- } else {
- current += spaceBuf.Len() + wordBuf.Len()
- spaceBuf.WriteTo(buf)
- spaceBuf.Reset()
- wordBuf.WriteTo(buf)
- wordBuf.Reset()
- }
- buf.WriteRune(char)
- current = 0
- } else if unicode.IsSpace(char) {
- if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
- current += spaceBuf.Len() + wordBuf.Len()
- spaceBuf.WriteTo(buf)
- spaceBuf.Reset()
- wordBuf.WriteTo(buf)
- wordBuf.Reset()
- }
- spaceBuf.WriteRune(char)
- } else {
- wordBuf.WriteRune(char)
- if current+spaceBuf.Len()+wordBuf.Len() > width && wordBuf.Len() < width {
- buf.WriteString(br)
- current = 0
- spaceBuf.Reset()
- }
- }
- }
-
- if wordBuf.Len() == 0 {
- if current+spaceBuf.Len() <= width {
- spaceBuf.WriteTo(buf)
- }
- } else {
- spaceBuf.WriteTo(buf)
- wordBuf.WriteTo(buf)
- }
- return buf.String()
-}
-
-// LenRune returns string length of unicode.
-func LenRune(str string) int {
- return utf8.RuneCountInString(str)
-}
-
-// Repeat returns a new string consisting of multiplier copies of the string input.
-func Repeat(input string, multiplier int) string {
- return strings.Repeat(input, multiplier)
-}
-
-// Shuffle randomly shuffles a string.
-// It considers parameter `str` as unicode string.
-func Shuffle(str string) string {
- runes := []rune(str)
- s := make([]rune, len(runes))
- for i, v := range grand.Perm(len(runes)) {
- s[i] = runes[v]
- }
- return string(s)
-}
-
-// Split splits string `str` by a string `delimiter`, to an array.
-func Split(str, delimiter string) []string {
- return strings.Split(str, delimiter)
-}
-
-// SplitAndTrim splits string `str` by a string `delimiter` to an array,
-// and calls Trim to every element of this array. It ignores the elements
-// which are empty after Trim.
-func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
- return utils.SplitAndTrim(str, delimiter, characterMask...)
-}
-
-// Join concatenates the elements of `array` to create a single string. The separator string
-// `sep` is placed between elements in the resulting string.
-func Join(array []string, sep string) string {
- return strings.Join(array, sep)
-}
-
-// JoinAny concatenates the elements of `array` to create a single string. The separator string
-// `sep` is placed between elements in the resulting string.
-//
-// The parameter `array` can be any type of slice, which be converted to string array.
-func JoinAny(array interface{}, sep string) string {
- return strings.Join(gconv.Strings(array), sep)
-}
-
-// Explode splits string `str` by a string `delimiter`, to an array.
-// See http://php.net/manual/en/function.explode.php.
-func Explode(delimiter, str string) []string {
- return Split(str, delimiter)
-}
-
-// Implode joins array elements `pieces` with a string `glue`.
-// http://php.net/manual/en/function.implode.php
-func Implode(glue string, pieces []string) string {
- return strings.Join(pieces, glue)
-}
-
-// Chr return the ascii string of a number(0-255).
-func Chr(ascii int) string {
- return string([]byte{byte(ascii % 256)})
-}
-
-// Ord converts the first byte of a string to a value between 0 and 255.
-func Ord(char string) int {
- return int(char[0])
-}
-
-// HideStr replaces part of the string `str` to `hide` by `percentage` from the `middle`.
-// It considers parameter `str` as unicode string.
-func HideStr(str string, percent int, hide string) string {
- array := strings.Split(str, "@")
- if len(array) > 1 {
- str = array[0]
- }
- var (
- rs = []rune(str)
- length = len(rs)
- mid = math.Floor(float64(length / 2))
- hideLen = int(math.Floor(float64(length) * (float64(percent) / 100)))
- start = int(mid - math.Floor(float64(hideLen)/2))
- hideStr = []rune("")
- hideRune = []rune(hide)
- )
- for i := 0; i < hideLen; i++ {
- hideStr = append(hideStr, hideRune...)
- }
- buffer := bytes.NewBuffer(nil)
- buffer.WriteString(string(rs[0:start]))
- buffer.WriteString(string(hideStr))
- buffer.WriteString(string(rs[start+hideLen:]))
- if len(array) > 1 {
- buffer.WriteString("@" + array[1])
- }
- return buffer.String()
-}
-
-// Nl2Br inserts HTML line breaks(`br`|
) before all newlines in a string:
-// \n\r, \r\n, \r, \n.
-// It considers parameter `str` as unicode string.
-func Nl2Br(str string, isXhtml ...bool) string {
- r, n, runes := '\r', '\n', []rune(str)
- var br []byte
- if len(isXhtml) > 0 && isXhtml[0] {
- br = []byte("
")
- } else {
- br = []byte("
")
- }
- skip := false
- length := len(runes)
- var buf bytes.Buffer
- for i, v := range runes {
- if skip {
- skip = false
- continue
- }
- switch v {
- case n, r:
- if (i+1 < length) && (v == r && runes[i+1] == n) || (v == n && runes[i+1] == r) {
- buf.Write(br)
- skip = true
- continue
- }
- buf.Write(br)
- default:
- buf.WriteRune(v)
- }
- }
- return buf.String()
-}
-
-// AddSlashes quotes chars('"\) with slashes.
-func AddSlashes(str string) string {
- var buf bytes.Buffer
- for _, char := range str {
- switch char {
- case '\'', '"', '\\':
- buf.WriteRune('\\')
- }
- buf.WriteRune(char)
- }
- return buf.String()
-}
-
-// StripSlashes un-quotes a quoted string by AddSlashes.
-func StripSlashes(str string) string {
- return utils.StripSlashes(str)
-}
-
-// QuoteMeta returns a version of str with a backslash character (\)
-// before every character that is among: .\+*?[^]($)
-func QuoteMeta(str string, chars ...string) string {
- var buf bytes.Buffer
- for _, char := range str {
- if len(chars) > 0 {
- for _, c := range chars[0] {
- if c == char {
- buf.WriteRune('\\')
- break
- }
- }
- } else {
- switch char {
- case '.', '+', '\\', '(', '$', ')', '[', '^', ']', '*', '?':
- buf.WriteRune('\\')
- }
- }
- buf.WriteRune(char)
- }
- return buf.String()
-}
diff --git a/text/gstr/gstr_compare.go b/text/gstr/gstr_compare.go
new file mode 100644
index 000000000..ae877f677
--- /dev/null
+++ b/text/gstr/gstr_compare.go
@@ -0,0 +1,21 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import "strings"
+
+// Compare returns an integer comparing two strings lexicographically.
+// The result will be 0 if a==b, -1 if a < b, and +1 if a > b.
+func Compare(a, b string) int {
+ return strings.Compare(a, b)
+}
+
+// Equal reports whether `a` and `b`, interpreted as UTF-8 strings,
+// are equal under Unicode case-folding, case-insensitively.
+func Equal(a, b string) bool {
+ return strings.EqualFold(a, b)
+}
diff --git a/text/gstr/gstr_convert.go b/text/gstr/gstr_convert.go
index 7efce8c2b..7c4a6add3 100644
--- a/text/gstr/gstr_convert.go
+++ b/text/gstr/gstr_convert.go
@@ -7,10 +7,26 @@
package gstr
import (
+ "bytes"
+ "fmt"
+ "github.com/gogf/gf/v2/util/grand"
+ "math"
"regexp"
"strconv"
+ "strings"
+ "unicode"
)
+// Chr return the ascii string of a number(0-255).
+func Chr(ascii int) string {
+ return string([]byte{byte(ascii % 256)})
+}
+
+// Ord converts the first byte of a string to a value between 0 and 255.
+func Ord(char string) int {
+ return int(char[0])
+}
+
var (
// octReg is the regular expression object for checks octal string.
octReg = regexp.MustCompile(`\\[0-7]{3}`)
@@ -28,3 +44,193 @@ func OctStr(str string) string {
},
)
}
+
+// Reverse returns a string which is the reverse of `str`.
+func Reverse(str string) string {
+ runes := []rune(str)
+ for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
+ runes[i], runes[j] = runes[j], runes[i]
+ }
+ return string(runes)
+}
+
+// NumberFormat formats a number with grouped thousands.
+// `decimals`: Sets the number of decimal points.
+// `decPoint`: Sets the separator for the decimal point.
+// `thousandsSep`: Sets the thousands' separator.
+// See http://php.net/manual/en/function.number-format.php.
+func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string {
+ neg := false
+ if number < 0 {
+ number = -number
+ neg = true
+ }
+ // Will round off
+ str := fmt.Sprintf("%."+strconv.Itoa(decimals)+"F", number)
+ prefix, suffix := "", ""
+ if decimals > 0 {
+ prefix = str[:len(str)-(decimals+1)]
+ suffix = str[len(str)-decimals:]
+ } else {
+ prefix = str
+ }
+ sep := []byte(thousandsSep)
+ n, l1, l2 := 0, len(prefix), len(sep)
+ // thousands sep num
+ c := (l1 - 1) / 3
+ tmp := make([]byte, l2*c+l1)
+ pos := len(tmp) - 1
+ for i := l1 - 1; i >= 0; i, n, pos = i-1, n+1, pos-1 {
+ if l2 > 0 && n > 0 && n%3 == 0 {
+ for j := range sep {
+ tmp[pos] = sep[l2-j-1]
+ pos--
+ }
+ }
+ tmp[pos] = prefix[i]
+ }
+ s := string(tmp)
+ if decimals > 0 {
+ s += decPoint + suffix
+ }
+ if neg {
+ s = "-" + s
+ }
+
+ return s
+}
+
+// Shuffle randomly shuffles a string.
+// It considers parameter `str` as unicode string.
+func Shuffle(str string) string {
+ runes := []rune(str)
+ s := make([]rune, len(runes))
+ for i, v := range grand.Perm(len(runes)) {
+ s[i] = runes[v]
+ }
+ return string(s)
+}
+
+// HideStr replaces part of the string `str` to `hide` by `percentage` from the `middle`.
+// It considers parameter `str` as unicode string.
+func HideStr(str string, percent int, hide string) string {
+ array := strings.Split(str, "@")
+ if len(array) > 1 {
+ str = array[0]
+ }
+ var (
+ rs = []rune(str)
+ length = len(rs)
+ mid = math.Floor(float64(length / 2))
+ hideLen = int(math.Floor(float64(length) * (float64(percent) / 100)))
+ start = int(mid - math.Floor(float64(hideLen)/2))
+ hideStr = []rune("")
+ hideRune = []rune(hide)
+ )
+ for i := 0; i < hideLen; i++ {
+ hideStr = append(hideStr, hideRune...)
+ }
+ buffer := bytes.NewBuffer(nil)
+ buffer.WriteString(string(rs[0:start]))
+ buffer.WriteString(string(hideStr))
+ buffer.WriteString(string(rs[start+hideLen:]))
+ if len(array) > 1 {
+ buffer.WriteString("@" + array[1])
+ }
+ return buffer.String()
+}
+
+// Nl2Br inserts HTML line breaks(`br`|
) before all newlines in a string:
+// \n\r, \r\n, \r, \n.
+// It considers parameter `str` as unicode string.
+func Nl2Br(str string, isXhtml ...bool) string {
+ r, n, runes := '\r', '\n', []rune(str)
+ var br []byte
+ if len(isXhtml) > 0 && isXhtml[0] {
+ br = []byte("
")
+ } else {
+ br = []byte("
")
+ }
+ skip := false
+ length := len(runes)
+ var buf bytes.Buffer
+ for i, v := range runes {
+ if skip {
+ skip = false
+ continue
+ }
+ switch v {
+ case n, r:
+ if (i+1 < length) && (v == r && runes[i+1] == n) || (v == n && runes[i+1] == r) {
+ buf.Write(br)
+ skip = true
+ continue
+ }
+ buf.Write(br)
+ default:
+ buf.WriteRune(v)
+ }
+ }
+ return buf.String()
+}
+
+// WordWrap wraps a string to a given number of characters.
+// TODO: Enable cut parameter, see http://php.net/manual/en/function.wordwrap.php.
+func WordWrap(str string, width int, br string) string {
+ if br == "" {
+ br = "\n"
+ }
+ var (
+ current int
+ wordBuf, spaceBuf bytes.Buffer
+ init = make([]byte, 0, len(str))
+ buf = bytes.NewBuffer(init)
+ )
+ for _, char := range []rune(str) {
+ if char == '\n' {
+ if wordBuf.Len() == 0 {
+ if current+spaceBuf.Len() > width {
+ current = 0
+ } else {
+ current += spaceBuf.Len()
+ spaceBuf.WriteTo(buf)
+ }
+ spaceBuf.Reset()
+ } else {
+ current += spaceBuf.Len() + wordBuf.Len()
+ spaceBuf.WriteTo(buf)
+ spaceBuf.Reset()
+ wordBuf.WriteTo(buf)
+ wordBuf.Reset()
+ }
+ buf.WriteRune(char)
+ current = 0
+ } else if unicode.IsSpace(char) {
+ if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
+ current += spaceBuf.Len() + wordBuf.Len()
+ spaceBuf.WriteTo(buf)
+ spaceBuf.Reset()
+ wordBuf.WriteTo(buf)
+ wordBuf.Reset()
+ }
+ spaceBuf.WriteRune(char)
+ } else {
+ wordBuf.WriteRune(char)
+ if current+spaceBuf.Len()+wordBuf.Len() > width && wordBuf.Len() < width {
+ buf.WriteString(br)
+ current = 0
+ spaceBuf.Reset()
+ }
+ }
+ }
+
+ if wordBuf.Len() == 0 {
+ if current+spaceBuf.Len() <= width {
+ spaceBuf.WriteTo(buf)
+ }
+ } else {
+ spaceBuf.WriteTo(buf)
+ wordBuf.WriteTo(buf)
+ }
+ return buf.String()
+}
diff --git a/text/gstr/gstr_count.go b/text/gstr/gstr_count.go
new file mode 100644
index 000000000..cd0f0a19e
--- /dev/null
+++ b/text/gstr/gstr_count.go
@@ -0,0 +1,63 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import (
+ "bytes"
+ "strings"
+ "unicode"
+)
+
+// Count counts the number of `substr` appears in `s`.
+// It returns 0 if no `substr` found in `s`.
+func Count(s, substr string) int {
+ return strings.Count(s, substr)
+}
+
+// CountI counts the number of `substr` appears in `s`, case-insensitively.
+// It returns 0 if no `substr` found in `s`.
+func CountI(s, substr string) int {
+ return strings.Count(ToLower(s), ToLower(substr))
+}
+
+// CountWords returns information about words' count used in a string.
+// It considers parameter `str` as unicode string.
+func CountWords(str string) map[string]int {
+ m := make(map[string]int)
+ buffer := bytes.NewBuffer(nil)
+ for _, r := range []rune(str) {
+ if unicode.IsSpace(r) {
+ if buffer.Len() > 0 {
+ m[buffer.String()]++
+ buffer.Reset()
+ }
+ } else {
+ buffer.WriteRune(r)
+ }
+ }
+ if buffer.Len() > 0 {
+ m[buffer.String()]++
+ }
+ return m
+}
+
+// CountChars returns information about chars' count used in a string.
+// It considers parameter `str` as unicode string.
+func CountChars(str string, noSpace ...bool) map[string]int {
+ m := make(map[string]int)
+ countSpace := true
+ if len(noSpace) > 0 && noSpace[0] {
+ countSpace = false
+ }
+ for _, r := range []rune(str) {
+ if !countSpace && unicode.IsSpace(r) {
+ continue
+ }
+ m[string(r)]++
+ }
+ return m
+}
diff --git a/text/gstr/gstr_create.go b/text/gstr/gstr_create.go
new file mode 100644
index 000000000..8e5ff3129
--- /dev/null
+++ b/text/gstr/gstr_create.go
@@ -0,0 +1,14 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import "strings"
+
+// Repeat returns a new string consisting of multiplier copies of the string input.
+func Repeat(input string, multiplier int) string {
+ return strings.Repeat(input, multiplier)
+}
diff --git a/text/gstr/gstr_is.go b/text/gstr/gstr_is.go
new file mode 100644
index 000000000..2f52e9495
--- /dev/null
+++ b/text/gstr/gstr_is.go
@@ -0,0 +1,14 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import "github.com/gogf/gf/v2/internal/utils"
+
+// IsNumeric tests whether the given string s is numeric.
+func IsNumeric(s string) bool {
+ return utils.IsNumeric(s)
+}
diff --git a/text/gstr/gstr_length.go b/text/gstr/gstr_length.go
new file mode 100644
index 000000000..9e5b915b4
--- /dev/null
+++ b/text/gstr/gstr_length.go
@@ -0,0 +1,14 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import "unicode/utf8"
+
+// LenRune returns string length of unicode.
+func LenRune(str string) int {
+ return utf8.RuneCountInString(str)
+}
diff --git a/text/gstr/gstr_levenshtein.go b/text/gstr/gstr_levenshtein.go
deleted file mode 100644
index 489c6eb3b..000000000
--- a/text/gstr/gstr_levenshtein.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
-//
-// This Source Code Form is subject to the terms of the MIT License.
-// If a copy of the MIT was not distributed with this file,
-// You can obtain one at https://github.com/gogf/gf.
-
-package gstr
-
-// Levenshtein calculates Levenshtein distance between two strings.
-// costIns: Defines the cost of insertion.
-// costRep: Defines the cost of replacement.
-// costDel: Defines the cost of deletion.
-// See http://php.net/manual/en/function.levenshtein.php.
-func Levenshtein(str1, str2 string, costIns, costRep, costDel int) int {
- var maxLen = 255
- l1 := len(str1)
- l2 := len(str2)
- if l1 == 0 {
- return l2 * costIns
- }
- if l2 == 0 {
- return l1 * costDel
- }
- if l1 > maxLen || l2 > maxLen {
- return -1
- }
-
- tmp := make([]int, l2+1)
- p1 := make([]int, l2+1)
- p2 := make([]int, l2+1)
- var c0, c1, c2 int
- var i1, i2 int
- for i2 := 0; i2 <= l2; i2++ {
- p1[i2] = i2 * costIns
- }
- for i1 = 0; i1 < l1; i1++ {
- p2[0] = p1[0] + costDel
- for i2 = 0; i2 < l2; i2++ {
- if str1[i1] == str2[i2] {
- c0 = p1[i2]
- } else {
- c0 = p1[i2] + costRep
- }
- c1 = p1[i2+1] + costDel
- if c1 < c0 {
- c0 = c1
- }
- c2 = p2[i2] + costIns
- if c2 < c0 {
- c0 = c2
- }
- p2[i2+1] = c0
- }
- tmp = p1
- p1 = p2
- p2 = tmp
- }
- c0 = p1[l2]
-
- return c0
-}
diff --git a/text/gstr/gstr_similar.go b/text/gstr/gstr_similar.go
new file mode 100644
index 000000000..7c19618fb
--- /dev/null
+++ b/text/gstr/gstr_similar.go
@@ -0,0 +1,158 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+// Levenshtein calculates Levenshtein distance between two strings.
+// costIns: Defines the cost of insertion.
+// costRep: Defines the cost of replacement.
+// costDel: Defines the cost of deletion.
+// See http://php.net/manual/en/function.levenshtein.php.
+func Levenshtein(str1, str2 string, costIns, costRep, costDel int) int {
+ var maxLen = 255
+ l1 := len(str1)
+ l2 := len(str2)
+ if l1 == 0 {
+ return l2 * costIns
+ }
+ if l2 == 0 {
+ return l1 * costDel
+ }
+ if l1 > maxLen || l2 > maxLen {
+ return -1
+ }
+
+ tmp := make([]int, l2+1)
+ p1 := make([]int, l2+1)
+ p2 := make([]int, l2+1)
+ var c0, c1, c2 int
+ var i1, i2 int
+ for i2 := 0; i2 <= l2; i2++ {
+ p1[i2] = i2 * costIns
+ }
+ for i1 = 0; i1 < l1; i1++ {
+ p2[0] = p1[0] + costDel
+ for i2 = 0; i2 < l2; i2++ {
+ if str1[i1] == str2[i2] {
+ c0 = p1[i2]
+ } else {
+ c0 = p1[i2] + costRep
+ }
+ c1 = p1[i2+1] + costDel
+ if c1 < c0 {
+ c0 = c1
+ }
+ c2 = p2[i2] + costIns
+ if c2 < c0 {
+ c0 = c2
+ }
+ p2[i2+1] = c0
+ }
+ tmp = p1
+ p1 = p2
+ p2 = tmp
+ }
+ c0 = p1[l2]
+
+ return c0
+}
+
+// SimilarText calculates the similarity between two strings.
+// See http://php.net/manual/en/function.similar-text.php.
+func SimilarText(first, second string, percent *float64) int {
+ var similarText func(string, string, int, int) int
+ similarText = func(str1, str2 string, len1, len2 int) int {
+ var sum, max int
+ pos1, pos2 := 0, 0
+
+ // Find the longest segment of the same section in two strings
+ for i := 0; i < len1; i++ {
+ for j := 0; j < len2; j++ {
+ for l := 0; (i+l < len1) && (j+l < len2) && (str1[i+l] == str2[j+l]); l++ {
+ if l+1 > max {
+ max = l + 1
+ pos1 = i
+ pos2 = j
+ }
+ }
+ }
+ }
+
+ if sum = max; sum > 0 {
+ if pos1 > 0 && pos2 > 0 {
+ sum += similarText(str1, str2, pos1, pos2)
+ }
+ if (pos1+max < len1) && (pos2+max < len2) {
+ s1 := []byte(str1)
+ s2 := []byte(str2)
+ sum += similarText(string(s1[pos1+max:]), string(s2[pos2+max:]), len1-pos1-max, len2-pos2-max)
+ }
+ }
+
+ return sum
+ }
+
+ l1, l2 := len(first), len(second)
+ if l1+l2 == 0 {
+ return 0
+ }
+ sim := similarText(first, second, l1, l2)
+ if percent != nil {
+ *percent = float64(sim*200) / float64(l1+l2)
+ }
+ return sim
+}
+
+// Soundex calculates the soundex key of a string.
+// See http://php.net/manual/en/function.soundex.php.
+func Soundex(str string) string {
+ if str == "" {
+ panic("str: cannot be an empty string")
+ }
+ table := [26]rune{
+ '0', '1', '2', '3', // A, B, C, D
+ '0', '1', '2', // E, F, G
+ '0', // H
+ '0', '2', '2', '4', '5', '5', // I, J, K, L, M, N
+ '0', '1', '2', '6', '2', '3', // O, P, Q, R, S, T
+ '0', '1', // U, V
+ '0', '2', // W, X
+ '0', '2', // Y, Z
+ }
+ last, code, small := -1, 0, 0
+ sd := make([]rune, 4)
+ // build soundex string
+ for i := 0; i < len(str) && small < 4; i++ {
+ // ToUpper
+ char := str[i]
+ if char < '\u007F' && 'a' <= char && char <= 'z' {
+ code = int(char - 'a' + 'A')
+ } else {
+ code = int(char)
+ }
+ if code >= 'A' && code <= 'Z' {
+ if small == 0 {
+ sd[small] = rune(code)
+ small++
+ last = int(table[code-'A'])
+ } else {
+ code = int(table[code-'A'])
+ if code != last {
+ if code != 0 {
+ sd[small] = rune(code)
+ small++
+ }
+ last = code
+ }
+ }
+ }
+ }
+ // pad with "0"
+ for ; small < 4; small++ {
+ sd[small] = '0'
+ }
+ return string(sd)
+}
diff --git a/text/gstr/gstr_similartext.go b/text/gstr/gstr_similartext.go
deleted file mode 100644
index efb253739..000000000
--- a/text/gstr/gstr_similartext.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
-//
-// This Source Code Form is subject to the terms of the MIT License.
-// If a copy of the MIT was not distributed with this file,
-// You can obtain one at https://github.com/gogf/gf.
-
-package gstr
-
-// SimilarText calculates the similarity between two strings.
-// See http://php.net/manual/en/function.similar-text.php.
-func SimilarText(first, second string, percent *float64) int {
- var similarText func(string, string, int, int) int
- similarText = func(str1, str2 string, len1, len2 int) int {
- var sum, max int
- pos1, pos2 := 0, 0
-
- // Find the longest segment of the same section in two strings
- for i := 0; i < len1; i++ {
- for j := 0; j < len2; j++ {
- for l := 0; (i+l < len1) && (j+l < len2) && (str1[i+l] == str2[j+l]); l++ {
- if l+1 > max {
- max = l + 1
- pos1 = i
- pos2 = j
- }
- }
- }
- }
-
- if sum = max; sum > 0 {
- if pos1 > 0 && pos2 > 0 {
- sum += similarText(str1, str2, pos1, pos2)
- }
- if (pos1+max < len1) && (pos2+max < len2) {
- s1 := []byte(str1)
- s2 := []byte(str2)
- sum += similarText(string(s1[pos1+max:]), string(s2[pos2+max:]), len1-pos1-max, len2-pos2-max)
- }
- }
-
- return sum
- }
-
- l1, l2 := len(first), len(second)
- if l1+l2 == 0 {
- return 0
- }
- sim := similarText(first, second, l1, l2)
- if percent != nil {
- *percent = float64(sim*200) / float64(l1+l2)
- }
- return sim
-}
diff --git a/text/gstr/gstr_slashes.go b/text/gstr/gstr_slashes.go
new file mode 100644
index 000000000..e897673ba
--- /dev/null
+++ b/text/gstr/gstr_slashes.go
@@ -0,0 +1,53 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import (
+ "bytes"
+ "github.com/gogf/gf/v2/internal/utils"
+)
+
+// AddSlashes quotes chars('"\) with slashes.
+func AddSlashes(str string) string {
+ var buf bytes.Buffer
+ for _, char := range str {
+ switch char {
+ case '\'', '"', '\\':
+ buf.WriteRune('\\')
+ }
+ buf.WriteRune(char)
+ }
+ return buf.String()
+}
+
+// StripSlashes un-quotes a quoted string by AddSlashes.
+func StripSlashes(str string) string {
+ return utils.StripSlashes(str)
+}
+
+// QuoteMeta returns a version of str with a backslash character (\)
+// before every character that is among: .\+*?[^]($)
+func QuoteMeta(str string, chars ...string) string {
+ var buf bytes.Buffer
+ for _, char := range str {
+ if len(chars) > 0 {
+ for _, c := range chars[0] {
+ if c == char {
+ buf.WriteRune('\\')
+ break
+ }
+ }
+ } else {
+ switch char {
+ case '.', '+', '\\', '(', '$', ')', '[', '^', ']', '*', '?':
+ buf.WriteRune('\\')
+ }
+ }
+ buf.WriteRune(char)
+ }
+ return buf.String()
+}
diff --git a/text/gstr/gstr_soundex.go b/text/gstr/gstr_soundex.go
deleted file mode 100644
index bdba8933e..000000000
--- a/text/gstr/gstr_soundex.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
-//
-// This Source Code Form is subject to the terms of the MIT License.
-// If a copy of the MIT was not distributed with this file,
-// You can obtain one at https://github.com/gogf/gf.
-
-package gstr
-
-// Soundex calculates the soundex key of a string.
-// See http://php.net/manual/en/function.soundex.php.
-func Soundex(str string) string {
- if str == "" {
- panic("str: cannot be an empty string")
- }
- table := [26]rune{
- '0', '1', '2', '3', // A, B, C, D
- '0', '1', '2', // E, F, G
- '0', // H
- '0', '2', '2', '4', '5', '5', // I, J, K, L, M, N
- '0', '1', '2', '6', '2', '3', // O, P, Q, R, S, T
- '0', '1', // U, V
- '0', '2', // W, X
- '0', '2', // Y, Z
- }
- last, code, small := -1, 0, 0
- sd := make([]rune, 4)
- // build soundex string
- for i := 0; i < len(str) && small < 4; i++ {
- // ToUpper
- char := str[i]
- if char < '\u007F' && 'a' <= char && char <= 'z' {
- code = int(char - 'a' + 'A')
- } else {
- code = int(char)
- }
- if code >= 'A' && code <= 'Z' {
- if small == 0 {
- sd[small] = rune(code)
- small++
- last = int(table[code-'A'])
- } else {
- code = int(table[code-'A'])
- if code != last {
- if code != 0 {
- sd[small] = rune(code)
- small++
- }
- last = code
- }
- }
- }
- }
- // pad with "0"
- for ; small < 4; small++ {
- sd[small] = '0'
- }
- return string(sd)
-}
diff --git a/text/gstr/gstr_split_join.go b/text/gstr/gstr_split_join.go
new file mode 100644
index 000000000..737f85c21
--- /dev/null
+++ b/text/gstr/gstr_split_join.go
@@ -0,0 +1,82 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import (
+ "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/util/gconv"
+ "strings"
+)
+
+// Split splits string `str` by a string `delimiter`, to an array.
+func Split(str, delimiter string) []string {
+ return strings.Split(str, delimiter)
+}
+
+// SplitAndTrim splits string `str` by a string `delimiter` to an array,
+// and calls Trim to every element of this array. It ignores the elements
+// which are empty after Trim.
+func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
+ return utils.SplitAndTrim(str, delimiter, characterMask...)
+}
+
+// Join concatenates the elements of `array` to create a single string. The separator string
+// `sep` is placed between elements in the resulting string.
+func Join(array []string, sep string) string {
+ return strings.Join(array, sep)
+}
+
+// JoinAny concatenates the elements of `array` to create a single string. The separator string
+// `sep` is placed between elements in the resulting string.
+//
+// The parameter `array` can be any type of slice, which be converted to string array.
+func JoinAny(array interface{}, sep string) string {
+ return strings.Join(gconv.Strings(array), sep)
+}
+
+// Explode splits string `str` by a string `delimiter`, to an array.
+// See http://php.net/manual/en/function.explode.php.
+func Explode(delimiter, str string) []string {
+ return Split(str, delimiter)
+}
+
+// Implode joins array elements `pieces` with a string `glue`.
+// http://php.net/manual/en/function.implode.php
+func Implode(glue string, pieces []string) string {
+ return strings.Join(pieces, glue)
+}
+
+// ChunkSplit splits a string into smaller chunks.
+// Can be used to split a string into smaller chunks which is useful for
+// e.g. converting BASE64 string output to match RFC 2045 semantics.
+// It inserts end every chunkLen characters.
+// It considers parameter `body` and `end` as unicode string.
+func ChunkSplit(body string, chunkLen int, end string) string {
+ if end == "" {
+ end = "\r\n"
+ }
+ runes, endRunes := []rune(body), []rune(end)
+ l := len(runes)
+ if l <= 1 || l < chunkLen {
+ return body + end
+ }
+ ns := make([]rune, 0, len(runes)+len(endRunes))
+ for i := 0; i < l; i += chunkLen {
+ if i+chunkLen > l {
+ ns = append(ns, runes[i:]...)
+ } else {
+ ns = append(ns, runes[i:i+chunkLen]...)
+ }
+ ns = append(ns, endRunes...)
+ }
+ return string(ns)
+}
+
+// Fields returns the words used in a string as slice.
+func Fields(str string) []string {
+ return strings.Fields(str)
+}
diff --git a/text/gstr/gstr_str.go b/text/gstr/gstr_str.go
deleted file mode 100644
index 8b316efd1..000000000
--- a/text/gstr/gstr_str.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
-//
-// This Source Code Form is subject to the terms of the MIT License.
-// If a copy of the MIT was not distributed with this file,
-// You can obtain one at https://github.com/gogf/gf.
-
-package gstr
-
-import "strings"
-
-// Str returns part of `haystack` string starting from and including
-// the first occurrence of `needle` to the end of `haystack`.
-// See http://php.net/manual/en/function.strstr.php.
-func Str(haystack string, needle string) string {
- if needle == "" {
- return ""
- }
- pos := strings.Index(haystack, needle)
- if pos == NotFoundIndex {
- return ""
- }
- return haystack[pos+len([]byte(needle))-1:]
-}
-
-// StrEx returns part of `haystack` string starting from and excluding
-// the first occurrence of `needle` to the end of `haystack`.
-func StrEx(haystack string, needle string) string {
- if s := Str(haystack, needle); s != "" {
- return s[1:]
- }
- return ""
-}
-
-// StrTill returns part of `haystack` string ending to and including
-// the first occurrence of `needle` from the start of `haystack`.
-func StrTill(haystack string, needle string) string {
- pos := strings.Index(haystack, needle)
- if pos == NotFoundIndex || pos == 0 {
- return ""
- }
- return haystack[:pos+1]
-}
-
-// StrTillEx returns part of `haystack` string ending to and excluding
-// the first occurrence of `needle` from the start of `haystack`.
-func StrTillEx(haystack string, needle string) string {
- pos := strings.Index(haystack, needle)
- if pos == NotFoundIndex || pos == 0 {
- return ""
- }
- return haystack[:pos]
-}
diff --git a/text/gstr/gstr_substr.go b/text/gstr/gstr_sub.go
similarity index 73%
rename from text/gstr/gstr_substr.go
rename to text/gstr/gstr_sub.go
index 18481ca55..8eef7edc6 100644
--- a/text/gstr/gstr_substr.go
+++ b/text/gstr/gstr_sub.go
@@ -6,6 +6,51 @@
package gstr
+import "strings"
+
+// Str returns part of `haystack` string starting from and including
+// the first occurrence of `needle` to the end of `haystack`.
+// See http://php.net/manual/en/function.strstr.php.
+func Str(haystack string, needle string) string {
+ if needle == "" {
+ return ""
+ }
+ pos := strings.Index(haystack, needle)
+ if pos == NotFoundIndex {
+ return ""
+ }
+ return haystack[pos+len([]byte(needle))-1:]
+}
+
+// StrEx returns part of `haystack` string starting from and excluding
+// the first occurrence of `needle` to the end of `haystack`.
+func StrEx(haystack string, needle string) string {
+ if s := Str(haystack, needle); s != "" {
+ return s[1:]
+ }
+ return ""
+}
+
+// StrTill returns part of `haystack` string ending to and including
+// the first occurrence of `needle` from the start of `haystack`.
+func StrTill(haystack string, needle string) string {
+ pos := strings.Index(haystack, needle)
+ if pos == NotFoundIndex || pos == 0 {
+ return ""
+ }
+ return haystack[:pos+1]
+}
+
+// StrTillEx returns part of `haystack` string ending to and excluding
+// the first occurrence of `needle` from the start of `haystack`.
+func StrTillEx(haystack string, needle string) string {
+ pos := strings.Index(haystack, needle)
+ if pos == NotFoundIndex || pos == 0 {
+ return ""
+ }
+ return haystack[:pos]
+}
+
// SubStr returns a portion of string `str` specified by the `start` and `length` parameters.
// The parameter `length` is optional, it uses the length of `str` in default.
func SubStr(str string, start int, length ...int) (substr string) {
diff --git a/text/gstr/gstr_trim.go b/text/gstr/gstr_trim.go
index ea46102f1..f7701505e 100644
--- a/text/gstr/gstr_trim.go
+++ b/text/gstr/gstr_trim.go
@@ -102,3 +102,13 @@ func TrimAll(str string, characterMask ...string) string {
}
return string(slice)
}
+
+// HasPrefix tests whether the string s begins with prefix.
+func HasPrefix(s, prefix string) bool {
+ return strings.HasPrefix(s, prefix)
+}
+
+// HasSuffix tests whether the string s ends with suffix.
+func HasSuffix(s, suffix string) bool {
+ return strings.HasSuffix(s, suffix)
+}
diff --git a/text/gstr/gstr_upper_lower.go b/text/gstr/gstr_upper_lower.go
new file mode 100644
index 000000000..3d6c75ad9
--- /dev/null
+++ b/text/gstr/gstr_upper_lower.go
@@ -0,0 +1,53 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gstr
+
+import (
+ "github.com/gogf/gf/v2/internal/utils"
+ "strings"
+)
+
+// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case.
+func ToLower(s string) string {
+ return strings.ToLower(s)
+}
+
+// ToUpper returns a copy of the string s with all Unicode letters mapped to their upper case.
+func ToUpper(s string) string {
+ return strings.ToUpper(s)
+}
+
+// UcFirst returns a copy of the string s with the first letter mapped to its upper case.
+func UcFirst(s string) string {
+ return utils.UcFirst(s)
+}
+
+// LcFirst returns a copy of the string s with the first letter mapped to its lower case.
+func LcFirst(s string) string {
+ if len(s) == 0 {
+ return s
+ }
+ if IsLetterUpper(s[0]) {
+ return string(s[0]+32) + s[1:]
+ }
+ return s
+}
+
+// UcWords uppercase the first character of each word in a string.
+func UcWords(str string) string {
+ return strings.Title(str)
+}
+
+// IsLetterLower tests whether the given byte b is in lower case.
+func IsLetterLower(b byte) bool {
+ return utils.IsLetterLower(b)
+}
+
+// IsLetterUpper tests whether the given byte b is in upper case.
+func IsLetterUpper(b byte) bool {
+ return utils.IsLetterUpper(b)
+}