generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fix weird case transforms like FTLName -> fTLName (#901)
This should now be determinstically mapped to upper/lower camel for types and functions/values respectively. Note that this casing differs from native Go casing in that initialisms/acronyms are never all-caps. That is, FTL casing is: Ftl not FTL. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
d83db73
commit be2bda1
Showing
26 changed files
with
424 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Package strcase provides programming case conversion functions for strings. | ||
// | ||
// These case conversion functions are used to deterministically convert strings | ||
// to various programming cases. | ||
package strcase | ||
|
||
// NOTE: This code is from https://github.com/fatih/camelcase. MIT license. | ||
|
||
import ( | ||
"strings" | ||
"unicode" | ||
"unicode/utf8" | ||
) | ||
|
||
func title(s string) string { | ||
r, n := utf8.DecodeRuneInString(s) | ||
return string(unicode.ToTitle(r)) + strings.ToLower(s[n:]) | ||
} | ||
|
||
func ToLowerCamel(s string) string { | ||
parts := split(s) | ||
for i := range parts { | ||
parts[i] = title(parts[i]) | ||
} | ||
return strings.ToLower(parts[0]) + strings.Join(parts[1:], "") | ||
} | ||
|
||
func ToUpperCamel(s string) string { | ||
parts := split(s) | ||
for i := range parts { | ||
parts[i] = title(parts[i]) | ||
} | ||
return strings.Join(parts, "") | ||
} | ||
|
||
func ToLowerSnake(s string) string { | ||
parts := split(s) | ||
out := make([]string, 0, len(parts)*2) | ||
for i := range parts { | ||
if parts[i] == "_" { | ||
continue | ||
} | ||
out = append(out, strings.ToLower(parts[i])) | ||
} | ||
return strings.Join(out, "_") | ||
} | ||
|
||
func ToUpperSnake(s string) string { | ||
parts := split(s) | ||
out := make([]string, 0, len(parts)*2) | ||
for i := range parts { | ||
if parts[i] == "_" { | ||
continue | ||
} | ||
out = append(out, strings.ToUpper(parts[i])) | ||
} | ||
return strings.Join(out, "_") | ||
} | ||
|
||
func ToLowerKebab(s string) string { | ||
parts := split(s) | ||
out := make([]string, 0, len(parts)*2) | ||
for i := range parts { | ||
if parts[i] == "-" || parts[i] == "_" { | ||
continue | ||
} | ||
out = append(out, strings.ToLower(parts[i])) | ||
} | ||
return strings.Join(out, "-") | ||
} | ||
|
||
func ToUpperKebab(s string) string { | ||
parts := split(s) | ||
out := make([]string, 0, len(parts)*2) | ||
for i := range parts { | ||
if parts[i] == "-" || parts[i] == "_" { | ||
continue | ||
} | ||
out = append(out, strings.ToUpper(parts[i])) | ||
} | ||
return strings.Join(out, "-") | ||
} | ||
|
||
// Splits a camelcase word and returns a list of words. It also | ||
// supports digits. Both lower camel case and upper camel case are supported. | ||
// For more info please check: http://en.wikipedia.org/wiki/CamelCase | ||
// | ||
// Examples | ||
// | ||
// "" => [""] | ||
// "lowercase" => ["lowercase"] | ||
// "Class" => ["Class"] | ||
// "MyClass" => ["My", "Class"] | ||
// "MyC" => ["My", "C"] | ||
// "HTML" => ["HTML"] | ||
// "PDFLoader" => ["PDF", "Loader"] | ||
// "AString" => ["A", "String"] | ||
// "SimpleXMLParser" => ["Simple", "XML", "Parser"] | ||
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"] | ||
// "GL11Version" => ["GL", "11", "Version"] | ||
// "99Bottles" => ["99", "Bottles"] | ||
// "May5" => ["May", "5"] | ||
// "BFG9000" => ["BFG", "9000"] | ||
// "BöseÜberraschung" => ["Böse", "Überraschung"] | ||
// "Two spaces" => ["Two", " ", "spaces"] | ||
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] | ||
// | ||
// Splitting rules | ||
// | ||
// 1. If string is not valid UTF-8, return it without splitting as | ||
// single item array. | ||
// 2. Assign all unicode characters into one of 4 sets: lower case | ||
// letters, upper case letters, numbers, and all other characters. | ||
// 3. Iterate through characters of string, introducing splits | ||
// between adjacent characters that belong to different sets. | ||
// 4. Iterate through array of split strings, and if a given string | ||
// is upper case: | ||
// if subsequent string is lower case: | ||
// move last character of upper case string to beginning of | ||
// lower case string | ||
func split(src string) (entries []string) { | ||
// don't split invalid utf8 | ||
if !utf8.ValidString(src) { | ||
return []string{src} | ||
} | ||
entries = []string{} | ||
var runes [][]rune | ||
lastClass := 0 | ||
// split into fields based on class of unicode character | ||
for _, r := range src { | ||
var class int | ||
switch { | ||
case unicode.IsLower(r): | ||
class = 1 | ||
case unicode.IsUpper(r): | ||
class = 2 | ||
case unicode.IsDigit(r): | ||
class = 3 | ||
default: | ||
class = 4 | ||
} | ||
if class == lastClass { | ||
runes[len(runes)-1] = append(runes[len(runes)-1], r) | ||
} else { | ||
runes = append(runes, []rune{r}) | ||
} | ||
lastClass = class | ||
} | ||
// handle upper case -> lower case sequences, e.g. | ||
// "PDFL", "oader" -> "PDF", "Loader" | ||
for i := 0; i < len(runes)-1; i++ { | ||
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { | ||
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) | ||
runes[i] = runes[i][:len(runes[i])-1] | ||
} | ||
} | ||
// construct []string from results | ||
for _, s := range runes { | ||
if len(s) > 0 { | ||
entries = append(entries, string(s)) | ||
} | ||
} | ||
return entries | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package strcase | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/alecthomas/assert/v2" | ||
) | ||
|
||
func TestCamelCase(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected []string | ||
}{ | ||
{"lowercase", []string{"lowercase"}}, | ||
{"Class", []string{"Class"}}, | ||
{"MyClass", []string{"My", "Class"}}, | ||
{"MyC", []string{"My", "C"}}, | ||
{"HTML", []string{"HTML"}}, | ||
{"PDFLoader", []string{"PDF", "Loader"}}, | ||
{"AString", []string{"A", "String"}}, | ||
{"SimpleXMLParser", []string{"Simple", "XML", "Parser"}}, | ||
{"vimRPCPlugin", []string{"vim", "RPC", "Plugin"}}, | ||
{"GL11Version", []string{"GL", "11", "Version"}}, | ||
{"99Bottles", []string{"99", "Bottles"}}, | ||
{"May5", []string{"May", "5"}}, | ||
{"BFG9000", []string{"BFG", "9000"}}, | ||
{"BöseÜberraschung", []string{"Böse", "Überraschung"}}, | ||
{"Two spaces", []string{"Two", " ", "spaces"}}, | ||
{"BadUTF8\xe2\xe2\xa1", []string{"BadUTF8\xe2\xe2\xa1"}}, | ||
{"snake_case", []string{"snake", "_", "case"}}, | ||
} { | ||
actual := split(tt.input) | ||
assert.Equal(t, tt.expected, actual, "camelCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} | ||
|
||
func TestLowerCamelCase(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected string | ||
}{ | ||
{"lowercase", "lowercase"}, | ||
{"Class", "class"}, | ||
{"MyClass", "myClass"}, | ||
{"MyC", "myC"}, | ||
{"HTML", "html"}, | ||
{"PDFLoader", "pdfLoader"}, | ||
{"AString", "aString"}, | ||
{"SimpleXMLParser", "simpleXmlParser"}, | ||
{"vimRPCPlugin", "vimRpcPlugin"}, | ||
{"GL11Version", "gl11Version"}, | ||
{"99Bottles", "99Bottles"}, | ||
{"May5", "may5"}, | ||
{"BFG9000", "bfg9000"}, | ||
{"BöseÜberraschung", "böseÜberraschung"}, | ||
{"snake_case", "snake_Case"}, | ||
} { | ||
actual := ToLowerCamel(tt.input) | ||
assert.Equal(t, tt.expected, actual, "LowerCamelCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} | ||
|
||
func TestUpperCamelCase(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected string | ||
}{ | ||
{"lowercase", "Lowercase"}, | ||
{"Class", "Class"}, | ||
{"MyClass", "MyClass"}, | ||
{"MyC", "MyC"}, | ||
{"HTML", "Html"}, | ||
{"PDFLoader", "PdfLoader"}, | ||
{"AString", "AString"}, | ||
{"SimpleXMLParser", "SimpleXmlParser"}, | ||
{"vimRPCPlugin", "VimRpcPlugin"}, | ||
{"GL11Version", "Gl11Version"}, | ||
{"99Bottles", "99Bottles"}, | ||
{"May5", "May5"}, | ||
{"BFG9000", "Bfg9000"}, | ||
{"BöseÜberraschung", "BöseÜberraschung"}, | ||
{"snake_case", "Snake_Case"}, | ||
} { | ||
actual := ToUpperCamel(tt.input) | ||
assert.Equal(t, tt.expected, actual, "UpperCamelCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} | ||
|
||
func TestLowerSnake(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected string | ||
}{ | ||
{"lowercase", "lowercase"}, | ||
{"Class", "class"}, | ||
{"MyClass", "my_class"}, | ||
{"MyC", "my_c"}, | ||
{"HTML", "html"}, | ||
{"PDFLoader", "pdf_loader"}, | ||
{"AString", "a_string"}, | ||
{"SimpleXMLParser", "simple_xml_parser"}, | ||
{"vimRPCPlugin", "vim_rpc_plugin"}, | ||
{"GL11Version", "gl_11_version"}, | ||
{"99Bottles", "99_bottles"}, | ||
{"May5", "may_5"}, | ||
{"BFG9000", "bfg_9000"}, | ||
{"BöseÜberraschung", "böse_überraschung"}, | ||
{"snake_case", "snake_case"}, | ||
} { | ||
actual := ToLowerSnake(tt.input) | ||
assert.Equal(t, tt.expected, actual, "LowerSnakeCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} | ||
|
||
func TestUpperSnake(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected string | ||
}{ | ||
{"lowercase", "LOWERCASE"}, | ||
{"Class", "CLASS"}, | ||
{"MyClass", "MY_CLASS"}, | ||
{"MyC", "MY_C"}, | ||
{"HTML", "HTML"}, | ||
{"PDFLoader", "PDF_LOADER"}, | ||
{"AString", "A_STRING"}, | ||
{"SimpleXMLParser", "SIMPLE_XML_PARSER"}, | ||
{"vimRPCPlugin", "VIM_RPC_PLUGIN"}, | ||
{"GL11Version", "GL_11_VERSION"}, | ||
{"99Bottles", "99_BOTTLES"}, | ||
{"May5", "MAY_5"}, | ||
{"BFG9000", "BFG_9000"}, | ||
{"BöseÜberraschung", "BÖSE_ÜBERRASCHUNG"}, | ||
{"snake_case", "SNAKE_CASE"}, | ||
} { | ||
actual := ToUpperSnake(tt.input) | ||
assert.Equal(t, tt.expected, actual, "UpperSnakeCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} | ||
|
||
func TestLowerKebabCase(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected string | ||
}{ | ||
{"lowercase", "lowercase"}, | ||
{"Class", "class"}, | ||
{"MyClass", "my-class"}, | ||
{"MyC", "my-c"}, | ||
{"HTML", "html"}, | ||
{"PDFLoader", "pdf-loader"}, | ||
{"AString", "a-string"}, | ||
{"SimpleXMLParser", "simple-xml-parser"}, | ||
{"vimRPCPlugin", "vim-rpc-plugin"}, | ||
{"GL11Version", "gl-11-version"}, | ||
{"99Bottles", "99-bottles"}, | ||
{"May5", "may-5"}, | ||
{"BFG9000", "bfg-9000"}, | ||
{"BöseÜberraschung", "böse-überraschung"}, | ||
{"snake_case", "snake-case"}, | ||
} { | ||
actual := ToLowerKebab(tt.input) | ||
assert.Equal(t, tt.expected, actual, "LowerKebabCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} | ||
|
||
func TestUpperKebabCase(t *testing.T) { | ||
for _, tt := range []struct { | ||
input string | ||
expected string | ||
}{ | ||
{"lowercase", "LOWERCASE"}, | ||
{"Class", "CLASS"}, | ||
{"MyClass", "MY-CLASS"}, | ||
{"MyC", "MY-C"}, | ||
{"HTML", "HTML"}, | ||
{"PDFLoader", "PDF-LOADER"}, | ||
{"AString", "A-STRING"}, | ||
{"SimpleXMLParser", "SIMPLE-XML-PARSER"}, | ||
{"vimRPCPlugin", "VIM-RPC-PLUGIN"}, | ||
{"GL11Version", "GL-11-VERSION"}, | ||
{"99Bottles", "99-BOTTLES"}, | ||
{"May5", "MAY-5"}, | ||
{"BFG9000", "BFG-9000"}, | ||
{"BöseÜberraschung", "BÖSE-ÜBERRASCHUNG"}, | ||
{"snake_case", "SNAKE-CASE"}, | ||
} { | ||
actual := ToUpperKebab(tt.input) | ||
assert.Equal(t, tt.expected, actual, "UpperKebabCase(%q) = %v; want %v", tt.input, actual, tt.expected) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.