/* Tool for generating helper functions for storage.Role structs inside of the storage package It generates the following functions: - CollapseRolesIntoOne: Collapse a list of roles into one singular role. Each value will be set to the value of the role with the highest priority - RoleDeepCopy: Copy a role, including all arrays. Every value will be copied too - CompareRoles: Compare two roles. Returns true only if all fields are equal (if one of the fields is nil, that field is seen as equal) */ package main import ( "flag" "fmt" "io" "os" "regexp" "strings" "git.mstar.dev/mstar/goutils/sliceutils" ) var findRoleStructRegex = regexp.MustCompile(`type Role struct \{([\s\S]+)\}\n\n/\*`) var ( flagInputFile = flag.String("input", "", "Specify the input file. If empty, read from stdin") flagOutputFile = flag.String( "output", "", "Specify the output file. If empty, writes to stdout", ) ) func main() { flag.Parse() var input io.Reader var output io.Writer if *flagInputFile == "" { input = os.Stdin } else { file, err := os.Open(*flagInputFile) if err != nil { panic(err) } defer file.Close() input = file } if *flagOutputFile == "" { output = os.Stdout } else { file, err := os.Create(*flagOutputFile) if err != nil { panic(err) } defer file.Close() output = file } data, err := io.ReadAll(input) if err != nil { panic(err) } if !findRoleStructRegex.Match(data) { panic("Input doesn't contain role struct") } content := findRoleStructRegex.FindStringSubmatch(string(data))[1] lines := strings.Split(content, "\n") lines = sliceutils.Map(lines, func(t string) string { return strings.TrimSpace(t) }) importantLines := sliceutils.Filter(lines, func(t string) bool { if strings.HasPrefix(t, "//") { return false } if strings.Contains(t, "gorm.Model") { return false } data := sliceutils.Filter(strings.Split(t, " "), func(t string) bool { return t != "" }) if len(data) < 2 { return false } if !strings.HasPrefix(data[1], "*") && !strings.HasPrefix(data[1], "[]") { return false } return true }) nameTypeMap := map[string]string{} for _, line := range importantLines { parts := sliceutils.Filter(strings.Split(line, " "), func(t string) bool { return t != "" }) nameTypeMap[parts[0]] = parts[1] } pkgString, _, _ := strings.Cut(string(data), "\n") outBuilder := strings.Builder{} outBuilder.WriteString(`// Code generated by cmd/RolesGenerator DO NOT EDIT. // If you need to refresh the content, run go generate again `) outBuilder.WriteString(pkgString + "\n\n") outBuilder.WriteString( "import (\n \"slices\"\n \"git.mstar.dev/mstar/goutils/sliceutils\"\n)\n\n", ) // Build role collapse function outBuilder.WriteString( `func CollapseRolesIntoOne(roles ...Role) Role { startingRole := RoleDeepCopy(DefaultUserRole) slices.SortFunc(roles, func(a, b Role) int { return int(int64(a.Priority)-int64(b.Priority)) }) for _, role := range roles { `) // Write all the stupid conditions here for valName, valType := range nameTypeMap { if strings.HasPrefix(valType, "[]") { outBuilder.WriteString(fmt.Sprintf(` if role.%s != nil { startingRole.%s = append(startingRole.%s, role.%s...) } `, valName, valName, valName, valName)) } else { outBuilder.WriteString(fmt.Sprintf(` if role.%s != nil { *startingRole.%s = *role.%s } `, valName, valName, valName)) } } // Then finish up with the end of the function outBuilder.WriteString( ` } return startingRole } `) // Then build the deep copy function outBuilder.WriteString("\nfunc RoleDeepCopy(o Role) Role {\n") outBuilder.WriteString(` n := Role{} n.Model = o.Model n.Name = o.Name n.Priority = o.Priority n.IsUserRole = o.IsUserRole n.IsBuiltIn = o.IsBuiltIn `) for valName, valType := range nameTypeMap { if strings.HasPrefix(valType, "[]") { outBuilder.WriteString(fmt.Sprintf(" n.%s = slices.Clone(o.%s)\n", valName, valName)) } else { outBuilder.WriteString(fmt.Sprintf(` if o.%s == nil { n.%s = nil } else { t := *o.%s n.%s = &t } `, valName, valName, valName, valName)) } } outBuilder.WriteString(" return n\n}\n\n") // Build compare function outBuilder.WriteString("func CompareRoles(a, b *Role) bool {\n") outBuilder.WriteString(" return ") lastName, lastType := "", "" for valName, valType := range nameTypeMap { lastName = valName lastType = valType outBuilder.WriteString(fmt.Sprintf("(a.%s == nil || b.%s == nil || ", valName, valName)) if strings.HasPrefix(valType, "[]") { outBuilder.WriteString( fmt.Sprintf("sliceutils.CompareUnordered(a.%s,b.%s)) && ", valName, valName), ) } else { outBuilder.WriteString(fmt.Sprintf("a.%s == b.%s) && ", valName, valName)) } } outBuilder.WriteString("(a == nil || b == nil || ") if strings.HasPrefix(lastType, "[]") { outBuilder.WriteString( fmt.Sprintf("sliceutils.CompareUnordered(a.%s,b.%s))", lastName, lastName), ) } else { outBuilder.WriteString(fmt.Sprintf("a.%s == b.%s)", lastName, lastName)) } outBuilder.WriteString("\n}") // And write the entire thing to the output fmt.Fprint(output, outBuilder.String()) }