// Copyright 2015 Huan Du. All rights reserved. // Licensed under the MIT license that can be found in the LICENSE file. package xstrings import ( "bytes" "math/rand" "unicode" "unicode/utf8" ) // ToCamelCase is to convert words separated by space, underscore and hyphen to camel case. // // Some samples. // "some_words" => "SomeWords" // "http_server" => "HttpServer" // "no_https" => "NoHttps" // "_complex__case_" => "_Complex_Case_" // "some words" => "SomeWords" func ToCamelCase(str string) string { if len(str) == 0 { return "" } buf := &bytes.Buffer{} var r0, r1 rune var size int // leading connector will appear in output. for len(str) > 0 { r0, size = utf8.DecodeRuneInString(str) str = str[size:] if !isConnector(r0) { r0 = unicode.ToUpper(r0) break } buf.WriteRune(r0) } if len(str) == 0 { // A special case for a string contains only 1 rune. if size != 0 { buf.WriteRune(r0) } return buf.String() } for len(str) > 0 { r1 = r0 r0, size = utf8.DecodeRuneInString(str) str = str[size:] if isConnector(r0) && isConnector(r1) { buf.WriteRune(r1) continue } if isConnector(r1) { r0 = unicode.ToUpper(r0) } else { r0 = unicode.ToLower(r0) buf.WriteRune(r1) } } buf.WriteRune(r0) return buf.String() } // ToSnakeCase can convert all upper case characters in a string to // snake case format. // // Some samples. // "FirstName" => "first_name" // "HTTPServer" => "http_server" // "NoHTTPS" => "no_https" // "GO_PATH" => "go_path" // "GO PATH" => "go_path" // space is converted to underscore. // "GO-PATH" => "go_path" // hyphen is converted to underscore. // "HTTP2XX" => "http_2xx" // insert an underscore before a number and after an alphabet. // "http2xx" => "http_2xx" // "HTTP20xOK" => "http_20x_ok" func ToSnakeCase(str string) string { return camelCaseToLowerCase(str, '_') } // ToKebabCase can convert all upper case characters in a string to // kebab case format. // // Some samples. // "FirstName" => "first-name" // "HTTPServer" => "http-server" // "NoHTTPS" => "no-https" // "GO_PATH" => "go-path" // "GO PATH" => "go-path" // space is converted to '-'. // "GO-PATH" => "go-path" // hyphen is converted to '-'. // "HTTP2XX" => "http-2xx" // insert a '-' before a number and after an alphabet. // "http2xx" => "http-2xx" // "HTTP20xOK" => "http-20x-ok" func ToKebabCase(str string) string { return camelCaseToLowerCase(str, '-') } func camelCaseToLowerCase(str string, connector rune) string { if len(str) == 0 { return "" } buf := &bytes.Buffer{} var prev, r0, r1 rune var size int r0 = connector for len(str) > 0 { prev = r0 r0, size = utf8.DecodeRuneInString(str) str = str[size:] switch { case r0 == utf8.RuneError: buf.WriteRune(r0) case unicode.IsUpper(r0): if prev != connector && !unicode.IsNumber(prev) { buf.WriteRune(connector) } buf.WriteRune(unicode.ToLower(r0)) if len(str) == 0 { break } r0, size = utf8.DecodeRuneInString(str) str = str[size:] if !unicode.IsUpper(r0) { buf.WriteRune(r0) break } // find next non-upper-case character and insert connector properly. // it's designed to convert `HTTPServer` to `http_server`. // if there are more than 2 adjacent upper case characters in a word, // treat them as an abbreviation plus a normal word. for len(str) > 0 { r1 = r0 r0, size = utf8.DecodeRuneInString(str) str = str[size:] if r0 == utf8.RuneError { buf.WriteRune(unicode.ToLower(r1)) buf.WriteRune(r0) break } if !unicode.IsUpper(r0) { if isConnector(r0) { r0 = connector buf.WriteRune(unicode.ToLower(r1)) } else if unicode.IsNumber(r0) { // treat a number as an upper case rune // so that both `http2xx` and `HTTP2XX` can be converted to `http_2xx`. buf.WriteRune(unicode.ToLower(r1)) buf.WriteRune(connector) buf.WriteRune(r0) } else { buf.WriteRune(connector) buf.WriteRune(unicode.ToLower(r1)) buf.WriteRune(r0) } break } buf.WriteRune(unicode.ToLower(r1)) } if len(str) == 0 || r0 == connector { buf.WriteRune(unicode.ToLower(r0)) } case unicode.IsNumber(r0): if prev != connector && !unicode.IsNumber(prev) { buf.WriteRune(connector) } buf.WriteRune(r0) default: if isConnector(r0) { r0 = connector } buf.WriteRune(r0) } } return buf.String() } func isConnector(r rune) bool { return r == '-' || r == '_' || unicode.IsSpace(r) } // SwapCase will swap characters case from upper to lower or lower to upper. func SwapCase(str string) string { var r rune var size int buf := &bytes.Buffer{} for len(str) > 0 { r, size = utf8.DecodeRuneInString(str) switch { case unicode.IsUpper(r): buf.WriteRune(unicode.ToLower(r)) case unicode.IsLower(r): buf.WriteRune(unicode.ToUpper(r)) default: buf.WriteRune(r) } str = str[size:] } return buf.String() } // FirstRuneToUpper converts first rune to upper case if necessary. func FirstRuneToUpper(str string) string { if str == "" { return str } r, size := utf8.DecodeRuneInString(str) if !unicode.IsLower(r) { return str } buf := &bytes.Buffer{} buf.WriteRune(unicode.ToUpper(r)) buf.WriteString(str[size:]) return buf.String() } // FirstRuneToLower converts first rune to lower case if necessary. func FirstRuneToLower(str string) string { if str == "" { return str } r, size := utf8.DecodeRuneInString(str) if !unicode.IsUpper(r) { return str } buf := &bytes.Buffer{} buf.WriteRune(unicode.ToLower(r)) buf.WriteString(str[size:]) return buf.String() } // Shuffle randomizes runes in a string and returns the result. // It uses default random source in `math/rand`. func Shuffle(str string) string { if str == "" { return str } runes := []rune(str) index := 0 for i := len(runes) - 1; i > 0; i-- { index = rand.Intn(i + 1) if i != index { runes[i], runes[index] = runes[index], runes[i] } } return string(runes) } // ShuffleSource randomizes runes in a string with given random source. func ShuffleSource(str string, src rand.Source) string { if str == "" { return str } runes := []rune(str) index := 0 r := rand.New(src) for i := len(runes) - 1; i > 0; i-- { index = r.Intn(i + 1) if i != index { runes[i], runes[index] = runes[index], runes[i] } } return string(runes) } // Successor returns the successor to string. // // If there is one alphanumeric rune is found in string, increase the rune by 1. // If increment generates a "carry", the rune to the left of it is incremented. // This process repeats until there is no carry, adding an additional rune if necessary. // // If there is no alphanumeric rune, the rightmost rune will be increased by 1 // regardless whether the result is a valid rune or not. // // Only following characters are alphanumeric. // * a - z // * A - Z // * 0 - 9 // // Samples (borrowed from ruby's String#succ document): // "abcd" => "abce" // "THX1138" => "THX1139" // "<>" => "<>" // "1999zzz" => "2000aaa" // "ZZZ9999" => "AAAA0000" // "***" => "**+" func Successor(str string) string { if str == "" { return str } var r rune var i int carry := ' ' runes := []rune(str) l := len(runes) lastAlphanumeric := l for i = l - 1; i >= 0; i-- { r = runes[i] if ('a' <= r && r <= 'y') || ('A' <= r && r <= 'Y') || ('0' <= r && r <= '8') { runes[i]++ carry = ' ' lastAlphanumeric = i break } switch r { case 'z': runes[i] = 'a' carry = 'a' lastAlphanumeric = i case 'Z': runes[i] = 'A' carry = 'A' lastAlphanumeric = i case '9': runes[i] = '0' carry = '0' lastAlphanumeric = i } } // Needs to add one character for carry. if i < 0 && carry != ' ' { buf := &bytes.Buffer{} buf.Grow(l + 4) // Reserve enough space for write. if lastAlphanumeric != 0 { buf.WriteString(str[:lastAlphanumeric]) } buf.WriteRune(carry) for _, r = range runes[lastAlphanumeric:] { buf.WriteRune(r) } return buf.String() } // No alphanumeric character. Simply increase last rune's value. if lastAlphanumeric == l { runes[l-1]++ } return string(runes) }