package strutil import ( "strings" "unicode" "unicode/utf8" "golang.org/x/text/width" ) // RuneIsWord char: a-zA-Z func RuneIsWord(c rune) bool { return RuneIsLower(c) || RuneIsUpper(c) } // RuneIsLower char func RuneIsLower(c rune) bool { return 'a' <= c && c <= 'z' } // RuneIsUpper char func RuneIsUpper(c rune) bool { return 'A' <= c && c <= 'Z' } // RunePos alias of the strings.IndexRune func RunePos(s string, ru rune) int { return strings.IndexRune(s, ru) } // IsSpaceRune returns true if the given rune is a space, otherwise false. func IsSpaceRune(r rune) bool { return r <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r) } // Utf8Len count rune of the string. // // Examples: // // str := "hi,你好" // // len(str) // 9 // strutil.RunesWidth(str) // 7 一个中文字占两个字符 // RuneCount(str) = Utf8Len(s) // 5 按字算 func Utf8Len(s string) int { return utf8.RuneCountInString(s) } // Utf8len count rune of the string. func Utf8len(s string) int { return utf8.RuneCountInString(s) } // RuneCount of the string. // // Examples: // // str := "hi,你好" // // len(str) // 9 // strutil.RunesWidth(str) // 7 一个中文字占两个字符 // RuneCount(str) = utf8.RuneCountInString(s) // 5 按字算 func RuneCount(s string) int { return len([]rune(s)) } // RuneWidth of the rune. // // Example: // // RuneWidth('你') // 2 // RuneWidth('a') // 1 // RuneWidth('\n') // 0 func RuneWidth(r rune) int { p := width.LookupRune(r) k := p.Kind() // eg: "\n" if k == width.Neutral { return 0 } if k == width.EastAsianFullwidth || k == width.EastAsianWide || k == width.EastAsianAmbiguous { return 2 } return 1 } // TextWidth utf8 string width. alias of RunesWidth() func TextWidth(s string) int { return Utf8Width(s) } // Utf8Width utf8 string width. alias of RunesWidth func Utf8Width(s string) int { return RunesWidth([]rune(s)) } // RunesWidth utf8 runes string width. // // Examples: // // str := "hi,你好" // // len(str) // 9 // strutil.RunesWidth(str) // 7 一个中文字占两个字符 // len([]rune(str)) = utf8.RuneCountInString(s) // 5 按字算 func RunesWidth(rs []rune) (w int) { if len(rs) == 0 { return } for _, runeVal := range rs { w += RuneWidth(runeVal) } return w } // Truncate alias of the Utf8Truncate() func Truncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) } // TextTruncate alias of the Utf8Truncate() func TextTruncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) } // Utf8Truncate a string with given width. func Utf8Truncate(s string, w int, tail string) string { return utf8Truncate(s, Utf8Width(s), w, tail) } // utf8Truncate a string with given width. func utf8Truncate(s string, sw, w int, tail string) string { if sw <= w { return s } i := 0 r := []rune(s) w -= TextWidth(tail) tmpW := 0 for ; i < len(r); i++ { cw := RuneWidth(r[i]) if tmpW+cw > w { break } tmpW += cw } return string(r[0:i]) + tail } // Chunk split string to chunks by size. // func Chunk[T ~string](s T, size int) []T { // } // TextSplit alias of the Utf8Split() func TextSplit(s string, w int) []string { return Utf8Split(s, w) } // Utf8Split split a string by width. func Utf8Split(s string, w int) []string { sw := Utf8Width(s) if sw <= w { return []string{s} } tmpW := 0 tmpS := "" ss := make([]string, 0, sw/w+1) for _, r := range s { rw := RuneWidth(r) if tmpW+rw == w { tmpS += string(r) ss = append(ss, tmpS) tmpW, tmpS = 0, "" // reset continue } if tmpW+rw > w { ss = append(ss, tmpS) // append to next line. tmpW, tmpS = rw, string(r) continue } tmpW += rw tmpS += string(r) } if tmpW > 0 { ss = append(ss, tmpS) } return ss } // TextWrap a string by "\n". alias of the WidthWrap() func TextWrap(s string, w int) string { return WidthWrap(s, w) } // WidthWrap a string by "\n" // // Example: // s := "hello 你好, world 世界" // s1 := strutil.TextWrap(s, 6) // "hello \n你好, \nworld \n世界" func WidthWrap(s string, w int) string { tmpW := 0 out := "" for _, r := range s { cw := RuneWidth(r) if r == '\n' { out += string(r) tmpW = 0 continue } if tmpW+cw > w { out += "\n" tmpW = 0 out += string(r) tmpW += cw continue } out += string(r) tmpW += cw } return out } // WordWrap text string and limit width. func WordWrap(s string, w int) string { tmpW := 0 out := "" for _, sub := range strings.Split(s, " ") { cw := TextWidth(sub) if tmpW+cw > w { if tmpW != 0 { out += "\n" } tmpW = 0 out += sub tmpW += cw continue } out += sub tmpW += cw } return out } // Runes data slice type Runes []rune // Padding a rune to want length and with position func (rs Runes) Padding(pad rune, length int, pos PosFlag) []rune { return PadChars(rs, pad, length, pos) } // PadLeft a rune to want length func (rs Runes) PadLeft(pad rune, length int) []rune { return rs.Padding(pad, length, PosLeft) } // PadRight a rune to want length func (rs Runes) PadRight(pad rune, length int) []rune { return rs.Padding(pad, length, PosRight) }