linstrom/cmd/RolesFrontendGenerator/main.go

180 lines
3.8 KiB
Go
Raw Normal View History

/*
Tool for generating the frontend definition of the role model
It generates a typescript file containing the full role, but without functions
*/
package main
import (
"flag"
"fmt"
"io"
"os"
"regexp"
"strings"
"unicode"
2024-12-18 14:24:56 +00:00
"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]
}
outBuilder := strings.Builder{}
outBuilder.WriteString(`// Code generated by cmd/RolesApiTypeGenerator DO NOT EDIT.
// If you need to refresh the content, run go generate again
`)
outBuilder.WriteString("import Model, { attr } from '@ember-data/model';\n\n")
outBuilder.WriteString("export default class Role extends Model {\n")
outBuilder.WriteString(` @attr() declare createdAt: Date
@attr() declare updatedAt: Date
@attr() declare name: string
@attr() declare priority: number
@attr() declare isUserRole: boolean
@attr() declare isBuiltIn: boolean
`)
for n, t := range nameTypeMap {
outBuilder.WriteString(fNT(n, t))
}
outBuilder.WriteString("}\n\n")
outBuilder.WriteString(`declare module 'ember-data/types/registries/model' {
export default interface ModelRegistry {
role: Role
}
}`)
fmt.Fprint(output, outBuilder.String())
}
func convertGoTypeToTS(t string) string {
switch t {
case "string", "*string":
return "string"
case "time.Time":
return "Date"
case "uint",
"uint8",
"uint16",
"uint32",
"uint64",
"int",
"int8",
"int16",
"int32",
"int64",
"float32",
"float64",
"byte",
"*uint",
"*uint8",
"*uint16",
"*uint32",
"*uint64",
"*int",
"*int8",
"*int16",
"*int32",
"*int64",
"*float32",
"*float64",
"*byte":
return "number"
case "bool", "*bool":
return "boolean"
case "[]string":
return "Array<string>"
default:
panic(fmt.Sprintf("Unknown type: %s", t))
}
}
func convertGoNameToTS(name string) string {
buffer := []rune{}
isFirst := true
for _, char := range name {
if isFirst {
buffer = append(buffer, unicode.ToLower(char))
isFirst = false
} else {
buffer = append(buffer, char)
}
}
return string(buffer)
}
func fNT(name, valType string) string {
return fmt.Sprintf(
" @attr() declare %s?: %s\n",
convertGoNameToTS(name),
convertGoTypeToTS(valType),
)
}