Compare commits

...

10 Commits

4 changed files with 252 additions and 106 deletions

318
class.go
View File

@@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"text/template" "text/template"
@@ -35,6 +36,7 @@ func init() {
type ( type (
Class struct { Class struct {
ClassName string ClassName string
Description string
Fields []Field Fields []Field
Methods []Method Methods []Method
Constructors []Constructor Constructors []Constructor
@@ -61,6 +63,7 @@ type (
} }
Constructor struct { Constructor struct {
Params []Param Params []Param
Returns string
Comment string Comment string
} }
) )
@@ -88,6 +91,7 @@ func (c *Class) Write(root string, tmpl *template.Template) error {
if err != nil { if err != nil {
return fmt.Errorf("error creating output file %v: %v", c.ClassName, err) return fmt.Errorf("error creating output file %v: %v", c.ClassName, err)
} }
// spew.Dump(c)
err = tmpl.Execute(outfile, c) err = tmpl.Execute(outfile, c)
if err != nil { if err != nil {
return fmt.Errorf("error writing output file %v: %v", c.ClassName, err) return fmt.Errorf("error writing output file %v: %v", c.ClassName, err)
@@ -113,126 +117,268 @@ func ParseClass(file string) (*Class, error) {
return nil, fmt.Errorf("error parsing file: %w", err) return nil, fmt.Errorf("error parsing file: %w", err)
} }
class := doc.Find("div.floatright > h1") dataContainer := doc.Find("div.floatright")
if class.Length() == 0 { if dataContainer.Length() == 0 {
return nil, fmt.Errorf("no class found") return nil, fmt.Errorf("no data container found")
} }
res.ClassName = strings.TrimSpace(class.Text())
res.Constructors, err = getConstructors(doc) className, err := getClassName(dataContainer)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting constructors: %w", err) return nil, fmt.Errorf("error getting class name: %w", err)
} }
res.ClassName = className
res.Fields, err = getFields(doc) classDescription, err := getClassDescription(dataContainer)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting fields: %w", err) return nil, fmt.Errorf("error getting class description: %w", err)
} }
res.Description = classDescription
res.Methods, err = getMethods(doc) constructor, err := getConstructor(dataContainer)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting methods: %w", err) return nil, fmt.Errorf("error getting constructor: %w", err)
}
res.Constructors = append(res.Constructors, constructor)
// Doing div+div specifically skips only the constructor which is usually the first div
// So the constructor would then be located at h1 + p + div OR h1 + div if there is no description (no p)
dataElements := dataContainer.ChildrenFiltered("div + div")
if dataElements.Length() == 0 {
return nil, fmt.Errorf("no data elements found")
} }
// spew.Dump(res) dataElements.Each(func(i int, s *goquery.Selection) {
id, ok := s.Attr("id")
if !ok {
return
}
if id == "Properties" {
s.Children().Each(func(i int, s *goquery.Selection) {
field, err := parseField(s)
if err != nil {
Error.Printf("Error parsing field: %v", err)
return
}
res.Fields = append(res.Fields, field)
})
} else {
method, err := parseMethod(s)
if err != nil {
Error.Printf("Error parsing method: %v", err)
return
}
res.Methods = append(res.Methods, method)
}
})
return &res, nil return &res, nil
} }
// TODO: Implement parsing comments for return values func getClassName(dataContainer *goquery.Selection) (string, error) {
// Something like "---returns (something) -> comment" class := dataContainer.ChildrenFiltered("h1")
// Where "-> comment" is only shown if there's a comment if class.Length() == 0 {
// And "---returns" only if there's a return type return "", fmt.Errorf("no class found")
// This is NOT a luals annotation because we can not use annotations on @overload }
// But just a regular plain Lua comment res := CleanUp(class.Text())
// TODO: Implement parsing comments for classes and constructors return res, nil
func getConstructors(doc *goquery.Document) ([]Constructor, error) { }
res := []Constructor{}
codeblocks := doc.Find("div.floatright > div.codecontainer") var manySpaceRe = regexp.MustCompile(`\s{2,}`)
if codeblocks.Length() == 0 {
return res, fmt.Errorf("no codeblocks found") func getClassDescription(dataContainer *goquery.Selection) (string, error) {
class := dataContainer.ChildrenFiltered("h1 + p")
if class.Length() == 0 {
return "", fmt.Errorf("no class description found")
}
res := CleanUp(class.Text())
return res, nil
}
func getConstructor(dataContainer *goquery.Selection) (Constructor, error) {
resConstructor := Constructor{}
constructorBlock := dataContainer.Find("h1 + p + div")
if constructorBlock.Length() == 0 {
constructorBlock = dataContainer.Find("h1 + div")
if constructorBlock.Length() == 0 {
return resConstructor, fmt.Errorf("no constructor found")
}
} }
// The first code block should be the constructor types := constructorBlock.Find("div.function span.type")
// So far I have not found any classes with overloaded constructors... if types.Length() == 0 {
// So I can not handle that case yet return resConstructor, fmt.Errorf("no types found")
constructorBlock := codeblocks.Eq(0) }
constructor := constructorBlock.Find("div.function") params := constructorBlock.Find("div.function span.parameter")
paramTypes := constructor.Find("span.type") if params.Length() == 0 {
paramNames := constructor.Find("span.parameter") return resConstructor, fmt.Errorf("no params found")
}
resConstructor := Constructor{}
paramTypes.Each(func(i int, s *goquery.Selection) {
pname := strings.TrimSpace(paramNames.Eq(i).Text())
ptype := strings.TrimSpace(paramTypes.Eq(i).Text())
ptype = MapType(ptype)
types.Each(func(i int, s *goquery.Selection) {
resConstructor.Params = append(resConstructor.Params, Param{ resConstructor.Params = append(resConstructor.Params, Param{
Name: pname, Name: CleanUp(params.Eq(i).Text()),
Type: ptype, Type: MapType(CleanUp(types.Eq(i).Text())),
Comment: "", Comment: "",
}) })
}) })
constructorDetails := constructorBlock.Children().Eq(1) descriptor := constructorBlock.Find("div:not(.function)")
constructorParameterDetails := constructorDetails.Find("span.parameter") if descriptor.Length() == 0 {
constructorParameterDetails.Each(func(i int, s *goquery.Selection) { return resConstructor, fmt.Errorf("no descriptor found")
param := strings.TrimSpace(s.Text()) }
parameterParent := s.Parent()
parameterDescription := parameterParent.Text()
parameterDescription = strings.ReplaceAll(parameterDescription, fmt.Sprintf("\n%s\n", param), "")
parameterDescription = strings.TrimSpace(parameterDescription)
resConstructor.Params[i].Comment = parameterDescription
})
constructorBlock.Find("div:not(.function):not(.indented) > p:not(:has(*))").Each(func(i int, s *goquery.Selection) { // 0 nothing
resConstructor.Comment += strings.TrimSpace(s.Text()) + "\n" // 1 parameters
resConstructor.Comment = strings.TrimSpace(resConstructor.Comment) // 2 returns
}) state := 0
children := descriptor.Eq(0).Children()
spew.Dump(resConstructor) childrenLength := children.Length()
return append(res, resConstructor), nil children.Each(func(i int, s *goquery.Selection) {
} if i == childrenLength-1 {
// For some stupid reason the last child is always a mispalced </p> tag that does not close any open <p>
func getFields(doc *goquery.Document) ([]Field, error) { // I assume this is a bug with their documentation generator
res := []Field{} // So we just ignore it
properties := doc.Find("div.floatright > div.codecontainer#Properties") return
properties.ChildrenFiltered("div").Each(func(i int, s *goquery.Selection) { }
property := Field{} text := CleanUp(s.Text())
property.Name = strings.TrimSpace(s.Find("span.property").Text()) if text == "" {
property.Type = strings.TrimSpace(s.Find("span.type").Text()) return
property.Type = MapType(property.Type) }
comment := s.Find("td[align='right']").Text() if s.Is("p") {
if comment != "" { switch text {
property.Comment = strings.TrimSpace(comment) case "Parameters":
state = 1
return
case "Returns":
state = 2
return
}
} else {
switch state {
case 0:
return
case 1:
param := s.ChildrenFiltered("span.parameter").Text()
paramTrimmed := CleanUp(param)
for i := range resConstructor.Params {
cparam := &resConstructor.Params[i]
if paramTrimmed == cparam.Name {
cleanText := strings.TrimPrefix(text, param)
cparam.Comment = CleanUp(cleanText)
}
}
case 2:
cleaned := CleanUp(s.Text())
if resConstructor.Returns != "" {
resConstructor.Returns += "\n"
}
resConstructor.Returns = cleaned
}
} }
res = append(res, property)
}) })
// spew.Dump(resConstructor)
return resConstructor, nil
}
func parseField(s *goquery.Selection) (Field, error) {
res := Field{}
id, ok := s.Attr("id")
if !ok {
return res, fmt.Errorf("no id found")
}
res.Name = id
typeElement := s.Find("span.type")
if typeElement.Length() == 0 {
return res, fmt.Errorf("no type found")
}
res.Type = MapType(CleanUp(typeElement.Text()))
readonlyElement := s.Find("td[align='right']")
if readonlyElement.Length() != 0 {
// return res, fmt.Errorf("no readonly found")
res.Comment = CleanUp(readonlyElement.Text())
}
comments := s.ChildrenFiltered("div")
if comments.Length() == 0 {
return res, fmt.Errorf("no comments found")
}
comments.Each(func(i int, s *goquery.Selection) {
text := CleanUp(s.Text())
if text == "" {
return
}
if res.Comment != "" {
res.Comment += ". "
}
res.Comment += text
})
res.Comment = strings.ReplaceAll(res.Comment, "\n--", ". ")
return res, nil return res, nil
} }
// TODO: Implement parsing return value types and comments func parseMethod(s *goquery.Selection) (Method, error) {
func getMethods(doc *goquery.Document) ([]Method, error) { res := Method{}
res := []Method{}
codeblocks := doc.Find("div.floatright > div.codecontainer") id, ok := s.Attr("id")
codeblocks.ChildrenFiltered("div.function").Each(func(i int, s *goquery.Selection) { if !ok {
method := Method{} return res, fmt.Errorf("no id found")
method.Name = strings.TrimSpace(s.AttrOr("id", "")) }
method.Comment = strings.TrimSpace(s.Find("span.comment").Text()) res.Name = id
types := s.Find("span.type") returns := s.Find("span.keyword")
parameters := s.Find("span.parameter") if returns.Length() != 0 {
types.Each(func(i int, s *goquery.Selection) { returnsText := CleanUp(returns.Text())
param := Param{} returnsText = strings.ReplaceAll(returnsText, "function", "")
param.Name = strings.TrimSpace(parameters.Eq(i).Text()) returnsText = CleanUp(returnsText)
param.Type = strings.TrimSpace(types.Eq(i).Text())
param.Type = MapType(param.Type) res.Returns = append(res.Returns, Return{
method.Params = append(method.Params, param) Type: MapType(returnsText),
Comment: "",
}) })
}
res = append(res, method) types := s.Find("span.type")
parameters := s.Find("span.parameter")
types.Each(func(i int, s *goquery.Selection) {
res.Params = append(res.Params, Param{
Name: MapName(CleanUp(parameters.Eq(i).Text())),
Type: MapType(CleanUp(types.Eq(i).Text())),
Comment: "",
})
}) })
// <div id="add" class="codecontainer">
// <div id="add" class="function">
// <p>
// <span class="keyword">function var</span> add(<span class="type">CargoBay</span> <span
// class="parameter">other</span>) <br />
// </p>
// </div>
// <div id="add" class="">
// <p><span class="docheader">Returns</span></p>
// <div class="indented">
// <p>
// nothing
// </p>
// </div>
// </p>
// </div>
// </div>
spew.Dump(res)
return res, nil return res, nil
} }
func CleanUp(s string) string {
s = strings.TrimSpace(s)
s = strings.ReplaceAll(s, "\t", " ")
s = strings.ReplaceAll(s, "\n", "")
s = manySpaceRe.ReplaceAllString(s, " ")
s = strings.ReplaceAll(s, ". ", "\n--")
return s
}

View File

@@ -1,5 +1,6 @@
---@diagnostic disable: missing-return, lowercase-global ---@diagnostic disable: missing-return, lowercase-global
---@class {{.ClassName}} ---@class {{.ClassName}}
---{{.Description}}
{{- range .Fields}} {{- range .Fields}}
---@field {{.Name}} {{.Type}}{{if ne .Comment ""}} {{.Comment}}{{end}} ---@field {{.Name}} {{.Type}}{{if ne .Comment ""}} {{.Comment}}{{end}}
{{- end}} {{- end}}
@@ -15,8 +16,7 @@
{{- end}} {{- end}}
{{.Name}} = function(self{{if gt (len .Params) 0}}, {{range $index, $param := .Params}}{{if $index}}, {{end}}{{$param.Name}}{{end}}{{end}}) end, {{.Name}} = function(self{{if gt (len .Params) 0}}, {{range $index, $param := .Params}}{{if $index}}, {{end}}{{$param.Name}}{{end}}{{end}}) end,
{{- if ne (plus1 $index) $n}} {{- if ne (plus1 $index) $n}}
{{end}}
{{- end}}
{{- end}} {{- end}}
} }
@@ -26,7 +26,8 @@
{{- if ne .Comment ""}} {{- if ne .Comment ""}}
---{{.Name}} ({{.Type}}) -> {{.Comment}} ---{{.Name}} ({{.Type}}) -> {{.Comment}}
{{- end}} {{- end}}
{{- end}} {{- end}}{{if ne .Returns ""}}
---Returns {{.Returns}}{{end}}
---@overload fun({{range $index, $param := .Params}}{{if $index}}, {{end}}{{$param.Name}}: {{$param.Type}}{{end}}): {{$.ClassName}}{{- if ne .Comment ""}} ---@overload fun({{range $index, $param := .Params}}{{if $index}}, {{end}}{{$param.Name}}: {{$param.Type}}{{end}}): {{$.ClassName}}{{- if ne .Comment ""}}
---{{.Comment}} ---{{.Comment}}
{{- end}} {{- end}}

31
main.go
View File

@@ -6,6 +6,7 @@ import (
"io" "io"
"log" "log"
"os" "os"
"strings"
"sync" "sync"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@@ -61,20 +62,18 @@ func main() {
} }
func MapType(t string) string { func MapType(t string) string {
switch t { t = strings.ReplaceAll(t, "var", "any")
case "var": t = strings.ReplaceAll(t, "int", "number")
return "any" t = strings.ReplaceAll(t, "unsigned", "")
case "int": t = strings.ReplaceAll(t, "float", "number")
return "number" t = strings.ReplaceAll(t, "double", "number")
case "float": t = strings.ReplaceAll(t, "bool", "boolean")
return "number" t = strings.ReplaceAll(t, "table_t", "table")
case "double": t = strings.ReplaceAll(t, "...", "[]")
return "number" return t
case "bool":
return "boolean"
case "table_t":
return "table"
default:
return t
}
} }
func MapName(s string) string {
s = strings.ReplaceAll(s, "in", "input")
return s
}

View File

@@ -207,7 +207,7 @@
<div id="CargoBay" class=""> <div id="CargoBay" class="">
<p><span class="docheader">Parameters</span></p> <p><span class="docheader">Parameters</span></p>
<div class="indented"> <div class="indented">
<span class="parameter">id</span> <span class="parameter">name</span>
The id of the entity this component belongs to, or the entity itself, must be an id of an The id of the entity this component belongs to, or the entity itself, must be an id of an
existing entity or nil for the entity in the current script context <br /> existing entity or nil for the entity in the current script context <br />
</div> </div>