package main import ( "flag" "fmt" "io" "log" "os" "strings" "sync" "text/template" "github.com/PuerkitoBio/goquery" "github.com/davecgh/go-spew/spew" _ "embed" ) //go:embed class.tmpl var templatestr string var Error *log.Logger var Warning *log.Logger func init() { log.SetFlags(log.Lmicroseconds | log.Lshortfile) logger := io.MultiWriter(os.Stdout) log.SetOutput(logger) Error = log.New(io.MultiWriter(os.Stderr, os.Stdout), fmt.Sprintf("%sERROR:%s ", "\033[0;101m", "\033[0m"), log.Lmicroseconds|log.Lshortfile) Warning = log.New(io.MultiWriter(os.Stdout), fmt.Sprintf("%sWarning:%s ", "\033[0;93m", "\033[0m"), log.Lmicroseconds|log.Lshortfile) spew.Config.Indent = " " spew.Config.SortKeys = true } var fns = template.FuncMap{ "plus1": func(x int) int { return x + 1 }, } func main() { flag.Parse() files := flag.Args() // if len(files) == 0 { // Error.Printf("No files specified") // flag.Usage() // return // } ltemplate, err := template.New("class").Funcs(fns).Parse(templatestr) if err != nil { Error.Printf("Error parsing template: %v", err) return } wg := sync.WaitGroup{} for _, file := range files { wg.Add(1) go func(file string) { defer wg.Done() class, err := ParseFile(file) if err != nil { Error.Printf("Error parsing file: %v", err) return } outfile, err := class.GetOutFile() if err != nil { Error.Printf("Error creating output file: %v", err) return } ltemplate.Execute(outfile, class) }(file) } wg.Wait() } func ParseFile(filename string) (*Class, error) { log.Printf("Parsing file: '%s'", filename) res := Class{ Fields: []Field{}, Methods: []Method{}, Constructors: []Constructor{}, } file, err := os.Open(filename) if err != nil { return nil, fmt.Errorf("error opening file: %w", err) } defer file.Close() doc, err := goquery.NewDocumentFromReader(file) if err != nil { 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") } res.ClassName = strings.TrimSpace(class.Text()) codeblocks := doc.Find("div.floatright > div.codecontainer") if codeblocks.Length() == 0 { return nil, fmt.Errorf("no codeblocks 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) resConstructor.Params = append(resConstructor.Params, Param{ Name: pname, Type: ptype, Comment: "", }) }) res.Constructors = append(res.Constructors, resConstructor) 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) } res.Fields = append(res.Fields, property) }) 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()) 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) }) res.Methods = append(res.Methods, method) }) spew.Dump(res) return &res, nil } 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 } }