286 lines
8.0 KiB
Go
286 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
_ "embed"
|
|
|
|
logger "git.site.quack-lab.dev/dave/cylogger"
|
|
utils "git.site.quack-lab.dev/dave/cyutils"
|
|
"github.com/PuerkitoBio/goquery"
|
|
)
|
|
|
|
func main() {
|
|
outdir := flag.String("o", ".", "Output directory")
|
|
flag.Parse()
|
|
logger.InitFlag()
|
|
logger.Info("Starting...")
|
|
|
|
files := flag.Args()
|
|
if len(files) == 0 {
|
|
logger.Error("No files specified")
|
|
flag.Usage()
|
|
return
|
|
}
|
|
|
|
// Group files by their base class name (without [Client]/[Server])
|
|
classGroups := make(map[string][]string)
|
|
for _, file := range files {
|
|
// Extract base class name from filename
|
|
baseName := extractBaseClassName(file)
|
|
classGroups[baseName] = append(classGroups[baseName], file)
|
|
}
|
|
|
|
// Convert groups to work items for parallel processing
|
|
var workItems []WorkItem
|
|
for baseName, groupFiles := range classGroups {
|
|
workItems = append(workItems, WorkItem{
|
|
BaseName: baseName,
|
|
GroupFiles: groupFiles,
|
|
})
|
|
}
|
|
|
|
// Process each group in parallel
|
|
utils.WithWorkers(100, workItems, func(worker int, item WorkItem) {
|
|
if len(item.GroupFiles) == 1 {
|
|
// Single file, process normally
|
|
class, err := ParseClass(item.GroupFiles[0])
|
|
if err != nil {
|
|
logger.Error("Error parsing file: %v", err)
|
|
return
|
|
}
|
|
class.Write(*outdir, classTemplate)
|
|
} else {
|
|
// Multiple files for same class, merge them
|
|
mergedClass, err := MergeClasses(item.GroupFiles)
|
|
if err != nil {
|
|
logger.Error("Error merging classes for %s: %v", item.BaseName, err)
|
|
return
|
|
}
|
|
mergedClass.Write(*outdir, classTemplate)
|
|
}
|
|
})
|
|
}
|
|
|
|
func MapType(t string) string {
|
|
// Handle complex types like table<int, string>
|
|
if strings.Contains(t, "<") && strings.Contains(t, ">") {
|
|
// Extract the base type and inner types
|
|
openBracket := strings.Index(t, "<")
|
|
closeBracket := strings.LastIndex(t, ">")
|
|
if openBracket != -1 && closeBracket != -1 {
|
|
baseType := t[:openBracket]
|
|
innerTypes := t[openBracket+1 : closeBracket]
|
|
|
|
// Split inner types by comma, but be careful about nested brackets
|
|
var mappedInnerTypes []string
|
|
var current strings.Builder
|
|
bracketDepth := 0
|
|
|
|
for i := 0; i < len(innerTypes); i++ {
|
|
char := innerTypes[i]
|
|
if char == '<' {
|
|
bracketDepth++
|
|
current.WriteByte(char)
|
|
} else if char == '>' {
|
|
bracketDepth--
|
|
current.WriteByte(char)
|
|
} else if char == ',' && bracketDepth == 0 {
|
|
// Only split on commas that are not inside nested brackets
|
|
mappedInnerTypes = append(mappedInnerTypes, MapType(strings.TrimSpace(current.String())))
|
|
current.Reset()
|
|
} else {
|
|
current.WriteByte(char)
|
|
}
|
|
}
|
|
// Add the last inner type
|
|
if current.Len() > 0 {
|
|
mappedInnerTypes = append(mappedInnerTypes, MapType(strings.TrimSpace(current.String())))
|
|
}
|
|
|
|
// Reconstruct the complex type
|
|
return baseType + "<" + strings.Join(mappedInnerTypes, ", ") + ">"
|
|
}
|
|
}
|
|
|
|
// Handle simple types
|
|
switch t {
|
|
case "var":
|
|
return "any"
|
|
case "var...":
|
|
return "..."
|
|
case "int":
|
|
return "number"
|
|
case "int...":
|
|
return "number..."
|
|
case "unsigned int":
|
|
return "number"
|
|
case "float":
|
|
return "number"
|
|
case "double":
|
|
return "number"
|
|
case "bool":
|
|
return "boolean"
|
|
case "char":
|
|
return "string"
|
|
case "table_t":
|
|
return "table"
|
|
default:
|
|
return t
|
|
}
|
|
}
|
|
|
|
func IsReservedKeyword(t string) bool {
|
|
switch t {
|
|
case "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "any", "boolean", "number", "string", "table", "thread", "userdata", "var":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// extractBaseClassName extracts the base class name from a filename
|
|
// e.g., "Player [Client].html" -> "Player"
|
|
func extractBaseClassName(filename string) string {
|
|
// Extract filename without path
|
|
base := filepath.Base(filename)
|
|
// Remove .html extension
|
|
base = strings.TrimSuffix(base, ".html")
|
|
// Remove [Client] or [Server] suffix
|
|
base = strings.ReplaceAll(base, " [Client]", "")
|
|
base = strings.ReplaceAll(base, " [Server]", "")
|
|
return base
|
|
}
|
|
|
|
// MergeClasses merges multiple class files into a single class
|
|
func MergeClasses(files []string) (*Class, error) {
|
|
if len(files) == 0 {
|
|
return nil, fmt.Errorf("no files to merge")
|
|
}
|
|
|
|
// Parse all classes
|
|
var classes []*Class
|
|
var baseName string
|
|
var inheritance string
|
|
|
|
for _, file := range files {
|
|
class, err := ParseClass(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing %s: %w", file, err)
|
|
}
|
|
classes = append(classes, class)
|
|
|
|
// Use the first class name as base
|
|
if baseName == "" {
|
|
baseName = class.ClassName
|
|
}
|
|
|
|
// Check for inheritance information in the original file
|
|
originalClassName := getOriginalClassName(file)
|
|
if strings.Contains(originalClassName, " : ") {
|
|
parts := strings.Split(originalClassName, " : ")
|
|
if len(parts) == 2 {
|
|
// Extract the inherited class name and clean it
|
|
inheritedClass := strings.TrimSpace(parts[1])
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, "[Client]", "")
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, "[Server]", "")
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, "[", "")
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, "]", "")
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, "-", "_")
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, ",", "")
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, " ", "_")
|
|
// Clean up multiple underscores
|
|
for strings.Contains(inheritedClass, "__") {
|
|
inheritedClass = strings.ReplaceAll(inheritedClass, "__", "_")
|
|
}
|
|
inheritedClass = strings.Trim(inheritedClass, "_")
|
|
|
|
if inheritance == "" {
|
|
inheritance = inheritedClass
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merge all classes into the first one
|
|
merged := classes[0]
|
|
|
|
// Set inheritance if found
|
|
if inheritance != "" {
|
|
merged.Inheritance = inheritance
|
|
}
|
|
|
|
// Create maps to track methods and fields by name
|
|
methodMap := make(map[string]*Method)
|
|
fieldMap := make(map[string]*Field)
|
|
|
|
// Add methods from all classes, handling duplicates
|
|
for _, class := range classes {
|
|
for _, method := range class.Methods {
|
|
if existing, exists := methodMap[method.Name]; exists {
|
|
// Method exists in multiple contexts, update availability
|
|
if !strings.Contains(existing.Comment, "[Client/Server]") {
|
|
if strings.Contains(existing.Comment, "[Client]") && strings.Contains(method.Comment, "[Server]") {
|
|
existing.Comment = "[Client/Server] " + strings.TrimPrefix(existing.Comment, "[Client] ")
|
|
} else if strings.Contains(existing.Comment, "[Server]") && strings.Contains(method.Comment, "[Client]") {
|
|
existing.Comment = "[Client/Server] " + strings.TrimPrefix(existing.Comment, "[Server] ")
|
|
}
|
|
}
|
|
} else {
|
|
// New method, add it
|
|
methodMap[method.Name] = &method
|
|
}
|
|
}
|
|
|
|
// Add fields from all classes, avoiding duplicates
|
|
for _, field := range class.Fields {
|
|
if _, exists := fieldMap[field.Name]; !exists {
|
|
fieldMap[field.Name] = &field
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert maps back to slices
|
|
merged.Methods = make([]Method, 0, len(methodMap))
|
|
for _, method := range methodMap {
|
|
merged.Methods = append(merged.Methods, *method)
|
|
}
|
|
|
|
merged.Fields = make([]Field, 0, len(fieldMap))
|
|
for _, field := range fieldMap {
|
|
merged.Fields = append(merged.Fields, *field)
|
|
}
|
|
|
|
return merged, nil
|
|
}
|
|
|
|
// getOriginalClassName extracts the original class name from the HTML file
|
|
func getOriginalClassName(file string) string {
|
|
filehandle, err := os.Open(file)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
defer filehandle.Close()
|
|
|
|
doc, err := goquery.NewDocumentFromReader(filehandle)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
class := doc.Find("div.floatright > h1")
|
|
if class.Length() == 0 {
|
|
return ""
|
|
}
|
|
|
|
return strings.TrimSpace(class.Text())
|
|
}
|
|
|
|
// WorkItem represents a group of files to be processed
|
|
type WorkItem struct {
|
|
BaseName string
|
|
GroupFiles []string
|
|
}
|