Some checks failed
CI / Lint (push) Successful in -27m59s
CI / Test (1.25) (push) Successful in -27m46s
CI / Test (1.24) (push) Failing after 59s
CI / Build (push) Successful in -28m14s
Integration Tests / Integration Tests (push) Failing after -28m16s
Release / Build and Release (push) Successful in 1m1s
feat(templ): ✨ added templ to command line that reads go template and outputs code Reviewed-on: #1 Co-authored-by: Hein <hein.puth@gmail.com> Co-committed-by: Hein <hein.puth@gmail.com>
317 lines
6.9 KiB
Go
317 lines
6.9 KiB
Go
package template
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// ToUpper converts a string to uppercase
|
|
func ToUpper(s string) string {
|
|
return strings.ToUpper(s)
|
|
}
|
|
|
|
// ToLower converts a string to lowercase
|
|
func ToLower(s string) string {
|
|
return strings.ToLower(s)
|
|
}
|
|
|
|
// ToCamelCase converts snake_case to camelCase
|
|
// Examples: user_name → userName, http_request → httpRequest
|
|
func ToCamelCase(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
parts := strings.Split(s, "_")
|
|
for i, part := range parts {
|
|
if i == 0 {
|
|
parts[i] = strings.ToLower(part)
|
|
} else {
|
|
parts[i] = capitalize(part)
|
|
}
|
|
}
|
|
return strings.Join(parts, "")
|
|
}
|
|
|
|
// ToPascalCase converts snake_case to PascalCase
|
|
// Examples: user_name → UserName, http_request → HTTPRequest
|
|
func ToPascalCase(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
parts := strings.Split(s, "_")
|
|
for i, part := range parts {
|
|
parts[i] = capitalize(part)
|
|
}
|
|
return strings.Join(parts, "")
|
|
}
|
|
|
|
// ToSnakeCase converts PascalCase/camelCase to snake_case
|
|
// Examples: UserName → user_name, HTTPRequest → http_request
|
|
func ToSnakeCase(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
var result strings.Builder
|
|
var prevUpper bool
|
|
var nextUpper bool
|
|
|
|
runes := []rune(s)
|
|
for i, r := range runes {
|
|
isUpper := unicode.IsUpper(r)
|
|
|
|
if i+1 < len(runes) {
|
|
nextUpper = unicode.IsUpper(runes[i+1])
|
|
} else {
|
|
nextUpper = false
|
|
}
|
|
|
|
if i > 0 && isUpper {
|
|
// Add underscore before uppercase letter if:
|
|
// 1. Previous char was lowercase, OR
|
|
// 2. Next char is lowercase (end of acronym)
|
|
if !prevUpper || (!nextUpper && i+1 < len(runes)) {
|
|
result.WriteRune('_')
|
|
}
|
|
}
|
|
|
|
result.WriteRune(unicode.ToLower(r))
|
|
prevUpper = isUpper
|
|
}
|
|
|
|
return result.String()
|
|
}
|
|
|
|
// ToKebabCase converts snake_case or PascalCase/camelCase to kebab-case
|
|
// Examples: user_name → user-name, UserName → user-name
|
|
func ToKebabCase(s string) string {
|
|
// First convert to snake_case, then replace underscores with hyphens
|
|
snakeCase := ToSnakeCase(s)
|
|
return strings.ReplaceAll(snakeCase, "_", "-")
|
|
}
|
|
|
|
// Title capitalizes the first letter of each word
|
|
func Title(s string) string {
|
|
caser := cases.Title(language.English)
|
|
src := []byte(s)
|
|
dest := []byte(s)
|
|
_, _, _ = caser.Transform(dest, src, true)
|
|
return string(dest)
|
|
}
|
|
|
|
// Pluralize converts a singular word to plural
|
|
// Basic implementation with common English rules
|
|
func Pluralize(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
// Special cases
|
|
irregular := map[string]string{
|
|
"person": "people",
|
|
"child": "children",
|
|
"tooth": "teeth",
|
|
"foot": "feet",
|
|
"man": "men",
|
|
"woman": "women",
|
|
"mouse": "mice",
|
|
"goose": "geese",
|
|
"ox": "oxen",
|
|
"datum": "data",
|
|
"medium": "media",
|
|
"analysis": "analyses",
|
|
"crisis": "crises",
|
|
"status": "statuses",
|
|
}
|
|
|
|
if plural, ok := irregular[strings.ToLower(s)]; ok {
|
|
return plural
|
|
}
|
|
|
|
// Already plural (ends in 's' but not 'ss' or 'us')
|
|
if strings.HasSuffix(s, "s") && !strings.HasSuffix(s, "ss") && !strings.HasSuffix(s, "us") {
|
|
return s
|
|
}
|
|
|
|
// Words ending in s, x, z, ch, sh
|
|
if strings.HasSuffix(s, "s") || strings.HasSuffix(s, "x") ||
|
|
strings.HasSuffix(s, "z") || strings.HasSuffix(s, "ch") ||
|
|
strings.HasSuffix(s, "sh") {
|
|
return s + "es"
|
|
}
|
|
|
|
// Words ending in consonant + y
|
|
if len(s) >= 2 && strings.HasSuffix(s, "y") {
|
|
prevChar := s[len(s)-2]
|
|
if !isVowel(prevChar) {
|
|
return s[:len(s)-1] + "ies"
|
|
}
|
|
}
|
|
|
|
// Words ending in f or fe
|
|
if strings.HasSuffix(s, "f") {
|
|
return s[:len(s)-1] + "ves"
|
|
}
|
|
if strings.HasSuffix(s, "fe") {
|
|
return s[:len(s)-2] + "ves"
|
|
}
|
|
|
|
// Words ending in consonant + o
|
|
if len(s) >= 2 && strings.HasSuffix(s, "o") {
|
|
prevChar := s[len(s)-2]
|
|
if !isVowel(prevChar) {
|
|
return s + "es"
|
|
}
|
|
}
|
|
|
|
// Default: add 's'
|
|
return s + "s"
|
|
}
|
|
|
|
// Singularize converts a plural word to singular
|
|
// Basic implementation with common English rules
|
|
func Singularize(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
// Special cases
|
|
irregular := map[string]string{
|
|
"people": "person",
|
|
"children": "child",
|
|
"teeth": "tooth",
|
|
"feet": "foot",
|
|
"men": "man",
|
|
"women": "woman",
|
|
"mice": "mouse",
|
|
"geese": "goose",
|
|
"oxen": "ox",
|
|
"data": "datum",
|
|
"media": "medium",
|
|
"analyses": "analysis",
|
|
"crises": "crisis",
|
|
"statuses": "status",
|
|
}
|
|
|
|
if singular, ok := irregular[strings.ToLower(s)]; ok {
|
|
return singular
|
|
}
|
|
|
|
// Words ending in ies
|
|
if strings.HasSuffix(s, "ies") && len(s) > 3 {
|
|
return s[:len(s)-3] + "y"
|
|
}
|
|
|
|
// Words ending in ves
|
|
if strings.HasSuffix(s, "ves") {
|
|
return s[:len(s)-3] + "f"
|
|
}
|
|
|
|
// Words ending in ses, xes, zes, ches, shes
|
|
if strings.HasSuffix(s, "ses") || strings.HasSuffix(s, "xes") ||
|
|
strings.HasSuffix(s, "zes") || strings.HasSuffix(s, "ches") ||
|
|
strings.HasSuffix(s, "shes") {
|
|
return s[:len(s)-2]
|
|
}
|
|
|
|
// Words ending in s (not ss)
|
|
if strings.HasSuffix(s, "s") && !strings.HasSuffix(s, "ss") {
|
|
return s[:len(s)-1]
|
|
}
|
|
|
|
// Already singular
|
|
return s
|
|
}
|
|
|
|
// Trim trims whitespace from both ends
|
|
func Trim(s string) string {
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
// TrimPrefix removes the prefix from the string if present
|
|
func TrimPrefix(s, prefix string) string {
|
|
return strings.TrimPrefix(s, prefix)
|
|
}
|
|
|
|
// TrimSuffix removes the suffix from the string if present
|
|
func TrimSuffix(s, suffix string) string {
|
|
return strings.TrimSuffix(s, suffix)
|
|
}
|
|
|
|
// Replace replaces occurrences of old with new (n times, or all if n < 0)
|
|
func Replace(s, old, newstr string, n int) string {
|
|
return strings.Replace(s, old, newstr, n)
|
|
}
|
|
|
|
// StringContains checks if substr is within s
|
|
func StringContains(s, substr string) bool {
|
|
return strings.Contains(s, substr)
|
|
}
|
|
|
|
// HasPrefix checks if string starts with prefix
|
|
func HasPrefix(s, prefix string) bool {
|
|
return strings.HasPrefix(s, prefix)
|
|
}
|
|
|
|
// HasSuffix checks if string ends with suffix
|
|
func HasSuffix(s, suffix string) bool {
|
|
return strings.HasSuffix(s, suffix)
|
|
}
|
|
|
|
// Split splits string by separator
|
|
func Split(s, sep string) []string {
|
|
return strings.Split(s, sep)
|
|
}
|
|
|
|
// Join joins string slice with separator
|
|
func Join(parts []string, sep string) string {
|
|
return strings.Join(parts, sep)
|
|
}
|
|
|
|
// capitalize capitalizes the first letter and handles common acronyms
|
|
func capitalize(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
upper := strings.ToUpper(s)
|
|
|
|
// Handle common acronyms
|
|
acronyms := map[string]bool{
|
|
"ID": true,
|
|
"UUID": true,
|
|
"GUID": true,
|
|
"URL": true,
|
|
"URI": true,
|
|
"HTTP": true,
|
|
"HTTPS": true,
|
|
"API": true,
|
|
"JSON": true,
|
|
"XML": true,
|
|
"SQL": true,
|
|
"HTML": true,
|
|
"CSS": true,
|
|
"RID": true,
|
|
}
|
|
|
|
if acronyms[upper] {
|
|
return upper
|
|
}
|
|
|
|
// Capitalize first letter
|
|
runes := []rune(s)
|
|
runes[0] = unicode.ToUpper(runes[0])
|
|
return string(runes)
|
|
}
|
|
|
|
// isVowel checks if a byte is a vowel
|
|
func isVowel(c byte) bool {
|
|
c = byte(unicode.ToLower(rune(c)))
|
|
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'
|
|
}
|