package naming import ( "strings" "unicode" ) // ToSnakeCase converts a string to snake_case func ToSnakeCase(s string) string { var result []rune for i, r := range s { if unicode.IsUpper(r) { if i > 0 { result = append(result, '_') } result = append(result, unicode.ToLower(r)) } else { result = append(result, r) } } return string(result) } // ToPascalCase converts snake_case to PascalCase func ToPascalCase(s string) string { parts := strings.Split(s, "_") for i, part := range parts { if len(part) > 0 { parts[i] = strings.ToUpper(part[:1]) + part[1:] } } return strings.Join(parts, "") } // Singularize converts a plural word to singular // Uses common English pluralization rules func Singularize(word string) string { // Handle empty string if word == "" { return word } // Common irregular plurals irregulars := map[string]string{ "people": "person", "men": "man", "women": "woman", "children": "child", "teeth": "tooth", "feet": "foot", "mice": "mouse", "geese": "goose", "oxen": "ox", "sheep": "sheep", "fish": "fish", "deer": "deer", "series": "series", "species": "species", "quizzes": "quiz", "analyses": "analysis", "diagnoses": "diagnosis", "oases": "oasis", "theses": "thesis", "crises": "crisis", "phenomena": "phenomenon", "criteria": "criterion", "data": "datum", } lower := strings.ToLower(word) if singular, ok := irregulars[lower]; ok { return preserveCase(word, singular) } // Handle words ending in 'ies' -> 'y' if strings.HasSuffix(lower, "ies") && len(word) > 3 { return word[:len(word)-3] + "y" } // Handle words ending in 'ves' -> 'fe' or 'f' if strings.HasSuffix(lower, "ves") && len(word) > 3 { base := word[:len(word)-3] // Common words that end in 'fe' if strings.HasSuffix(strings.ToLower(base), "li") || strings.HasSuffix(strings.ToLower(base), "wi") || strings.HasSuffix(strings.ToLower(base), "kni") { return base + "fe" } return base + "f" } // Handle words ending in 'xes', 'ses', 'shes', 'ches' -> remove 'es' if strings.HasSuffix(lower, "xes") || strings.HasSuffix(lower, "ses") || strings.HasSuffix(lower, "shes") || strings.HasSuffix(lower, "ches") { if len(word) > 2 { return word[:len(word)-2] } } // Handle words ending in 'oes' -> 'o' if strings.HasSuffix(lower, "oes") && len(word) > 3 { return word[:len(word)-2] } // Handle simple 's' suffix if strings.HasSuffix(lower, "s") && len(word) > 1 { // Don't remove 's' from words that naturally end in 's' if !strings.HasSuffix(lower, "ss") && !strings.HasSuffix(lower, "us") && !strings.HasSuffix(lower, "is") { return word[:len(word)-1] } } return word } // Pluralize converts a singular word to plural func Pluralize(word string) string { if word == "" { return word } // Common irregular plurals irregulars := map[string]string{ "person": "people", "man": "men", "woman": "women", "child": "children", "tooth": "teeth", "foot": "feet", "mouse": "mice", "goose": "geese", "ox": "oxen", "sheep": "sheep", "fish": "fish", "deer": "deer", "series": "series", "species": "species", "quiz": "quizzes", "analysis": "analyses", "diagnosis": "diagnoses", "oasis": "oases", "thesis": "theses", "crisis": "crises", "phenomenon": "phenomena", "criterion": "criteria", "datum": "data", } lower := strings.ToLower(word) if plural, ok := irregulars[lower]; ok { return preserveCase(word, plural) } // Already plural (ends in 's' after a consonant, but not 'ss', 'us', 'is') // Skip this check if word ends in 'ss' (like 'class', 'glass') if strings.HasSuffix(lower, "s") && !strings.HasSuffix(lower, "ss") && !strings.HasSuffix(lower, "us") && !strings.HasSuffix(lower, "is") { return word } // Handle words ending in 'y' -> 'ies' if strings.HasSuffix(lower, "y") && len(word) > 1 { prevChar := rune(lower[len(lower)-2]) if !isVowel(prevChar) { return word[:len(word)-1] + "ies" } } // Handle words ending in 'f' or 'fe' -> 'ves' if strings.HasSuffix(lower, "f") { return word[:len(word)-1] + "ves" } if strings.HasSuffix(lower, "fe") { return word[:len(word)-2] + "ves" } // Handle words ending in 'o' -> 'oes' if strings.HasSuffix(lower, "o") && len(word) > 1 { prevChar := rune(lower[len(lower)-2]) if !isVowel(prevChar) { return word + "es" } } // Handle words ending in 'x', 's', 'sh', 'ch' -> add 'es' if strings.HasSuffix(lower, "x") || strings.HasSuffix(lower, "s") || strings.HasSuffix(lower, "sh") || strings.HasSuffix(lower, "ch") { return word + "es" } // Default: just add 's' return word + "s" } // preserveCase preserves the case pattern of the original word func preserveCase(original, replacement string) string { if len(original) == 0 { return replacement } if unicode.IsUpper(rune(original[0])) { return strings.ToUpper(replacement[:1]) + replacement[1:] } return replacement } // isVowel checks if a rune is a vowel func isVowel(r rune) bool { vowels := "aeiouAEIOU" return strings.ContainsRune(vowels, r) } // SingularizeTableName converts a table name to its singular form // Handles snake_case table names func SingularizeTableName(tableName string) string { parts := strings.Split(tableName, "_") if len(parts) > 0 { // Only singularize the last part lastIdx := len(parts) - 1 parts[lastIdx] = Singularize(parts[lastIdx]) } return strings.Join(parts, "_") }