Generators updated
Now includes generators for transforming storage.Role into the API representation and the Typescript version for the frontend
This commit is contained in:
parent
46bfac7540
commit
378f94fa97
4 changed files with 326 additions and 4 deletions
|
@ -82,17 +82,18 @@ func main() {
|
||||||
parts := sliceutils.Filter(strings.Split(line, " "), func(t string) bool { return t != "" })
|
parts := sliceutils.Filter(strings.Split(line, " "), func(t string) bool { return t != "" })
|
||||||
nameTypeMap[parts[0]] = parts[1]
|
nameTypeMap[parts[0]] = parts[1]
|
||||||
}
|
}
|
||||||
pkgString, _, _ := strings.Cut(string(data), "\n")
|
|
||||||
|
|
||||||
outBuilder := strings.Builder{}
|
outBuilder := strings.Builder{}
|
||||||
outBuilder.WriteString(`// Code generated by cmd/RolesApiConverter DO NOT EDIT.
|
outBuilder.WriteString(`// Code generated by cmd/RolesApiConverter DO NOT EDIT.
|
||||||
// If you need to refresh the content, run go generate again
|
// If you need to refresh the content, run go generate again
|
||||||
`)
|
`)
|
||||||
outBuilder.WriteString(pkgString + "\n\n")
|
outBuilder.WriteString("package server\n\n")
|
||||||
|
|
||||||
outBuilder.WriteString("func convertStorageRoleToApiRole(r storage.Role) linstromRole {\n")
|
outBuilder.WriteString("import \"gitlab.com/mstarongitlab/linstrom/storage\"\n")
|
||||||
|
|
||||||
|
outBuilder.WriteString("func convertRoleStorageToLinstrom(r storage.Role) linstromRole {\n")
|
||||||
outBuilder.WriteString("return linstromRole{")
|
outBuilder.WriteString("return linstromRole{")
|
||||||
outBuilder.WriteString("Id:r.ID,CreatedAt:r.CreatedAt,UpdatedAt:&r.UpdatedAt,Name:r.Name,")
|
outBuilder.WriteString("Id:r.ID,CreatedAt:r.CreatedAt,UpdatedAt:r.UpdatedAt,Name:r.Name,")
|
||||||
outBuilder.WriteString("Priority:r.Priority,IsUserRole:r.IsUserRole,IsBuiltIn:r.IsBuiltIn,")
|
outBuilder.WriteString("Priority:r.Priority,IsUserRole:r.IsUserRole,IsBuiltIn:r.IsBuiltIn,")
|
||||||
for k := range nameTypeMap {
|
for k := range nameTypeMap {
|
||||||
outBuilder.WriteString(fmt.Sprintf("%s:r.%s,", k, k))
|
outBuilder.WriteString(fmt.Sprintf("%s:r.%s,", k, k))
|
||||||
|
|
136
cmd/RolesApiTypeGenerator/main.go
Normal file
136
cmd/RolesApiTypeGenerator/main.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
Generator for the API type definition on the go server side
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"gitlab.com/mstarongitlab/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("package server\n\n")
|
||||||
|
outBuilder.WriteString("import \"time\"\n")
|
||||||
|
|
||||||
|
outBuilder.WriteString("type linstromRole struct {\n")
|
||||||
|
outBuilder.WriteString(" Id uint `jsonapi:\"primary,roles\"`\n")
|
||||||
|
outBuilder.WriteString(fNT("CreatedAt", "time.Time"))
|
||||||
|
outBuilder.WriteString(fNT("UpdatedAt", "time.Time"))
|
||||||
|
outBuilder.WriteString(fNT("Name", "string"))
|
||||||
|
outBuilder.WriteString(fNT("Priority", "uint32"))
|
||||||
|
outBuilder.WriteString(fNT("IsUserRole", "bool"))
|
||||||
|
outBuilder.WriteString(fNT("IsBuiltIn", "bool"))
|
||||||
|
|
||||||
|
for n, t := range nameTypeMap {
|
||||||
|
outBuilder.WriteString(fNT(n, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
outBuilder.WriteString("}")
|
||||||
|
fmt.Fprint(output, outBuilder.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertGoNameToJsonApiName(name string) string {
|
||||||
|
buffer := []rune{}
|
||||||
|
isFirst := true
|
||||||
|
for _, char := range name {
|
||||||
|
if unicode.IsUpper(char) {
|
||||||
|
if isFirst {
|
||||||
|
buffer = append(buffer, unicode.ToLower(char))
|
||||||
|
isFirst = false
|
||||||
|
} else {
|
||||||
|
buffer = append(buffer, '-', unicode.ToLower(char))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer = append(buffer, char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fNT(name, valType string) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
" %s %s `jsonapi:\"attr,%s\"`\n",
|
||||||
|
name,
|
||||||
|
valType,
|
||||||
|
convertGoNameToJsonApiName(name),
|
||||||
|
)
|
||||||
|
}
|
174
cmd/RolesFrontendGenerator/main.go
Normal file
174
cmd/RolesFrontendGenerator/main.go
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"gitlab.com/mstarongitlab/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 RoleModel 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("}")
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
|
@ -11,6 +11,17 @@ import (
|
||||||
//go:generate ./RolesGenerator -input=roles.go -output=rolesUtil_generated.go
|
//go:generate ./RolesGenerator -input=roles.go -output=rolesUtil_generated.go
|
||||||
//go:generate rm RolesGenerator
|
//go:generate rm RolesGenerator
|
||||||
|
|
||||||
|
//go:generate go build -o ApiGenerator ../cmd/RolesApiTypeGenerator/main.go
|
||||||
|
//go:generate ./ApiGenerator -input=roles.go -output=../server/apiLinstromTypes_generated.go
|
||||||
|
//go:generate rm ApiGenerator
|
||||||
|
|
||||||
|
//go:generate go build -o HelperGenerator ../cmd/RolesApiConverter/main.go
|
||||||
|
//go:generate ./HelperGenerator -input=roles.go -output=../server/apiLinstromTypeHelpers_generated.go
|
||||||
|
//go:generate rm HelperGenerator
|
||||||
|
|
||||||
|
//go:generate go build -o FrontendGenerator ../cmd/RolesFrontendGenerator/main.go
|
||||||
|
//go:generate ./FrontendGenerator -input=roles.go -output=../frontend-reactive/app/models/role.ts
|
||||||
|
|
||||||
// A role is, in concept, similar to how Discord handles roles
|
// A role is, in concept, similar to how Discord handles roles
|
||||||
// Some permission can be either disallowed (&false), don't care (nil) or allowed (&true)
|
// Some permission can be either disallowed (&false), don't care (nil) or allowed (&true)
|
||||||
// Don't care just says to use the value from the next lower role where it is set
|
// Don't care just says to use the value from the next lower role where it is set
|
||||||
|
|
Loading…
Reference in a new issue