Rewrite generator and update for Lemmy 0.18.3

This commit is contained in:
2023-09-24 19:28:46 -07:00
parent 9f4bae88f2
commit 44dbe8c934
62 changed files with 3034 additions and 4194 deletions

View File

@@ -0,0 +1,192 @@
package extractor
import (
"fmt"
"os"
"strings"
"github.com/tidwall/gjson"
)
type Route struct {
Name string
Summary string
Method string
Path string
ParamsName string
ParamsID int64
ReturnName string
ReturnID int64
}
type Struct struct {
Name string
Fields []Field
UnionNames []string
}
type Field struct {
Name string
IsArray bool
IsOptional bool
Type string
}
type Extractor struct {
root gjson.Result
}
func New(path string) (*Extractor, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return &Extractor{gjson.ParseBytes(data)}, nil
}
func (e *Extractor) Routes() []Route {
var out []Route
routes := e.root.Get("children.#.children.#(kind==2048)#|@flatten")
for _, route := range routes.Array() {
name := route.Get("name").String()
signature := route.Get(`signatures.0`)
httpInfo := signature.Get(`comment.summary.#(kind=="code").text`).String()
if !strings.HasPrefix(httpInfo, "`HTTP") {
continue
}
method, path := parseHTTPInfo(httpInfo)
summary := strings.TrimSpace(signature.Get(`comment.summary.#(kind=="text").text`).String())
if summary == "" {
continue
}
paramsID := signature.Get("parameters.0.type.target").Int()
paramsName := signature.Get("parameters.0.type.name").String()
returnID := signature.Get("type.typeArguments.0.target").Int()
returnName := signature.Get("type.typeArguments.0.name").String()
out = append(out, Route{
Name: name,
Summary: summary,
Method: method,
Path: path,
ParamsName: paramsName,
ParamsID: paramsID,
ReturnName: returnName,
ReturnID: returnID,
})
}
return out
}
func (e *Extractor) Structs(routes []Route) []Struct {
var ids []int64
for _, route := range routes {
ids = append(ids, route.ParamsID)
if route.ReturnID != 0 {
ids = append(ids, route.ReturnID)
}
}
structs := map[int64]Struct{}
e.getStructs(ids, structs)
return getKeys(structs)
}
func (e *Extractor) getStructs(ids []int64, structs map[int64]Struct) {
for _, id := range ids {
if _, ok := structs[id]; ok {
continue
}
jstruct := e.root.Get(fmt.Sprintf("children.#(id==%d)", id))
if !jstruct.Exists() {
continue
}
name := jstruct.Get("name").String()
if jstruct.Get("type.type").String() == "union" {
structs[id] = Struct{
Name: name,
UnionNames: e.unionNames(jstruct),
}
} else {
fields, newIDs := e.fields(jstruct)
structs[id] = Struct{
Name: name,
Fields: fields,
}
e.getStructs(newIDs, structs)
}
}
}
func (e *Extractor) unionNames(jstruct gjson.Result) []string {
jnames := jstruct.Get("type.types").Array()
out := make([]string, len(jnames))
for i, name := range jnames {
out[i] = name.Get("value").String()
}
return out
}
func (e *Extractor) fields(jstruct gjson.Result) ([]Field, []int64) {
var fields []Field
var ids []int64
jfields := jstruct.Get("children").Array()
for _, jfield := range jfields {
var field Field
field.Name = jfield.Get("name").String()
field.IsOptional = jfield.Get("flags.isOptional").Bool()
if jfield.Get("type.type").String() == "array" {
field.IsArray = true
field.Type = jfield.Get("type.elementType.name").String()
switch jfield.Get("type.elementType.type").String() {
case "reference":
ids = append(ids, jfield.Get("type.elementType.target").Int())
case "union":
field.Type = "string"
}
} else {
field.Type = jfield.Get("type.name").String()
switch jfield.Get("type.type").String() {
case "reference":
ids = append(ids, jfield.Get("type.target").Int())
case "union":
field.Type = "string"
}
}
fields = append(fields, field)
}
return fields, ids
}
func parseHTTPInfo(httpInfo string) (method, path string) {
httpInfo = strings.Trim(httpInfo, "`")
method, path, _ = strings.Cut(httpInfo, " ")
method = strings.TrimPrefix(method, "HTTP.")
method = strings.ToUpper(method)
return method, path
}
func getKeys(m map[int64]Struct) []Struct {
out := make([]Struct, len(m))
i := 0
for _, s := range m {
out[i] = s
i++
}
return out
}

View File

@@ -5,7 +5,7 @@ import (
"strings"
"github.com/dave/jennifer/jen"
"go.elara.ws/go-lemmy/cmd/gen/parser"
"go.elara.ws/go-lemmy/cmd/gen/extractor"
)
type RoutesGenerator struct {
@@ -17,23 +17,30 @@ func NewRoutes(w io.Writer, pkgName string) *RoutesGenerator {
return &RoutesGenerator{w, pkgName}
}
func (r *RoutesGenerator) Generate(routes []parser.Route, impls map[string]string) error {
func (r *RoutesGenerator) Generate(routes []extractor.Route) error {
f := jen.NewFile(r.PkgName)
f.HeaderComment("Code generated by go.elara.ws/go-lemmy/cmd/gen (routes generator). DO NOT EDIT.")
for _, r := range routes {
resStruct := impls[r.Struct]
f.Comment(r.Summary)
f.Func().Params(
jen.Id("c").Id("*Client"),
).Id(transformName(r.Struct)).Params(
).Id(transformName(r.Name)).Params(
jen.Id("ctx").Qual("context", "Context"),
jen.Id("data").Qual("go.elara.ws/go-lemmy/types", r.Struct),
).Params(
jen.Op("*").Qual("go.elara.ws/go-lemmy/types", resStruct),
jen.Error(),
).BlockFunc(func(g *jen.Group) {
g.Id("resData").Op(":=").Op("&").Qual("go.elara.ws/go-lemmy/types", resStruct).Block()
jen.Id("data").Qual("go.elara.ws/go-lemmy/types", r.ParamsName),
).ParamsFunc(func(g *jen.Group) {
if r.ReturnName != "" {
g.Op("*").Qual("go.elara.ws/go-lemmy/types", r.ReturnName)
}
g.Error()
}).BlockFunc(func(g *jen.Group) {
returnName := r.ReturnName
if returnName == "" {
returnName = "EmptyResponse"
}
g.Id("resData").Op(":=").Op("&").Qual("go.elara.ws/go-lemmy/types", returnName).Block()
var funcName string
switch r.Method {
@@ -46,16 +53,28 @@ func (r *RoutesGenerator) Generate(routes []parser.Route, impls map[string]strin
g.List(jen.Id("res"), jen.Err()).Op(":=").Id("c").Dot(funcName).Params(
jen.Id("ctx"), jen.Lit(r.Method), jen.Lit(r.Path), jen.Id("data"), jen.Op("&").Id("resData"),
)
g.If(jen.Err().Op("!=").Nil()).Block(
jen.Return(jen.Nil(), jen.Err()),
)
g.If(jen.Err().Op("!=").Nil()).BlockFunc(func(g *jen.Group) {
if returnName == "EmptyResponse" {
g.Return(jen.Err())
} else {
g.Return(jen.Nil(), jen.Err())
}
})
g.Err().Op("=").Id("resError").Params(jen.Id("res"), jen.Id("resData").Dot("LemmyResponse"))
g.If(jen.Err().Op("!=").Nil()).Block(
jen.Return(jen.Nil(), jen.Err()),
)
g.If(jen.Err().Op("!=").Nil()).BlockFunc(func(g *jen.Group) {
if returnName == "EmptyResponse" {
g.Return(jen.Err())
} else {
g.Return(jen.Nil(), jen.Err())
}
})
g.Return(jen.Id("resData"), jen.Nil())
if returnName == "EmptyResponse" {
g.Return(jen.Nil())
} else {
g.Return(jen.Id("resData"), jen.Nil())
}
})
}
@@ -63,6 +82,7 @@ func (r *RoutesGenerator) Generate(routes []parser.Route, impls map[string]strin
}
func transformName(s string) string {
s = strings.ToUpper(s[:1]) + s[1:]
s = strings.TrimPrefix(s, "Get")
s = strings.TrimPrefix(s, "List")
return s

View File

@@ -3,9 +3,10 @@ package generator
import (
"io"
"strings"
"unicode"
"github.com/dave/jennifer/jen"
"go.elara.ws/go-lemmy/cmd/gen/parser"
"go.elara.ws/go-lemmy/cmd/gen/extractor"
)
type StructGenerator struct {
@@ -17,36 +18,88 @@ func NewStruct(w io.Writer, pkgName string) *StructGenerator {
return &StructGenerator{w, pkgName}
}
func (s *StructGenerator) Generate(items []parser.Item) error {
func (s *StructGenerator) Generate(items []extractor.Struct) error {
f := jen.NewFile(s.PkgName)
f.HeaderComment("Code generated by go.elara.ws/go-lemmy/cmd/gen (struct generator). DO NOT EDIT.")
for _, item := range items {
if item.Struct != nil {
st := item.Struct
f.Type().Id(st.Name).StructFunc(func(g *jen.Group) {
for _, field := range st.Fields {
g.Id(field.Name).Id(field.Type).Tag(map[string]string{
"json": field.OrigName,
"url": field.OrigName + ",omitempty",
if len(item.UnionNames) > 0 {
f.Type().Id(item.Name).String()
f.Const().DefsFunc(func(g *jen.Group) {
for _, member := range item.UnionNames {
constName := strings.Replace(item.Name+string(member), " ", "", -1)
g.Id(constName).Id(item.Name).Op("=").Lit(string(member))
}
})
} else {
f.Type().Id(item.Name).StructFunc(func(g *jen.Group) {
for _, field := range item.Fields {
g.Id(transformFieldName(field.Name)).Id(getType(field)).Tag(map[string]string{
"json": field.Name,
"url": field.Name + ",omitempty",
})
}
if strings.HasSuffix(st.Name, "Response") {
if strings.HasSuffix(item.Name, "Response") {
g.Id("LemmyResponse")
}
})
} else if item.Enum != nil {
e := item.Enum
f.Type().Id(e.Name).String()
f.Const().DefsFunc(func(g *jen.Group) {
for _, member := range e.Members {
g.Id(e.Name + string(member)).Id(e.Name).Op("=").Lit(string(member))
}
})
}
}
return f.Render(s.w)
}
func getType(f extractor.Field) string {
t := transformType(f.Type)
if f.IsArray {
t = "[]" + t
}
if f.IsOptional {
t = "Optional[" + t + "]"
}
return t
}
func transformType(s string) string {
switch s {
case "number":
return "float64"
case "boolean":
return "bool"
default:
return s
}
}
func transformFieldName(s string) string {
s = snakeToCamel(s)
s = strings.NewReplacer(
"Id", "ID",
"Url", "URL",
"Nsfw", "NSFW",
"Jwt", "JWT",
"Crud", "CRUD",
).Replace(s)
return s
}
func snakeToCamel(s string) string {
sb := &strings.Builder{}
capitalizeNext := true
for _, char := range s {
if char == '_' {
capitalizeNext = true
continue
}
if capitalizeNext {
sb.WriteRune(unicode.ToUpper(char))
capitalizeNext = false
} else {
sb.WriteRune(char)
}
}
return sb.String()
}

View File

@@ -2,14 +2,11 @@ package main
import (
"flag"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"go.elara.ws/go-lemmy/cmd/gen/extractor"
"go.elara.ws/go-lemmy/cmd/gen/generator"
"go.elara.ws/go-lemmy/cmd/gen/parser"
"go.elara.ws/logger"
"go.elara.ws/logger/log"
)
@@ -18,145 +15,33 @@ func init() {
log.Logger = logger.NewPretty(os.Stderr)
}
var implDirs = [...]string{
"crates/api_crud/src",
"crates/apub/src/api",
"crates/api/src",
}
var structDirs = [...]string{
"crates/api_common",
"crates/db_schema/src/source",
"crates/db_views_actor/src/structs.rs",
"crates/db_views/src/structs.rs",
"crates/db_views_moderator/src/structs.rs",
"crates/db_schema/src/aggregates/structs.rs",
"crates/db_schema/src/lib.rs",
"crates/websocket/src/lib.rs",
}
const routesFile = "src/api_routes_http.rs"
func main() {
lemmyDir := flag.String("lemmy-dir", "lemmy", "Path to Lemmy repository")
jsonFile := flag.String("json-file", "lemmy.json", "Path to the JSON file generated from lemmy's docs")
outDir := flag.String("out-dir", "out", "Directory to write output in")
flag.Parse()
baseStructDir := filepath.Join(*outDir, "types")
sp := parser.NewStruct(nil)
sp.Skip = []string{"LemmyContext", "Recipient", "WsMessage", "Connect", "SessionInfo"}
for _, structDir := range structDirs {
dir := filepath.Join(*lemmyDir, structDir)
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
return nil
}
if filepath.Ext(path) != ".rs" {
return nil
}
name := d.Name()
if name == "context.rs" ||
name == "local_user_language.rs" ||
name == "chat_server.rs" {
return nil
}
fl, err := os.Open(path)
if err != nil {
return err
}
defer fl.Close()
sp.Reset(fl)
fileStructs, err := sp.Parse()
if err != nil {
return err
}
nameNoExt := strings.TrimSuffix(d.Name(), ".rs")
goFilePath := filepath.Join(baseStructDir, nameNoExt+".gen.go")
i := 1
_, err = os.Stat(goFilePath)
for err == nil {
goFilePath = filepath.Join(baseStructDir, nameNoExt+"."+strconv.Itoa(i)+".gen.go")
_, err = os.Stat(goFilePath)
i++
}
outFl, err := os.Create(goFilePath)
if err != nil {
return err
}
defer outFl.Close()
_, err = outFl.WriteString("// Source: " + path + "\n")
if err != nil {
return err
}
return generator.NewStruct(outFl, "types").Generate(fileStructs)
})
if err != nil {
log.Fatal("Error walking directory while parsing structs").Err(err).Str("dir", dir).Send()
}
}
ip := parser.NewImpl(nil)
impls := map[string]string{}
for _, implDir := range implDirs {
dir := filepath.Join(*lemmyDir, implDir)
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
return nil
}
if filepath.Ext(path) != ".rs" {
return nil
}
fl, err := os.Open(path)
if err != nil {
return err
}
defer fl.Close()
ip.Reset(fl)
implMap, err := ip.Parse()
if err != nil {
return err
}
for reqStruct, resStruct := range implMap {
impls[reqStruct] = resStruct
}
return nil
})
if err != nil {
log.Fatal("Error walking directory while parsing impls").Err(err).Str("dir", dir).Send()
}
}
routesPath := filepath.Join(*lemmyDir, routesFile)
rf, err := os.Open(routesPath)
e, err := extractor.New(*jsonFile)
if err != nil {
log.Fatal("Error opening routes file").Err(err).Send()
log.Fatal("Error creating extractor").Err(err).Send()
}
defer rf.Close()
rp := parser.NewRoutes(rf)
routes, err := rp.Parse()
routes := e.Routes()
structs := e.Structs(routes)
err = os.MkdirAll(filepath.Join(*outDir, "types"), 0o755)
if err != nil {
log.Fatal("Error parsing routes file").Err(err).Send()
log.Fatal("Error creating types directory").Err(err).Send()
}
otf, err := os.Create(filepath.Join(*outDir, "types/types.gen.go"))
if err != nil {
log.Fatal("Error creating types output file").Err(err).Send()
}
defer otf.Close()
err = generator.NewStruct(otf, "types").Generate(structs)
if err != nil {
log.Fatal("Error generating output routes file").Err(err).Send()
}
orf, err := os.Create(filepath.Join(*outDir, "routes.gen.go"))
@@ -165,12 +50,7 @@ func main() {
}
defer orf.Close()
_, err = orf.WriteString("// Source: " + routesPath + "\n")
if err != nil {
log.Fatal("Error writing source string to routes file").Err(err).Send()
}
err = generator.NewRoutes(orf, "lemmy").Generate(routes, impls)
err = generator.NewRoutes(orf, "lemmy").Generate(routes)
if err != nil {
log.Fatal("Error generating output routes file").Err(err).Send()
}

View File

@@ -1,62 +0,0 @@
package parser
import (
"bufio"
"errors"
"io"
"regexp"
)
var (
implRegex = regexp.MustCompile(`impl Perform.* for (.+) {`)
respTypeRegex = regexp.MustCompile(`type Response = (.+);`)
)
var ErrNoType = errors.New("type line not found")
type ImplParser struct {
r *bufio.Reader
}
func NewImpl(r io.Reader) *ImplParser {
return &ImplParser{
r: bufio.NewReader(r),
}
}
func (i *ImplParser) Parse() (map[string]string, error) {
out := map[string]string{}
for {
line, err := i.r.ReadString('\n')
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, err
}
if implRegex.MatchString(line) {
im := implRegex.FindStringSubmatch(line)
line, err := i.r.ReadString('\n')
if errors.Is(err, io.EOF) {
return nil, io.ErrUnexpectedEOF
} else if err != nil {
return nil, err
}
if !respTypeRegex.MatchString(line) {
return nil, ErrNoType
}
rtm := respTypeRegex.FindStringSubmatch(line)
out[im[1]] = rtm[1]
}
}
return out, nil
}
func (i *ImplParser) Reset(r io.Reader) {
i.r.Reset(r)
}

View File

@@ -1,121 +0,0 @@
package parser
import (
"bufio"
"errors"
"io"
"net/url"
"regexp"
"strings"
)
var (
scopeRegex = regexp.MustCompile(`web::(?:scope|resource)\("(.*)"\)\n`)
routeRegex = regexp.MustCompile(`\.route\(\n?\s*(?:"(.*)",[ \n])?\s*web::(.+)\(\)\.to\(route_.*::<(.+)>`)
)
type Route struct {
Path string
Method string
Struct string
}
type RoutesParser struct {
r *bufio.Reader
}
func NewRoutes(r io.Reader) *RoutesParser {
return &RoutesParser{
r: bufio.NewReader(r),
}
}
func (r *RoutesParser) Parse() ([]Route, error) {
var out []Route
for {
line, err := r.r.ReadString('\n')
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, err
}
if scopeRegex.MatchString(line) {
scopePath := scopeRegex.FindStringSubmatch(line)[1]
if scopePath == "/api/v3" {
continue
}
routes, err := r.parseRoutes()
if err != nil {
return nil, err
}
for i := range routes {
path, err := url.JoinPath(scopePath, routes[i].Path)
if err != nil {
return nil, err
}
routes[i].Path = path
}
out = append(out, routes...)
}
}
return out, nil
}
func (r *RoutesParser) parseRoutes() ([]Route, error) {
var out []Route
for {
line, err := r.r.ReadString('\n')
if errors.Is(err, io.EOF) {
if strings.TrimSpace(line)[:1] == ")" {
return out, nil
} else {
return nil, io.ErrUnexpectedEOF
}
} else if err != nil {
return nil, err
}
if strings.TrimSpace(line) == ".route(" {
lines, err := r.readLines(3)
if err != nil {
return nil, err
}
line += lines
}
if strings.TrimSpace(line)[:1] == ")" {
return out, nil
} else if strings.HasPrefix(line, "//") {
continue
} else if !routeRegex.MatchString(line) {
continue
}
sm := routeRegex.FindStringSubmatch(line)
out = append(out, Route{
Path: sm[1],
Method: strings.ToUpper(sm[2]),
Struct: sm[3],
})
}
}
func (r *RoutesParser) readLines(n int) (string, error) {
out := ""
for i := 0; i < n; i++ {
line, err := r.r.ReadString('\n')
if err != nil {
return "", err
}
out += line
}
return out, nil
}
func (r *RoutesParser) Reset(rd io.Reader) {
r.r.Reset(rd)
}

View File

@@ -1,274 +0,0 @@
package parser
import (
"bufio"
"errors"
"io"
"regexp"
"strings"
"golang.org/x/exp/slices"
)
var (
structRegex = regexp.MustCompile(`pub struct (.+) \{`)
fieldRegex = regexp.MustCompile(`(?U) {1,1}([^ ]+): (.+),`)
enumRegex = regexp.MustCompile(`pub enum (.+) \{`)
memberRegex = regexp.MustCompile(` ([^ #]+),\n`)
)
type Item struct {
Struct *Struct
Enum *Enum
}
type Struct struct {
Name string
Fields []Field
}
type Field struct {
OrigName string
Name string
Type string
}
type Enum struct {
Name string
Members []Member
}
type Member string
type StructParser struct {
r *bufio.Reader
Skip []string
TransformName func(string) string
TransformType func(string) string
}
func NewStruct(r io.Reader) *StructParser {
return &StructParser{
r: bufio.NewReader(r),
TransformName: TransformNameGo,
TransformType: TransformTypeGo,
}
}
func (s *StructParser) Parse() ([]Item, error) {
var out []Item
for {
line, err := s.r.ReadString('\n')
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, err
}
if structRegex.MatchString(line) {
structName := structRegex.FindStringSubmatch(line)[1]
if slices.Contains(s.Skip, structName) {
continue
}
structName = s.TransformName(structName)
// If the line ends with "}", this is a struct with no fields
if strings.HasSuffix(line, "}\n") {
out = append(out, Item{
Struct: &Struct{
Name: structRegex.FindStringSubmatch(line)[1],
},
})
continue
}
fields, err := s.parseStructFields()
if err != nil {
return nil, err
}
out = append(out, Item{
Struct: &Struct{
Name: structName,
Fields: fields,
},
})
} else if enumRegex.MatchString(line) {
enumName := enumRegex.FindStringSubmatch(line)[1]
if slices.Contains(s.Skip, enumName) {
continue
}
enumName = s.TransformName(enumName)
members, err := s.parseEnumMemebers()
if err != nil {
return nil, err
}
out = append(out, Item{
Enum: &Enum{
Name: enumName,
Members: members,
},
})
}
}
return out, nil
}
func (s *StructParser) parseStructFields() ([]Field, error) {
encountered := map[string]struct{}{}
var out []Field
for {
line, err := s.r.ReadString('\n')
if errors.Is(err, io.EOF) {
if strings.HasPrefix(line, "}") {
return out, nil
} else {
return nil, io.ErrUnexpectedEOF
}
} else if err != nil {
return nil, err
}
if strings.HasPrefix(line, "}") {
return out, nil
} else if strings.HasPrefix(line, "//") {
continue
} else if !fieldRegex.MatchString(line) {
continue
}
sm := fieldRegex.FindStringSubmatch(line)
if sm[1] == "Example" {
continue
}
if _, ok := encountered[sm[1]]; ok {
continue
} else {
encountered[sm[1]] = struct{}{}
}
out = append(out, Field{
OrigName: sm[1],
Name: s.TransformName(sm[1]),
Type: s.TransformType(sm[2]),
})
}
}
func (s *StructParser) parseEnumMemebers() ([]Member, error) {
var out []Member
for {
line, err := s.r.ReadString('\n')
if errors.Is(err, io.EOF) {
if strings.HasPrefix(line, "}") {
return out, nil
} else {
return nil, io.ErrUnexpectedEOF
}
} else if err != nil {
return nil, err
}
if strings.HasPrefix(line, "}") {
return out, nil
} else if strings.HasPrefix(line, "//") {
continue
} else if !memberRegex.MatchString(line) {
continue
}
sm := memberRegex.FindStringSubmatch(line)
out = append(out, Member(sm[1]))
}
}
// TransformTypeGo transforms Rust types to Go
//
// Example: TransformTypeGo("Option<Vec<i64>>") // returns "Optional[[]int64]"
func TransformTypeGo(t string) string {
prefix := ""
suffix := ""
for strings.HasPrefix(t, "Option<") {
t = strings.TrimPrefix(strings.TrimSuffix(t, ">"), "Option<")
prefix += "Optional["
suffix += "]"
}
for strings.HasPrefix(t, "Vec<") {
t = strings.TrimPrefix(strings.TrimSuffix(t, ">"), "Vec<")
prefix += "[]"
}
for strings.HasPrefix(t, "Sensitive<") {
t = strings.TrimPrefix(strings.TrimSuffix(t, ">"), "Sensitive<")
}
if strings.HasSuffix(t, "Id") {
t = "int"
}
switch t {
case "String", "Url", "DbUrl", "Ltree":
t = "string"
case "usize":
t = "uint"
case "i64":
t = "int64"
case "i32":
t = "int32"
case "i16":
t = "int16"
case "i8":
t = "int8"
case "chrono::NaiveDateTime":
return "LemmyTime"
case "Value":
return "any"
}
return prefix + t + suffix
}
// TransformNameGo transforms conventional Rust naming to
// conventional Go naming.
//
// Example: TransformNameGo("post_id") // returns "PostID"
func TransformNameGo(s string) string {
out := ""
s = strings.ReplaceAll(s, "Crud", "CRUD")
splitName := strings.Split(s, "_")
for _, segment := range splitName {
switch segment {
case "id":
out += "ID"
case "url":
out += "URL"
case "nsfw":
out += "NSFW"
case "jwt":
out += "JWT"
case "crud":
out += "CRUD"
default:
if len(segment) == 0 {
continue
}
out += strings.ToUpper(segment[:1]) + segment[1:]
}
}
return out
}
func (s *StructParser) Reset(r io.Reader) {
s.r.Reset(r)
}

View File

@@ -1,49 +0,0 @@
package parser
import "testing"
func TestTransformNameGo(t *testing.T) {
type testcase struct {
name string
expect string
}
cases := []testcase{
{"post_id", "PostID"},
{"nsfw", "NSFW"},
{"test_url", "TestURL"},
{"some_complex_name_with_id_and_nsfw_and_url", "SomeComplexNameWithIDAndNSFWAndURL"},
}
for _, testcase := range cases {
t.Run(testcase.name, func(t *testing.T) {
got := TransformNameGo(testcase.name)
if got != testcase.expect {
t.Errorf("Expected %s, got %s", testcase.expect, got)
}
})
}
}
func TestTransformTypeGo(t *testing.T) {
type testcase struct {
typeName string
expect string
}
cases := []testcase{
{"i16", "int16"},
{"Option<Vec<i64>>", "Optional[[]int64]"},
{"Url", "string"},
{"Sensitive<String>", "string"},
}
for _, testcase := range cases {
t.Run(testcase.typeName, func(t *testing.T) {
got := TransformTypeGo(testcase.typeName)
if got != testcase.expect {
t.Errorf("Expected %s, got %s", testcase.expect, got)
}
})
}
}