initial public commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
package inflector
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var columnifyRemoveRegex = regexp.MustCompile(`[^\w\.\*\-\_\@\#]+`)
|
||||
var snakecaseSplitRegex = regexp.MustCompile(`[\W_]+`)
|
||||
var usernamifySplitRegex = regexp.MustCompile(`\W+`)
|
||||
|
||||
// UcFirst converts the first character of a string into uppercase.
|
||||
func UcFirst(str string) string {
|
||||
if str == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
s := []rune(str)
|
||||
|
||||
return string(unicode.ToUpper(s[0])) + string(s[1:])
|
||||
}
|
||||
|
||||
// Columnify strips invalid db identifier characters.
|
||||
func Columnify(str string) string {
|
||||
return columnifyRemoveRegex.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
// Sentenize converts and normalizes string into a sentence.
|
||||
func Sentenize(str string) string {
|
||||
str = strings.TrimSpace(str)
|
||||
if str == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
s := []rune(str)
|
||||
sentence := string(unicode.ToUpper(s[0])) + string(s[1:])
|
||||
|
||||
lastChar := string(s[len(s)-1:])
|
||||
if lastChar != "." && lastChar != "?" && lastChar != "!" {
|
||||
return sentence + "."
|
||||
}
|
||||
|
||||
return sentence
|
||||
}
|
||||
|
||||
// Sanitize sanitizes `str` by removing all characters satisfying `removePattern`.
|
||||
// Returns an error if the pattern is not valid regex string.
|
||||
func Sanitize(str string, removePattern string) (string, error) {
|
||||
exp, err := regexp.Compile(removePattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return exp.ReplaceAllString(str, ""), nil
|
||||
}
|
||||
|
||||
// Snakecase removes all non word characters and converts any english text into a snakecase.
|
||||
// "ABBREVIATIONS" are preserved, eg. "myTestDB" will become "my_test_db".
|
||||
func Snakecase(str string) string {
|
||||
var result strings.Builder
|
||||
|
||||
// split at any non word character and underscore
|
||||
words := snakecaseSplitRegex.Split(str, -1)
|
||||
|
||||
for _, word := range words {
|
||||
if word == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if result.Len() > 0 {
|
||||
result.WriteString("_")
|
||||
}
|
||||
|
||||
for i, c := range word {
|
||||
if unicode.IsUpper(c) && i > 0 &&
|
||||
// is not a following uppercase character
|
||||
!unicode.IsUpper(rune(word[i-1])) {
|
||||
result.WriteString("_")
|
||||
}
|
||||
|
||||
result.WriteRune(c)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.ToLower(result.String())
|
||||
}
|
||||
|
||||
// Usernamify generates a properly formatted username from the provided string.
|
||||
// Returns "unknown" if `str` is empty or contains only non word characters.
|
||||
//
|
||||
// ```go
|
||||
// Usernamify("John Doe, hello") // "john.doe.hello"
|
||||
// ```
|
||||
func Usernamify(str string) string {
|
||||
// split at any non word character
|
||||
words := usernamifySplitRegex.Split(strings.ToLower(str), -1)
|
||||
|
||||
// concatenate any non empty word with a dot
|
||||
var result strings.Builder
|
||||
for _, word := range words {
|
||||
if word == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if result.Len() > 0 {
|
||||
result.WriteString(".")
|
||||
}
|
||||
|
||||
result.WriteString(word)
|
||||
}
|
||||
|
||||
if result.Len() == 0 {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package inflector_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
)
|
||||
|
||||
func TestUcFirst(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
val string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"Test", "Test"},
|
||||
{"test", "Test"},
|
||||
{"test test2", "Test test2"},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
if result := inflector.UcFirst(scenario.val); result != scenario.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, scenario.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumnify(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
val string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{" ", ""},
|
||||
{"123", "123"},
|
||||
{"Test.", "Test."},
|
||||
{" test ", "test"},
|
||||
{"test1.test2", "test1.test2"},
|
||||
{"@test!abc", "@testabc"},
|
||||
{"#test?abc", "#testabc"},
|
||||
{"123test(123)#", "123test123#"},
|
||||
{"test1--test2", "test1--test2"},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
if result := inflector.Columnify(scenario.val); result != scenario.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, scenario.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSentenize(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
val string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{" ", ""},
|
||||
{"Test", "Test."},
|
||||
{" test ", "Test."},
|
||||
{"hello world", "Hello world."},
|
||||
{"hello world.", "Hello world."},
|
||||
{"hello world!", "Hello world!"},
|
||||
{"hello world?", "Hello world?"},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
if result := inflector.Sentenize(scenario.val); result != scenario.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, scenario.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitize(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
val string
|
||||
pattern string
|
||||
expected string
|
||||
expectErr bool
|
||||
}{
|
||||
{"", ``, "", false},
|
||||
{" ", ``, " ", false},
|
||||
{" ", ` `, "", false},
|
||||
{"", `[A-Z]`, "", false},
|
||||
{"abcABC", `[A-Z]`, "abc", false},
|
||||
{"abcABC", `[A-Z`, "", true}, // invlid pattern
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result, err := inflector.Sanitize(scenario.val, scenario.pattern)
|
||||
hasErr := err != nil
|
||||
|
||||
if scenario.expectErr != hasErr {
|
||||
if scenario.expectErr {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
} else {
|
||||
t.Errorf("(%d) Didn't expect error, got", err)
|
||||
}
|
||||
}
|
||||
|
||||
if result != scenario.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, scenario.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnakecase(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
val string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{" ", ""},
|
||||
{"!@#$%^", ""},
|
||||
{"...", ""},
|
||||
{"_", ""},
|
||||
{"John Doe", "john_doe"},
|
||||
{"John_Doe", "john_doe"},
|
||||
{".a!b@c#d$e%123. ", "a_b_c_d_e_123"},
|
||||
{"HelloWorld", "hello_world"},
|
||||
{"HelloWorld1HelloWorld2", "hello_world1_hello_world2"},
|
||||
{"TEST", "test"},
|
||||
{"testABR", "test_abr"},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
if result := inflector.Snakecase(scenario.val); result != scenario.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, scenario.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsernamify(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
val string
|
||||
expected string
|
||||
}{
|
||||
{"", "unknown"},
|
||||
{" ", "unknown"},
|
||||
{"!@#$%^", "unknown"},
|
||||
{"...", "unknown"},
|
||||
{"_", "_"}, // underscore is valid word character
|
||||
{"John Doe", "john.doe"},
|
||||
{"John_Doe", "john_doe"},
|
||||
{".a!b@c#d$e%123. ", "a.b.c.d.e.123"},
|
||||
{"Hello, world", "hello.world"},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
if result := inflector.Usernamify(scenario.val); result != scenario.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, scenario.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user