Compare commits

15 Commits

5 changed files with 471 additions and 115 deletions

437
class.go
View File

@@ -3,14 +3,15 @@ package main
import (
_ "embed"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/PuerkitoBio/goquery"
"github.com/davecgh/go-spew/spew"
)
//go:embed class.tmpl
@@ -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
}
)
@@ -76,6 +79,7 @@ func (c *Class) GetOutFile(root string) (*os.File, error) {
filename = strings.ReplaceAll(filename, ":", "")
filePath := filepath.Join(root, filename)
filePath = filepath.Clean(filePath)
log.Printf("Writing file to '%s'", filePath)
f, err := os.Create(filePath)
if err != nil {
return nil, err
@@ -88,6 +92,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)
@@ -95,14 +100,26 @@ func (c *Class) Write(root string, tmpl *template.Template) error {
return nil
}
func ParseClass(file string) (*Class, error) {
log.Printf("Parsing file: '%s'", file)
type ClassParser struct {
log *log.Logger
inputfile string
}
func NewClassParser(file string) *ClassParser {
return &ClassParser{
log: log.New(io.MultiWriter(os.Stdout, os.Stderr), fmt.Sprintf("%s[ClassParser] %s%s", GenerateRandomAnsiColor(), file, Reset), log.Lmicroseconds|log.Lshortfile),
inputfile: file,
}
}
func (p *ClassParser) Parse() (*Class, error) {
p.log.Printf("Parsing file: '%s'", p.inputfile)
res := Class{
Fields: []Field{},
Methods: []Method{},
Constructors: []Constructor{},
}
filehandle, err := os.Open(file)
filehandle, err := os.Open(p.inputfile)
if err != nil {
return nil, fmt.Errorf("error opening file: %w", err)
}
@@ -112,127 +129,365 @@ func ParseClass(file string) (*Class, error) {
if err != nil {
return nil, fmt.Errorf("error parsing file: %w", err)
}
p.log.Printf("Document loaded")
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())
p.log.Printf("Data container found")
res.Constructors, err = getConstructors(doc)
className, err := p.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
p.log.Printf("Class name resolved as '%s'", res.ClassName)
classDescription, err := p.getClassDescription(dataContainer)
if err == nil {
// return nil, fmt.Errorf("error getting class description: %w", err)
res.Description = classDescription
p.log.Printf("Class description resolved as '%s'", res.Description)
}
res.Fields, err = getFields(doc)
constructor, err := p.getConstructor(dataContainer)
if err != nil {
return nil, fmt.Errorf("error getting fields: %w", err)
return nil, fmt.Errorf("error getting constructor: %w", err)
}
res.Constructors = append(res.Constructors, constructor)
p.log.Printf("Constructor resolved")
res.Methods, err = getMethods(doc)
if err != nil {
return nil, fmt.Errorf("error getting methods: %w", err)
// 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")
}
p.log.Printf("Data elements (%d) found", dataElements.Length())
// 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) {
p.log.Printf("Parsing field")
field, err := p.parseField(s)
if err != nil {
Error.Printf("Error parsing field: %v", err)
return
}
res.Fields = append(res.Fields, field)
})
} else {
p.log.Printf("Parsing method")
method, err := p.parseMethod(s)
if err != nil {
Error.Printf("Error parsing method: %v", err)
return
}
res.Methods = append(res.Methods, method)
}
})
p.log.Printf("Class parsed")
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 (p *ClassParser) getClassName(dataContainer *goquery.Selection) (string, error) {
p.log.Printf("Getting class name")
class := dataContainer.ChildrenFiltered("h1")
if class.Length() == 0 {
return "", fmt.Errorf("no class found")
}
res := CleanUp(class.Text())
p.log.Printf("Class name resolved as '%s'", res)
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 (p *ClassParser) getClassDescription(dataContainer *goquery.Selection) (string, error) {
p.log.Printf("Getting class description")
class := dataContainer.ChildrenFiltered("h1 + p")
if class.Length() == 0 {
return "", fmt.Errorf("no class description found")
}
res := CleanUp(class.Text())
p.log.Printf("Class description resolved as '%s'", res)
return res, nil
}
func (p *ClassParser) getConstructor(dataContainer *goquery.Selection) (Constructor, error) {
resConstructor := Constructor{}
p.log.Printf("Getting constructor")
constructorBlock := dataContainer.Find("h1 + p + div")
if constructorBlock.Length() == 0 {
p.log.Printf("No constructor block found, trying fallback (h1 + div)")
constructorBlock = dataContainer.Find("h1 + div")
if constructorBlock.Length() == 0 {
return resConstructor, fmt.Errorf("no constructor found")
}
p.log.Printf("Fallback constructor block 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")
}
p.log.Printf("Constructor types found")
params := constructorBlock.Find("div.function span.parameter")
if params.Length() == 0 {
return resConstructor, fmt.Errorf("no params found")
}
p.log.Printf("Constructor 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: "",
})
})
p.log.Printf("Constructor params resolved")
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")
}
p.log.Printf("Constructor 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)
p.log.Printf("Parameter '%s' found", paramTrimmed)
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())
p.log.Printf("Return value '%s' found", cleaned)
if resConstructor.Returns != "" {
resConstructor.Returns += "\n"
}
resConstructor.Returns = cleaned
}
}
res = append(res, property)
})
p.log.Printf("Constructor resolved")
// spew.Dump(resConstructor)
return resConstructor, nil
}
func (p *ClassParser) parseField(s *goquery.Selection) (Field, error) {
res := Field{}
p.log.Printf("Parsing field")
id, ok := s.Attr("id")
if !ok {
return res, fmt.Errorf("no id found")
}
res.Name = id
p.log.Printf("Field name resolved as '%s'", res.Name)
typeElement := s.Find("span.type")
if typeElement.Length() == 0 {
return res, fmt.Errorf("no type found")
}
res.Type = MapType(CleanUp(typeElement.Text()))
p.log.Printf("Field type resolved as '%s'", res.Type)
readonlyElement := s.Find("td[align='right']")
if readonlyElement.Length() != 0 {
// return res, fmt.Errorf("no readonly found")
res.Comment = CleanUp(readonlyElement.Text())
p.log.Printf("Field comment resolved as '%s'", res.Comment)
}
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
}
p.log.Printf("Field comment resolved as '%s'", text)
if res.Comment != "" {
res.Comment += ". "
}
res.Comment += text
})
res.Comment = strings.ReplaceAll(res.Comment, "\n--", ". ")
p.log.Printf("Field resolved")
return res, nil
}
// TODO: Implement parsing return value types and comments
func getMethods(doc *goquery.Document) ([]Method, error) {
res := []Method{}
func (p *ClassParser) parseMethod(s *goquery.Selection) (Method, error) {
res := Method{}
p.log.Printf("Parsing 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
p.log.Printf("Method name resolved as '%s'", res.Name)
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)
signatureData := s.Children().Eq(0)
returns := signatureData.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: "",
})
p.log.Printf("Method return value resolved as '%s'", res.Returns[0].Type)
}
res = append(res, method)
types := signatureData.Find("span.type")
parameters := signatureData.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: "",
})
p.log.Printf("Method parameter resolved as '%s'", res.Params[i].Name)
})
// 0 nothing
// 1 parameters
// 2 returns
state := 0
signatureDetails := s.Children().Eq(1)
signatureDetailsChildren := signatureDetails.Children()
signatureDetailsChildrenLength := signatureDetailsChildren.Length()
signatureDetailsChildren.Each(func(i int, s *goquery.Selection) {
if s.Is("p") && i == 0 {
methodDescription := CleanUp(s.Text())
methodDescription = strings.ReplaceAll(methodDescription, "\n--", "<br>\n\t---")
res.Comment = methodDescription
p.log.Printf("Method description resolved as '%s'", res.Comment)
}
if i == signatureDetailsChildrenLength-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
case "Returns":
state = 2
return
}
} else {
if s.Is("div.indented") {
if state == 1 {
parameter := ""
html, err := s.Html()
if err != nil {
Error.Printf("Error parsing html: %v", err)
return
}
html = strings.ReplaceAll(html, "\t", "")
htmlLines := strings.Split(html, "\n")
for _, line := range htmlLines {
if strings.Contains(line, "<span class=\"parameter\">") {
line = strings.TrimSpace(line)
line = strings.ReplaceAll(line, "<span class=\"parameter\">", "")
line = strings.ReplaceAll(line, "</span>", "")
parameter = line
p.log.Printf("Parameter '%s' found", parameter)
continue
}
if strings.Contains(line, "<br/>") {
comment := strings.TrimSpace(line)
comment = strings.ReplaceAll(comment, "<br/>", "")
for i := range res.Params {
if res.Params[i].Name == parameter {
res.Params[i].Comment = comment
p.log.Printf("Parameter '%s' comment resolved as '%s'", parameter, comment)
break
}
}
}
}
} else if state == 2 {
text := CleanUp(s.Text())
if text == "" {
return
}
p.log.Printf("Return value comment '%s' found", text)
// TODO: Figure out what to do when a function
if res.Returns[0].Comment != "" {
res.Returns[0].Comment += "\n"
}
res.Returns[0].Comment += text
}
}
}
})
p.log.Printf("Method resolved")
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}}
---@class {{.ClassName}}{{if ne .Description ""}}
---{{.Description}}{{end}}
{{- range .Fields}}
---@field {{.Name}} {{.Type}}{{if ne .Comment ""}} {{.Comment}}{{end}}
{{- end}}
@@ -12,11 +13,11 @@
{{- end}}
{{- range $ret := $method.Returns}}
---@return {{.Type}}{{if ne .Comment ""}} #{{.Comment}}{{end}}
{{- end}}
{{- end}}{{if ne .Comment ""}}
---{{.Comment}}{{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}}
{{- if ne (plus1 $index) $n}}
{{end}}
{{- end}}
}
@@ -26,7 +27,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}}

99
color.go Normal file
View File

@@ -0,0 +1,99 @@
package main
import (
"fmt"
"math/rand/v2"
)
const (
// Reset
Reset = "\033[0m" // Text Reset
// Regular Colors
Black = "\033[0;30m" // Black
Red = "\033[0;31m" // Red
Green = "\033[0;32m" // Green
Yellow = "\033[0;33m" // Yellow
Blue = "\033[0;34m" // Blue
Purple = "\033[0;35m" // Purple
Cyan = "\033[0;36m" // Cyan
White = "\033[0;37m" // White
// Bold
BBlack = "\033[1;30m" // Black
BRed = "\033[1;31m" // Red
BGreen = "\033[1;32m" // Green
BYellow = "\033[1;33m" // Yellow
BBlue = "\033[1;34m" // Blue
BPurple = "\033[1;35m" // Purple
BCyan = "\033[1;36m" // Cyan
BWhite = "\033[1;37m" // White
// Underline
UBlack = "\033[4;30m" // Black
URed = "\033[4;31m" // Red
UGreen = "\033[4;32m" // Green
UYellow = "\033[4;33m" // Yellow
UBlue = "\033[4;34m" // Blue
UPurple = "\033[4;35m" // Purple
UCyan = "\033[4;36m" // Cyan
UWhite = "\033[4;37m" // White
// Background
On_Black = "\033[40m" // Black
On_Red = "\033[41m" // Red
On_Green = "\033[42m" // Green
On_Yellow = "\033[43m" // Yellow
On_Blue = "\033[44m" // Blue
On_Purple = "\033[45m" // Purple
On_Cyan = "\033[46m" // Cyan
On_White = "\033[47m" // White
// High Intensty
IBlack = "\033[0;90m" // Black
IRed = "\033[0;91m" // Red
IGreen = "\033[0;92m" // Green
IYellow = "\033[0;93m" // Yellow
IBlue = "\033[0;94m" // Blue
IPurple = "\033[0;95m" // Purple
ICyan = "\033[0;96m" // Cyan
IWhite = "\033[0;97m" // White
// Bold High Intensty
BIBlack = "\033[1;90m" // Black
BIRed = "\033[1;91m" // Red
BIGreen = "\033[1;92m" // Green
BIYellow = "\033[1;93m" // Yellow
BIBlue = "\033[1;94m" // Blue
BIPurple = "\033[1;95m" // Purple
BICyan = "\033[1;96m" // Cyan
BIWhite = "\033[1;97m" // White
// High Intensty backgrounds
On_IBlack = "\033[0;100m" // Black
On_IRed = "\033[0;101m" // Red
On_IGreen = "\033[0;102m" // Green
On_IYellow = "\033[0;103m" // Yellow
On_IBlue = "\033[0;104m" // Blue
On_IPurple = "\033[10;95m" // Purple
On_ICyan = "\033[0;106m" // Cyan
On_IWhite = "\033[0;107m" // White
)
// The acceptable range is [16, 231] but here we remove some very dark colors
// That make text unreadable on a dark terminal
// See https://www.hackitu.de/termcolor256/
var colors = []int{22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 57, 62, 63, 64, 65, 67, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 184, 185, 186, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 226, 227, 228, 229, 230}
var colorsIndex int = -1
var shuffled bool
func GenerateRandomAnsiColor() string {
if !shuffled {
rand.Shuffle(len(colors), func(i int, j int) {
colors[i], colors[j] = colors[j], colors[i]
})
shuffled = true
}
colorsIndex++
return fmt.Sprintf("\033[1;4;38;5;%dm", colors[colorsIndex%len(colors)])
}

34
main.go
View File

@@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
"strings"
"sync"
"github.com/davecgh/go-spew/spew"
@@ -49,7 +50,8 @@ func main() {
wg.Add(1)
go func(file string) {
defer wg.Done()
class, err := ParseClass(file)
parser := NewClassParser(file)
class, err := parser.Parse()
if err != nil {
Error.Printf("Error parsing file: %v", err)
return
@@ -61,20 +63,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>