Compare commits

...

10 Commits

4 changed files with 252 additions and 106 deletions

318
class.go
View File

@@ -6,6 +6,7 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
@@ -35,6 +36,7 @@ func init() {
type (
Class struct {
ClassName string
Description string
Fields []Field
Methods []Method
Constructors []Constructor
@@ -61,6 +63,7 @@ type (
}
Constructor struct {
Params []Param
Returns string
Comment string
}
)
@@ -88,6 +91,7 @@ func (c *Class) Write(root string, tmpl *template.Template) error {
if err != nil {
return fmt.Errorf("error creating output file %v: %v", c.ClassName, err)
}
// spew.Dump(c)
err = tmpl.Execute(outfile, c)
if err != nil {
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)
}
class := doc.Find("div.floatright > h1")
if class.Length() == 0 {
return nil, fmt.Errorf("no class found")
dataContainer := doc.Find("div.floatright")
if dataContainer.Length() == 0 {
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 {
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 {
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 {
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
}
// TODO: Implement parsing comments for return values
// Something like "---returns (something) -> comment"
// Where "-> comment" is only shown if there's a comment
// And "---returns" only if there's a return type
// This is NOT a luals annotation because we can not use annotations on @overload
// But just a regular plain Lua comment
// TODO: Implement parsing comments for classes and constructors
func getConstructors(doc *goquery.Document) ([]Constructor, error) {
res := []Constructor{}
func getClassName(dataContainer *goquery.Selection) (string, error) {
class := dataContainer.ChildrenFiltered("h1")
if class.Length() == 0 {
return "", fmt.Errorf("no class found")
}
res := CleanUp(class.Text())
return res, nil
}
codeblocks := doc.Find("div.floatright > div.codecontainer")
if codeblocks.Length() == 0 {
return res, fmt.Errorf("no codeblocks found")
var manySpaceRe = regexp.MustCompile(`\s{2,}`)
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
// So far I have not found any classes with overloaded constructors...
// So I can not handle that case yet
constructorBlock := codeblocks.Eq(0)
constructor := constructorBlock.Find("div.function")
paramTypes := constructor.Find("span.type")
paramNames := constructor.Find("span.parameter")
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 := constructorBlock.Find("div.function span.type")
if types.Length() == 0 {
return resConstructor, fmt.Errorf("no types found")
}
params := constructorBlock.Find("div.function span.parameter")
if params.Length() == 0 {
return resConstructor, fmt.Errorf("no params found")
}
types.Each(func(i int, s *goquery.Selection) {
resConstructor.Params = append(resConstructor.Params, Param{
Name: pname,
Type: ptype,
Name: CleanUp(params.Eq(i).Text()),
Type: MapType(CleanUp(types.Eq(i).Text())),
Comment: "",
})
})
constructorDetails := constructorBlock.Children().Eq(1)
constructorParameterDetails := constructorDetails.Find("span.parameter")
constructorParameterDetails.Each(func(i int, s *goquery.Selection) {
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
})
descriptor := constructorBlock.Find("div:not(.function)")
if descriptor.Length() == 0 {
return resConstructor, fmt.Errorf("no descriptor found")
}
constructorBlock.Find("div:not(.function):not(.indented) > p:not(:has(*))").Each(func(i int, s *goquery.Selection) {
resConstructor.Comment += strings.TrimSpace(s.Text()) + "\n"
resConstructor.Comment = strings.TrimSpace(resConstructor.Comment)
})
spew.Dump(resConstructor)
return append(res, resConstructor), nil
}
func getFields(doc *goquery.Document) ([]Field, error) {
res := []Field{}
properties := doc.Find("div.floatright > div.codecontainer#Properties")
properties.ChildrenFiltered("div").Each(func(i int, s *goquery.Selection) {
property := Field{}
property.Name = strings.TrimSpace(s.Find("span.property").Text())
property.Type = strings.TrimSpace(s.Find("span.type").Text())
property.Type = MapType(property.Type)
comment := s.Find("td[align='right']").Text()
if comment != "" {
property.Comment = strings.TrimSpace(comment)
// 0 nothing
// 1 parameters
// 2 returns
state := 0
children := descriptor.Eq(0).Children()
childrenLength := children.Length()
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>
// I assume this is a bug with their documentation generator
// So we just ignore it
return
}
text := CleanUp(s.Text())
if text == "" {
return
}
if s.Is("p") {
switch text {
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
}
// TODO: Implement parsing return value types and comments
func getMethods(doc *goquery.Document) ([]Method, error) {
res := []Method{}
func parseMethod(s *goquery.Selection) (Method, error) {
res := Method{}
codeblocks := doc.Find("div.floatright > div.codecontainer")
codeblocks.ChildrenFiltered("div.function").Each(func(i int, s *goquery.Selection) {
method := Method{}
method.Name = strings.TrimSpace(s.AttrOr("id", ""))
method.Comment = strings.TrimSpace(s.Find("span.comment").Text())
id, ok := s.Attr("id")
if !ok {
return res, fmt.Errorf("no id found")
}
res.Name = id
types := s.Find("span.type")
parameters := s.Find("span.parameter")
types.Each(func(i int, s *goquery.Selection) {
param := Param{}
param.Name = strings.TrimSpace(parameters.Eq(i).Text())
param.Type = strings.TrimSpace(types.Eq(i).Text())
param.Type = MapType(param.Type)
method.Params = append(method.Params, param)
returns := s.Find("span.keyword")
if returns.Length() != 0 {
returnsText := CleanUp(returns.Text())
returnsText = strings.ReplaceAll(returnsText, "function", "")
returnsText = CleanUp(returnsText)
res.Returns = append(res.Returns, Return{
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
}
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
---@class {{.ClassName}}
---{{.Description}}
{{- range .Fields}}
---@field {{.Name}} {{.Type}}{{if ne .Comment ""}} {{.Comment}}{{end}}
{{- end}}
@@ -15,8 +16,7 @@
{{- 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}}
{{- end}}
{{end}}
{{- end}}
}
@@ -26,7 +26,8 @@
{{- if ne .Comment ""}}
---{{.Name}} ({{.Type}}) -> {{.Comment}}
{{- 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 ""}}
---{{.Comment}}
{{- end}}

31
main.go
View File

@@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
"strings"
"sync"
"github.com/davecgh/go-spew/spew"
@@ -61,20 +62,18 @@ func main() {
}
func MapType(t string) string {
switch t {
case "var":
return "any"
case "int":
return "number"
case "float":
return "number"
case "double":
return "number"
case "bool":
return "boolean"
case "table_t":
return "table"
default:
return t
}
t = strings.ReplaceAll(t, "var", "any")
t = strings.ReplaceAll(t, "int", "number")
t = strings.ReplaceAll(t, "unsigned", "")
t = strings.ReplaceAll(t, "float", "number")
t = strings.ReplaceAll(t, "double", "number")
t = strings.ReplaceAll(t, "bool", "boolean")
t = strings.ReplaceAll(t, "table_t", "table")
t = strings.ReplaceAll(t, "...", "[]")
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="">
<p><span class="docheader">Parameters</span></p>
<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
existing entity or nil for the entity in the current script context <br />
</div>