Rewrite generator and update for Lemmy 0.18.3
This commit is contained in:
		
							
								
								
									
										192
									
								
								cmd/gen/extractor/extractor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								cmd/gen/extractor/extractor.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										162
									
								
								cmd/gen/main.go
									
									
									
									
									
								
							
							
						
						
									
										162
									
								
								cmd/gen/main.go
									
									
									
									
									
								
							@@ -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()
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user