// General purpose "utilities" that act as my own "standard library" package basic import ( "fmt" "math/rand" "os" "reflect" "regexp" "sort" "strconv" "strings" "unicode" ) // Equivalent to Atoi, but returns int32 rather than (int, error) func Atoi32(s string) int32 { i, _ := strconv.ParseInt(s, 10, 32) return int32(i) } // Equivalent to Atoi, but returns int64 rather than (int, error) func Atoi64(s string) int64 { i, _ := strconv.ParseInt(s, 10, 64) return i } func GetPathParts(path string) []string { trimmed := strings.TrimPrefix(path, "/") return strings.Split(trimmed, "/") } // Takes a tree pointer and a slice of path segments to insert. // It looks for an existing child with the current segment name; if none is found, // it creates a new node on the tree. Then it recurses on the remaining segments. // // Ex: Generate tree nodes from url segments // `/app/examples/webpage` -> {"app", "examples", "webpage"} // `/app/examples/hello-world` -> {"app", "examples", "hello-world" // `/auth/login` -> {"auth", "login"} // // => // // root { // app { // examples { // webpage // hello-world // } // } // // auth { // login // } // } // // This function is used in the code generation process to generate `pageinfo` structs // from all known application page URLs. func AddStringPartsToTree(tree *Tree, parts []string) { if len(parts) == 0 { return } for i := range parts { if parts[i] == "" { parts[i] = "index" } } if tree.Children == nil { tree.Children = new([]Tree) } // Search for an existing child with the current part's name. var child *Tree for i := range *tree.Children { if (*tree.Children)[i].Name == parts[0] { child = &((*tree.Children)[i]) break } } // If no child is found, create a new one and append it. if child == nil { newNode := Tree{Name: parts[0]} *tree.Children = append(*tree.Children, newNode) child = &((*tree.Children)[len(*tree.Children)-1]) } AddStringPartsToTree(child, parts[1:]) } type Tree struct { Name string Children *[]Tree } func CapitalizeFirstLetter(s string) string { if s == "" { return s } // Convert the first rune to uppercase first := []rune(s)[0] return string(unicode.ToUpper(first)) + s[1:] } // Known compound surnames where a simple prefix rule would cause false positives // (e.g. "De" matches Dean/Dennis, "La" matches Laura/Lance). Add entries as needed. var compoundSurnames = map[string]string{ // De- "deangelo": "DeAngelo", "decarlo": "DeCarlo", "dejesus": "DeJesus", "deleon": "DeLeon", "deluca": "DeLuca", "demarco": "DeMarco", "depaul": "DePaul", "derosa": "DeRosa", "desantis": "DeSantis", "devries": "DeVries", "devris": "DeVris", "dewitt": "DeWitt", "deyoung": "DeYoung", // Di- "dicaprio": "DiCaprio", "dicarlo": "DiCarlo", "dimaggio": "DiMaggio", "dinapoli": "DiNapoli", "dipietro": "DiPietro", // La- "lafleur": "LaFleur", "lafrance": "LaFrance", "lamontagne": "LaMontagne", "laporte": "LaPorte", "larocca": "LaRocca", "larue": "LaRue", "lasalle": "LaSalle", // Le- "leblanc": "LeBlanc", "lebron": "LeBron", "legrand": "LeGrand", "lemay": "LeMay", // Lo- "lopresti": "LoPresti", // Du- "dubois": "DuBois", "dupont": "DuPont", "dupree": "DuPree", } // NormalizeName intelligently capitalizes a name field (first or last name). // Handles edge cases like O'Brian, McDonald, MacArthur, hyphenated names, // compound surnames (DeSantis, DiCaprio, LeBlanc), and suffixes (Jr, III). func NormalizeName(name string) string { name = strings.TrimSpace(name) if name == "" { return name } // If the name has mixed capitalization, assume the user typed it intentionally. hasUpper, hasLower := false, false for _, r := range name { if unicode.IsUpper(r) { hasUpper = true } else if unicode.IsLower(r) { hasLower = true } if hasUpper && hasLower { return name } } name = strings.ToLower(name) hyphenParts := strings.Split(name, "-") for i, hpart := range hyphenParts { words := strings.Fields(hpart) for j, word := range words { if mapped, ok := compoundSurnames[word]; ok { words[j] = mapped continue } runes := []rune(word) switch { case word == "ii" || word == "iii" || word == "iv" || word == "vi" || word == "vii" || word == "viii": words[j] = strings.ToUpper(word) case word == "jr" || word == "sr": words[j] = CapitalizeFirstLetter(word) + "." case strings.HasPrefix(word, "mc") && len(runes) >= 3: words[j] = "Mc" + string(unicode.ToUpper(runes[2])) + string(runes[3:]) case strings.HasPrefix(word, "mac") && len(runes) >= 5: words[j] = "Mac" + string(unicode.ToUpper(runes[3])) + string(runes[4:]) case strings.HasPrefix(word, "o'") && len(runes) >= 3: words[j] = "O'" + string(unicode.ToUpper(runes[2])) + string(runes[3:]) default: words[j] = string(unicode.ToUpper(runes[0])) + string(runes[1:]) } } hyphenParts[i] = strings.Join(words, " ") } return strings.Join(hyphenParts, "-") } var profanityList = map[string]bool{ // Common profanity — whole-word matches only, so substrings in real names // (e.g. "Massimo", "Dickens", "Cockburn") are not flagged. "ass": true, "arse": true, "asshole": true, "bastard": true, "bitch": true, "bollocks": true, "cock": true, "crap": true, "cunt": true, "damn": true, "dildo": true, "fag": true, "fuck": true, "fucker": true, "goddamn": true, "jackass": true, "motherfucker": true, "nigger": true, "nigga": true, "piss": true, "prick": true, "pussy": true, "shit": true, "slut": true, "tit": true, "tits": true, "twat": true, "wanker": true, "whore": true, } var leetReplacer = strings.NewReplacer( "0", "o", "1", "i", "3", "e", "4", "a", "5", "s", "7", "t", "8", "b", "@", "a", "$", "s", "!", "i", ) // NameContainsProfanity checks whether any whole word in the name matches a known profane term. // Words are split on spaces, hyphens, and apostrophes so that substrings within legitimate // names (e.g. "Massimo", "Dickens", "Cockburn") are not flagged. // Also normalizes leet speak substitutions (e.g. "b1tch", "a$$", "sh!t"). func NameContainsProfanity(name string) bool { name = strings.ToLower(name) parts := strings.FieldsFunc(name, func(r rune) bool { return r == ' ' || r == '-' || r == '\'' }) for _, part := range parts { if profanityList[part] { return true } // Check leet speak variant normalized := leetReplacer.Replace(part) if normalized != part && profanityList[normalized] { return true } } return false } func IntAbs(x int) int { if x < 0 { return -x } return x } func MakeURLParams(base string, params ...[2]string) string { output := base for i, v := range params { if i == 0 { output += "?" + v[0] + "=" + v[1] } else { output += "&" + v[0] + "=" + v[1] } } return output } func ToSnakeCase(s string) string { s = strings.ReplaceAll(s, " ", "_") return s } func SnakeCaseToTitleCase(s string) string { parts := strings.Split(s, "_") for i, part := range parts { parts[i] = CapitalizeFirstLetter(part) } return strings.Join(parts, " ") } func NullableToString[T any](i *T) string { if i == nil { return "" } return ToString(*i) } func ToString(i any) string { v := reflect.ValueOf(i) output := "" switch v.Kind() { case reflect.String: output = v.String() case reflect.Int: output = fmt.Sprintf("%d", v.Int()) case reflect.Float64: output = fmt.Sprintf("%f", v.Float()) case reflect.Bool: output = fmt.Sprintf("%t", v.Bool()) default: output = fmt.Sprintf("%v", i) } return output } func Int64ToStringWithCommas(i int64) string { str := strconv.Itoa(int(i)) negative := false if strings.HasPrefix(str, "-") { negative = true str = str[1:] } result := "" for i, char := range str { if i > 0 && (len(str)-i)%3 == 0 { result += "," } result += string(char) } if negative { result = "-" + result } return result } // Strips non-numeric characters from a string, then converts digits in string to int func StringToInt(input string) int { i, _ := strconv.Atoi(SanitizeNum(input)) return i } // Strips non-numeric characters from a string, then converts digits in string to int32 func StringToInt32(input string) int32 { return Atoi32(SanitizeNum(input)) } // Strips non-numeric characters from a string, then converts digits in string to int64 func StringToInt64(input string) int64 { return Atoi64(SanitizeNum(input)) } // ABC func SafeIndex[T any](index int, arr []T) T { var temp T if index > len(arr)-1 { return temp } return arr[index] } func SafeDereference[T any](value *T, optionalDefault ...T) T { if value != nil { return *value } if len(optionalDefault) > 0 { return optionalDefault[0] } return *new(T) } func MakePtr[T any](value T) *T { output := new(T) *output = value return output } func Reverse[T comparable](s []T) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } } // Given two arrays, make sure that elements in array 1 are present in array 2 // Return false if the requirements are not met func ContainsAll[T comparable](arr1, arr2 []T) bool { elements := make(map[T]bool) for _, num := range arr1 { elements[num] = true } for _, num := range arr2 { if !elements[num] { return false } } return true } func IndexOf[T comparable](collection []T, el T) int { for i, x := range collection { if x == el { return i } } return -1 } func Remove[T comparable](arr []T, s T) []T { return append(arr[:IndexOf(arr, s)], arr[IndexOf(arr, s)+1:]...) } func RemoveDuplicates[T comparable](sliceList []T) []T { allKeys := make(map[T]bool) list := []T{} for _, item := range sliceList { if _, value := allKeys[item]; !value { allKeys[item] = true list = append(list, item) } } return list } // Return elements in slice 1 minus elements in slice2 func RemoveMany[T comparable](slice1, slice2 []T) []T { removeMap := make(map[T]bool) for _, item := range slice2 { removeMap[item] = true } result := make([]T, 0) for _, item := range slice1 { if !removeMap[item] { result = append(result, item) } } return result } func GetFirstNChars(s string, n int) string { i := 0 for j := range s { if i == n { return s[:j] } i++ } return s } func PrintStatus(b bool) { var status string if b { status = "SUCCESS" } else { status = "FAILED" } fmt.Printf("... %s\n", status) } func DirExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } func MapToSortedArray(m map[string]int64) [][2]int64 { pairs := make([][2]int64, 0, len(m)) for k, v := range m { numKey, err := strconv.ParseInt(k, 10, 64) if err != nil { continue } pairs = append(pairs, [2]int64{numKey, v}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i][0] < pairs[j][0] }) return pairs } func RandomSortInt32(n int) []int32 { result := make([]int32, n) for i := 0; i < n; i++ { result[i] = int32(i + 1) } // Shuffle the list using Fisher-Yates algorithm for i := n - 1; i > 0; i-- { j := rand.Intn(i + 1) result[i], result[j] = result[j], result[i] } return result } func SlicesEqual[T comparable](a []T, b []T) bool { if len(a) != len(b) { return false } for i := range a { if b[i] != a[i] { return false } } return true } type StructDiff struct { FieldName string `json:"field_name"` OldValue any `json:"old_value"` NewValue any `json:"new_value"` } type StructComparison struct { Changes []StructDiff `json:"changes"` Summary string `json:"summary"` } func CompareStructs(oldStruct, newStruct any) StructComparison { result := StructComparison{ Changes: []StructDiff{}, } if oldStruct == nil && newStruct == nil { result.Summary = "Both structs are nil" return result } if oldStruct == nil { result.Summary = "Old struct is nil, new struct has values" return result } if newStruct == nil { result.Summary = "New struct is nil, old struct had values" return result } oldVal := reflect.ValueOf(oldStruct) newVal := reflect.ValueOf(newStruct) if oldVal.Type() != newVal.Type() { result.Summary = "Struct types do not match" return result } if oldVal.Kind() == reflect.Ptr { oldVal = oldVal.Elem() } if newVal.Kind() == reflect.Ptr { newVal = newVal.Elem() } if oldVal.Kind() != reflect.Struct || newVal.Kind() != reflect.Struct { result.Summary = "Both values must be structs" return result } oldType := oldVal.Type() changeCount := 0 for i := 0; i < oldVal.NumField(); i++ { field := oldType.Field(i) if !field.IsExported() { continue } oldFieldVal := oldVal.Field(i) newFieldVal := newVal.Field(i) if !oldFieldVal.CanInterface() || !newFieldVal.CanInterface() { continue } oldInterface := oldFieldVal.Interface() newInterface := newFieldVal.Interface() if !reflect.DeepEqual(oldInterface, newInterface) { result.Changes = append(result.Changes, StructDiff{ FieldName: field.Name, OldValue: oldInterface, NewValue: newInterface, }) changeCount++ } } if changeCount == 0 { result.Summary = "No changes detected" } else if changeCount == 1 { result.Summary = "1 field changed" } else { result.Summary = fmt.Sprintf("%d fields changed", changeCount) } return result } // Strips non-numeric characters from a string including spaces. func SanitizeNum(input string) string { re := regexp.MustCompile(`[^\d]+`) return re.ReplaceAllString(input, "") } // Strips non-alphanumeric, non-space characters from a string. func SanitizeAlphaNum(input string) string { re := regexp.MustCompile(`[^a-zA-Z0-9 ]+`) return re.ReplaceAllString(input, "") } // Strips non-alphanumeric characters from a string including spaces. func SanitizeAlphaNumStrict(input string) string { re := regexp.MustCompile(`[^a-zA-Z0-9]+`) return re.ReplaceAllString(input, "") } // MapMerge merges two maps, with values from m2 overriding values from m1. func MapMerge[K comparable, V any](m1, m2 map[K]V) map[K]V { result := make(map[K]V, len(m1)+len(m2)) for k, v := range m1 { result[k] = v } for k, v := range m2 { result[k] = v } return result }